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

本文关键字:存储 一帧 文件 何获得 azure 的视频 | 更新日期: 2023-09-27 18:13:46

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

任何帮助都将非常感激。由于

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

您可以使用Windows Azure媒体服务执行此操作。这听起来像是他们可能会帮你处理的事情,但我对他们没有任何特别的经验。

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

  1. 将blob从存储中取出到您可以使用它的地方
  2. 抓取帧
  3. 将框架保存到SQL Azure。

我假设你能弄清楚#1  .自己做。对于第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;
        }
    }
}

依赖于以下helper类:

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