我必须提取作为块 blob 存储在 azure 存储中的视频文件(mp4、wmv、mov)的第一帧。我必须在 httphandler 中执行此操作,然后将其作为字节缓冲区存储到我们的 SQL Azure 数据库上的表中。
任何帮助将不胜感激。谢谢
我必须提取作为块 blob 存储在 azure 存储中的视频文件(mp4、wmv、mov)的第一帧。我必须在 httphandler 中执行此操作,然后将其作为字节缓冲区存储到我们的 SQL Azure 数据库上的表中。
任何帮助将不胜感激。谢谢
您可以使用 Windows Azure 媒体服务来完成此操作。这听起来像是他们可能会为你处理的那种事情,但我对他们没有任何特别的经验。
否则,你必须自己做。看起来像这样:
我假设您可以自己弄清楚#1和#3。对于#2,我建议查看ffmpeg。这有点让人头疼,但它几乎可以从任何东西中抓住一个框架。实际上,我已经为此使用了几年,而且效果还不错。我用于抓帧的参数是:
ffmpeg -i <inputFileName> -frames:v 1 -ss <video duration in seconds / 2> -f image2 <output file name>
但肯定有更多的方法可以做到这一点。
示例代码: 这是我如何做的(有些简化的)版本。注意我的 ffmpeg 版本有点旧,所以命令行参数可能已经改变。但基本的想法应该有效。
/// <summary>
/// Extract a thumbnail from the middle (by duration) of a video file
/// </summary>
/// <param name="inputFileName">Path to the video file on the local filesystem</param>
/// <param name="duration">Duration of the video</param>
/// <returns></returns>
public Image ExtractThumbnail(string inputFileName)
{
if (string.IsNullOrEmpty(inputFileName))
{
throw new ArgumentNullException("inputFileName", "Input file is null");
}
TimeSpan duration = GetVideoDuration(inputFileName);
const string framegrabTemplate = @"-i ""{0}"" -frames:v 1 -ss {2:##0.0##} -f image2 {1}";
string framegrabArgs = string.Format(framegrabTemplate, inputFileName, OutputFileName, duration.TotalSeconds / 2);
WindowsProcessResult result = null;
try
{
result = WindowsProcessUtil.RunProcess(ExePath, framegrabArgs);
}
catch (Exception ex)
{
log.Error("Framegrab process failed with exception {0}.", ex);
return null;
}
if (result.ExitCode != 0)
{
log.Error("Framegrab process failed with exitCode {0}. Process output:\r\n{1}\r\nProcess Error: {2}", result.ExitCode, result.StandardOutput, result.StandardError);
return null;
}
var img = Image.FromFile(OutputFileName);
//Certain video sources (primarily iOS v5 and below) will give you videos rotated to one side with embedded metadata telling you what that rotation is
//If you need to deal with that, you can set rotationAngle from that metadata, but that's a whole other issue
//Leaving it here for now as an FYI
//int rotationAngle = 0;
//if (rotationAngle != 0)
//{
// if (rotationAngle == 90)
// {
// img.RotateFlip(RotateFlipType.Rotate90FlipNone);
// }
// else if (rotationAngle == 180)
// {
// img.RotateFlip(RotateFlipType.Rotate180FlipNone);
// }
// else if (rotationAngle == 270)
// {
// img.RotateFlip(RotateFlipType.Rotate270FlipNone);
// }
//}
return img;
}
private static readonly Regex durationRegex = new Regex(@"Duration\: (?<duration>[\d\:\.]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
private TimeSpan GetVideoDuration(string inputFileName)
{
Process getDurationProcess = null;
try
{
const string fileInfoTemplate = @"-i ""{0}""";
ProcessStartInfo psi = new ProcessStartInfo(ExePath, string.Format(fileInfoTemplate, inputFileName));
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.CreateNoWindow = true;
//psi.WorkingDirectory = Path.GetDirectoryName(ExePath);
//psi.EnvironmentVariables["Path"] = psi.EnvironmentVariables["Path"] + ";" + Path.GetDirectoryName(ExePath);
getDurationProcess = new Process();
getDurationProcess.StartInfo = psi;
getDurationProcess.EnableRaisingEvents = true;
StringBuilder processOutput = new StringBuilder();
StringBuilder processError = new StringBuilder();
getDurationProcess.OutputDataReceived += (o, args) =>
{
processOutput.AppendLine(args.Data);
};
getDurationProcess.ErrorDataReceived += (o, args) =>
{
processError.AppendLine(args.Data);
};
getDurationProcess.Start();
getDurationProcess.BeginOutputReadLine();
getDurationProcess.BeginErrorReadLine();
getDurationProcess.WaitForExit();
//Don't do this - ffmpeg errors out because we didn't give it an output file
//if (getDurationProcess.ExitCode != 0)
//{
// log.Error("Get video duration process failed with exitCode {0}. Process output:\r\n{1}\r\nProcess Error: {2}", getDurationProcess.ExitCode, processOutput, processError);
//}
//Now we need to parse output
#region Sample output
/*
ffmpeg version git-N-29946-g27614b1, Copyright (c) 2000-2011 the FFmpeg developers
built on May 15 2011 15:07:09 with gcc 4.5.3
configuration: --enable-gpl --enable-version3 --enable-memalign-hack --enable-
runtime-cpudetect --enable-avisynth --enable-bzlib --enable-frei0r --enable-libo
pencore-amrnb --enable-libopencore-amrwb --enable-libfreetype --enable-libgsm --
enable-libmp3lame --enable-libopenjpeg --enable-librtmp --enable-libschroedinger
--enable-libspeex --enable-libtheora --enable-libvorbis --enable-libvpx --enabl
e-libx264 --enable-libxavs --enable-libxvid --enable-zlib --pkg-config=pkg-confi
g
libavutil 51. 2. 1 / 51. 2. 1
libavcodec 53. 5. 0 / 53. 5. 0
libavformat 53. 0. 3 / 53. 0. 3
libavdevice 53. 0. 0 / 53. 0. 0
libavfilter 2. 5. 0 / 2. 5. 0
libswscale 0. 14. 0 / 0. 14. 0
libpostproc 51. 2. 0 / 51. 2. 0
Seems stream 0 codec frame rate differs from container frame rate: 180000.00 (18
0000/1) -> 30.00 (30/1)
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '..\fromQuicktime.mp4':
Metadata:
major_brand : mp42
minor_version : 0
compatible_brands: mp42isomavc1
creation_time : 2011-04-22 16:36:45
encoder : HandBrake 0.9.5 2011010300
Duration: 00:00:22.13, start: 0.000000, bitrate: 712 kb/s
Stream #0.0(und): Video: h264 (High), yuv420p, 480x272, 578 kb/s, 30 fps, 30
tbr, 90k tbn, 180k tbc
Metadata:
creation_time : 2011-04-22 16:36:45
Stream #0.1(und): Audio: aac, 44100 Hz, mono, s16, 127 kb/s
Metadata:
creation_time : 2011-04-22 16:36:45
At least one output file must be specified
* */
#endregion
processOutput.Append(processError.ToString());
Match m = durationRegex.Match(processOutput.ToString());
Group durationGroup = m.Groups["duration"];
TimeSpan duration;
if (!TimeSpan.TryParse(durationGroup.Value, out duration))
{
log.Error("Failed to parse duration from FFMpeg output:\r\n{0}", processOutput);
return TimeSpan.Zero;
}
else
{
return duration;
}
}
finally
{
if (getDurationProcess != null)
{
getDurationProcess.Dispose();
getDurationProcess = null;
}
}
}
这依赖于以下辅助类:
public static class WindowsProcessUtil
{
/// <summary>
/// Spawn a Windows process, capture StandardOut and StandardError, and wait for it to complete
/// </summary>
public static WindowsProcessResult RunProcess(string exePath, string cmdLineArgs, TimeSpan? timeout = null)
{
Process p = null;
StringBuilder processOutput = new StringBuilder();
StringBuilder processError = new StringBuilder();
int exitCode = 0;
try
{
ProcessStartInfo psi = new ProcessStartInfo(exePath, cmdLineArgs);
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.CreateNoWindow = true;
psi.WorkingDirectory = Path.GetDirectoryName(exePath);
psi.EnvironmentVariables["Path"] = psi.EnvironmentVariables["Path"] + ";" + Path.GetDirectoryName(exePath);
psi.LoadUserProfile = true;
p = new Process();
p.StartInfo = psi;
p.EnableRaisingEvents = true;
p.OutputDataReceived += (o, args) =>
{
processOutput.AppendLine(args.Data);
};
p.ErrorDataReceived += (o, args) =>
{
processError.AppendLine(args.Data);
};
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
if (timeout.HasValue)
{
bool processExited = p.WaitForExit((int)timeout.Value.TotalMilliseconds);
if (!processExited)
{
p.Kill();
throw new TimeoutException("Process did not complete after " + timeout.Value.TotalMilliseconds + " msec");
}
}
else
{
p.WaitForExit();
}
exitCode = p.ExitCode;
}
finally
{
if (p != null)
{
p.Dispose();
p = null;
}
}
return new WindowsProcessResult()
{
ExitCode = exitCode,
StandardError = processError.ToString(),
StandardOutput = processOutput.ToString()
};
}
}
public class WindowsProcessResult
{
public int ExitCode { get; set; }
public string StandardOutput { get; set; }
public string StandardError { get; set; }
}