ASP.NET MVC项目中上传视频并提取帧/首帧作为缩略图的实现方案
视频上传并提取缩略图(ASP.NET MVC + Azure Blob)
嗨,我来帮你搞定这个需求!你有两个主要方向可以选:要么在本地服务器先提取视频帧,再把视频和缩略图一起传到Azure Blob;要么借助Azure的媒体服务自动处理,不用自己在服务器上折腾视频编码。下面给你详细讲两种方案:
方法一:本地提取视频帧后上传到Azure Blob
Azure Blob Storage本身不直接支持视频帧提取,所以得先在你的MVC项目里处理好缩略图,再上传。这里推荐用MediaToolkit(封装了FFmpeg,用起来很省心):
步骤1:安装依赖
先在NuGet包管理器里安装MediaToolkit:
Install-Package MediaToolkit
或者用.NET CLI:
dotnet add package MediaToolkit
步骤2:修改Controller代码
在你的Upload方法里,先保存上传的视频临时文件,然后提取第一帧,最后把视频和缩略图都传到Azure Blob:
using MediaToolkit; using MediaToolkit.Model; using Azure.Storage.Blobs; using System.IO; [HttpPost] public async Task<ActionResult> Upload(HttpPostedFileBase file) { if (file == null || file.ContentLength == 0) { return RedirectToAction("Index", "Home"); } // 临时文件路径(记得用完要删除) var tempVideoPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + Path.GetExtension(file.FileName)); var tempThumbPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".jpg"); try { // 1. 保存上传的视频到临时文件 file.SaveAs(tempVideoPath); // 2. 提取视频第一帧作为缩略图 var inputFile = new MediaFile { Filename = tempVideoPath }; var outputFile = new MediaFile { Filename = tempThumbPath }; using (var engine = new Engine()) { // 提取第一帧(如果要指定帧,可以调整Seek参数,比如TimeSpan.FromSeconds(5)) engine.GetThumbnail(inputFile, outputFile, TimeSpan.FromSeconds(0)); } // 3. 上传视频和缩略图到Azure Blob var connectionString = "你的Azure Blob连接字符串"; var containerName = "视频存储容器名"; var blobServiceClient = new BlobServiceClient(connectionString); var containerClient = blobServiceClient.GetBlobContainerClient(containerName); // 上传视频 var videoBlobName = $"videos/{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; using (var videoStream = new FileStream(tempVideoPath, FileMode.Open)) { await containerClient.UploadBlobAsync(videoBlobName, videoStream); } // 上传缩略图 var thumbBlobName = $"thumbnails/{Path.GetFileNameWithoutExtension(videoBlobName)}.jpg"; using (var thumbStream = new FileStream(tempThumbPath, FileMode.Open)) { await containerClient.UploadBlobAsync(thumbBlobName, thumbStream); } // 这里可以返回缩略图URL或者成功提示 return Content($"上传成功!视频Blob:{videoBlobName},缩略图Blob:{thumbBlobName}"); } catch (Exception ex) { // 处理异常,比如日志记录 return Content($"上传失败:{ex.Message}"); } finally { // 清理临时文件 if (System.IO.File.Exists(tempVideoPath)) { System.IO.File.Delete(tempVideoPath); } if (System.IO.File.Exists(tempThumbPath)) { System.IO.File.Delete(tempThumbPath); } } }
方法二:用Azure Media Services自动生成缩略图
如果不想在自己的服务器上处理视频编码(比如担心性能、FFmpeg环境配置),可以用Azure Media Services——它专门做媒体处理,能自动帮你生成缩略图、转码等。
核心流程:
- 把上传的视频直接传到Azure Blob(或者Media Services的资产容器)
- 在Media Services里创建一个转码作业,配置缩略图生成规则(比如第一帧、每隔N秒生成一帧)
- 作业完成后,Media Services会把生成的缩略图存到指定的Blob容器里
简化代码示例(需要先配置Media Services账户):
using Azure.ResourceManager.Media; using Azure.ResourceManager.Media.Models; using Azure.Storage.Blobs; [HttpPost] public async Task<ActionResult> Upload(HttpPostedFileBase file) { if (file == null || file.ContentLength == 0) { return RedirectToAction("Index"); } // 1. 先把视频传到Azure Blob的临时容器 var tempContainerName = "temp-uploads"; var blobServiceClient = new BlobServiceClient("你的Blob连接字符串"); var tempContainerClient = blobServiceClient.GetBlobContainerClient(tempContainerName); await tempContainerClient.CreateIfNotExistsAsync(); var tempBlobName = Guid.NewGuid() + Path.GetExtension(file.FileName); using (var stream = file.InputStream) { await tempContainerClient.UploadBlobAsync(tempBlobName, stream); } // 2. 配置Media Services客户端 var mediaServicesClient = new MediaServicesClient("你的Azure订阅ID", new DefaultAzureCredential()); mediaServicesClient.AccountName = "你的Media Services账户名"; mediaServicesClient.ResourceGroupName = "资源组名"; // 3. 创建媒体资产,关联刚才上传的Blob var asset = await mediaServicesClient.Assets.CreateOrUpdateAsync( "资源组名", "你的Media Services账户名", "视频资产名", new Asset { StorageAccountName = "你的存储账户名" }); // 4. 把Blob里的视频导入资产(具体逻辑可参考Media Services SDK官方文档) // 省略导入细节,核心是将临时Blob的内容复制到Media Services资产容器 // 5. 创建转码作业,配置缩略图生成 var transformName = "GenerateThumbnailTransform"; // 先检查Transform是否存在,不存在则创建 var transform = await mediaServicesClient.Transforms.GetIfExistsAsync("资源组名", "账户名", transformName); if (!transform.HasValue) { var output = new TransformOutput( new StandardEncoderPreset( codecs: new CodecBase[] { new JpgImage( start: "PT0S", // 从第0秒开始(第一帧) step: "PT1H", // 只生成一帧 range: "PT0S", layers: new JpgLayer[] { new JpgLayer(quality: 80, width: "1920", height: "1080") } ) }, formats: new Format[] { new JpgFormat(new JpgFormatFilenamePattern("thumbnail-{Index}.jpg")) } )); transform = await mediaServicesClient.Transforms.CreateOrUpdateAsync("资源组名", "账户名", transformName, new Transform { Outputs = { output } }); } // 6. 提交作业 var jobName = $"GenerateThumbnailJob-{Guid.NewGuid()}"; var jobInput = new JobInputAsset(assetName: asset.Name); var jobOutput = new JobOutputAsset(assetName: $"thumbnail-output-{Guid.NewGuid()}"); var job = await mediaServicesClient.Jobs.CreateAsync("资源组名", "账户名", transformName, jobName, new Job { Input = jobInput, Outputs = { jobOutput } }); // 7. 等待作业完成(生产环境建议用WebHook或者定时轮询) // 省略等待逻辑,作业完成后可从输出资产中获取缩略图的Blob URL return Content("作业已提交,稍后可以获取缩略图!"); }
注意事项:
- 方法一中,如果你直接用原生FFmpeg而非MediaToolkit,需要把FFmpeg可执行文件放到项目中,并在初始化
Engine时指定路径(比如new Engine(@"C:\ffmpeg\bin")) - 方法二中,Media Services会产生额外成本,但省去了自己维护视频编码环境的麻烦
- 不管哪种方法,都要确保服务器有读写临时文件的权限,Azure Blob容器的权限配置正确(比如允许上传操作)
内容的提问来源于stack exchange,提问作者Max
相关产品推荐
相关产品推荐

