如何获取存储在azure存储中的视频文件的第一帧

时间:2013-05-16 10:00:42

标签: c# video blob thumbnails azure-storage

我必须提取视频文件的第一帧(mp4,wmv,mov),它存储在azure存储上作为块blob。我必须在httphandler中执行此操作,然后将其作为字节缓冲区存储到SQL Azure数据库的表中。

非常感谢任何帮助。 感谢

1 个答案:

答案 0 :(得分:1)

您可以使用Windows Azure Media Services执行此操作。这听起来像是他们可能为你处理的事情,但我对他们没有任何特殊的经验。

否则,你必须自己做。这看起来像这样:

  1. 将blob从存储中移除到可以使用它的地方
  2. 抓住画架。
  3. 将框架保存到SQL Azure。
  4. 我假设你能算出#1&你自己#3。对于#2,我建议调查ffmpeg。这对屁股来说是一种痛苦,但几乎可以从任何东西中抓住一个框架。实际上,我已经使用它几年了,而且效果相当好。我用于framegrabbing的参数是:

    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; }
    }