0

ブロックブロブとしてAzureストレージに保存されているビデオファイル(mp4、wmv、mov)の最初のフレームを抽出する必要がありました。これを httphandler 内で実行し、SQL Azure データベースのテーブルにバイト バッファーとして格納する必要がありました。

どんな助けでも大歓迎です。ありがとう

4

1 に答える 1

1

これは、Windows Azure メディア サービスで実現できる可能性があります。彼らがあなたのために処理するようなものに聞こえますが、私は彼らとの特別な経験はありません.

それ以外の場合は、自分で行う必要があります。それは次のようになります。

  1. BLOB をストレージから取り出して、作業できる場所に移動します
  2. フレームを取り出します。
  3. フレームを SQL 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; }
}
于 2013-05-16T15:13:24.987 に答える