1

好的。我正在尝试从视频文件中获取相关的媒体信息。为此,我从脚本运行 ffprobe。像这样的东西:

Shell("cmd /c [ffpath]\ffprobe.exe -probesize 1000000 -hide_banner -i ""[path]\[video].mp4"" >""[path]\[video]_probe.log"" 2>&1")

这会创建一个包含以下内容的文件:

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '[path]\[video].mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf57.8.102
  Duration: 00:04:34.41, start: 0.033333, bitrate: 957 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 720x480 [SAR 32:27 DAR 16:9], 820 kb/s, 29.97 fps, 29.97 tbr, 30k tbn, 59.94 tbc (default)
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 128 kb/s (default)
    Metadata:
      handler_name    : SoundHandler

我说“类似这样”,因为 Stream #0:0(und): 行上的数据并不总是以相同的顺序排列。我需要从日志中获取以下信息:

  • 持续时间 (00:04:34.41)
  • 编解码器 (h264)
  • 分辨率 (720x480)
  • 宽度(从 x 之前的分辨率)
  • 高度(从 x 后的分辨率)
  • 帧速率 (29.97 fps)
  • 音频编解码器 (aac)
  • 音频采样频率 (48 KHz)
  • 音频质量 (192 kb/s)

在批处理脚本中,我会使用标记和分隔符来扫描我的信息,并检查我是否抓取了正确的数据;如果没有,请尝试下一个令牌。

这是我如何在批处理脚本中执行任务的示例:

REM Find frame rate and resolution
set count=0
for /f "tokens=1-18* delims=," %%a in (input.log) do (
    REM we only need to parse the first line
    if !count!==0 (
        set fps=%%e
        echo !fps! >res.tmp
        findstr "fps" res.tmp >nul
        if not !errorlevel!==0 (
            set fps=%%f
        )
        set resolution=%%c
        echo !resolution! >res.tmp
        findstr "x" res.tmp >nul
        rem echo errorlevel = !errorlevel!
        if not !errorlevel!==0 (
            set resolution=%%d
        )
        del null
        set fps=!fps:~1,-4!
    )
    set /A count+=1
)

我怎样才能在VB中做到这一点?我正在使用 Visual Studio Express 2015 桌面版。

好的,在进行了更多的挖掘和播放之后,这是我设法完成任务的方法:

 Sub main()
        Dim strDuration As String
        Dim strCodec As String
        Dim strRes As String
        Dim lngWidth, lngHeight As Long
        Dim strAudCodec As String
        Dim dblFPS As Double
        Dim audFreq As Double
        Dim audQual As Double
        Using logReader As New Microsoft.VisualBasic.FileIO.TextFieldParser("..\..\Sirach and Matthew003_probe.log")
            logReader.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited
            logReader.SetDelimiters(",")
            Dim curRow As String()
            While Not logReader.EndOfData
                Try
                    curRow = logReader.ReadFields()
                    'look for and assign duration
                    If Mid(curRow(0), 1, 8) = "Duration" Then
                        strDuration = Mid(curRow(0), 11, 11)
                    End If
                    'look for the video stream row
                    If Mid(curRow(0), 19, 6) = "Video:" Then
                        'Assign the video codec
                        strCodec = Mid(curRow(0), 26, Len(curRow(0)))
                        strCodec = Mid(strCodec, 1, InStr(1, strCodec, " ", CompareMethod.Text) - 1)
                        'Look in each field of current row
                        For i = 0 To 10 Step 1
                            'look for the field containing the resolution ("x" should be the 4th or 5th character)
                            If InStr(1, curRow(i), "x", CompareMethod.Text) = 4 Or InStr(1, curRow(i), "x", CompareMethod.Text) = 5 Then
                                'Assign resolution
                                strRes = Mid(curRow(i), 1, InStr(1, curRow(i), " ", CompareMethod.Text))
                                'Assign Width
                                lngWidth = Mid(strRes, 1, InStr(1, strRes, "x", CompareMethod.Text) - 1)
                                'Assign Heigh
                                lngHeight = Mid(strRes, InStr(1, strRes, "x", CompareMethod.Text) + 1, Len(strRes))
                            End If
                            'loof for fps suffix
                            If Mid(curRow(i), Len(curRow(i)) - 2, 3) = "fps" Then
                                'Assign frame rate
                                dblFPS = Mid(curRow(i), 1, Len(curRow(i)) - 4)
                            End If
                        Next i
                    End If
                    'Look for the audio stream row
                    If Mid(curRow(0), 19, 6) = "Audio:" Then
                        'Assign the audio codec
                        strAudCodec = Mid(curRow(0), 26, Len(curRow(0)))
                        strAudCodec = Mid(strAudCodec, 1, InStr(1, strAudCodec, " ", CompareMethod.Text) - 1)
                        For i = 0 To 10 Step 1
                            'look for the field containing the audio sampling frequency
                            If InStr(1, curRow(i), "Hz", CompareMethod.Text) Then
                                'Assign Audio Sampling Frequency
                                audFreq = Mid(curRow(i), 1, InStr(1, curRow(i), " ", CompareMethod.Text) - 1)
                            End If
                            'look for the field containing the audio quality
                            If InStr(1, curRow(i), "kb/s", CompareMethod.Text) Then
                                'assign audio quality
                                audQual = Mid(curRow(i), 1, InStr(1, curRow(i), " ", CompareMethod.Text) - 1)
                            End If
                        Next
                    End If
                Catch ex As Exception
                End Try
            End While
        End Using
        Dim strMsg As String
        strMsg = "Duration: " & strDuration & Chr(13) _
            & "Codec: " & strCodec & Chr(13) _
            & "Resolution: " & strRes & Chr(13) _
            & "Width: " & lngWidth & Chr(13) _
            & "Height: " & lngHeight & Chr(13) _
            & "Frame Rate: " & dblFPS & " fps" & Chr(13) _
            & "Audio Codec: " & strAudCodec & Chr(13) _
            & "Audio Sampling Freq: " & audFreq & " Hz" & Chr(13) _
            & "Audio Quality: " & audQual & " kb/s" & Chr(13)
        MsgBox(strMsg)
    End Sub

产生结果:

在此处输入图像描述

我现在需要做的就是使用表单代码中的变量填充表单的字段:Me.[field].text = [variable]这应该很好。

如您所见,我利用curRow()数组来缩小使用mid()InStr()函数的字段。虽然它似乎已经完成了任务,但我不确定这是否是最好的方法,或者是否有其他方法。

如果您有任何改进建议,请告诉我。

4

1 回答 1

1

Please let me know if you have any suggestions for improvement.

有一种更直接的方式来获取信息,而不是使用旧的Shell命令,以及一种更有条理的方式来访问信息。以下将ffprobe直接从标准输出中读取结果。为此,请使用闪亮的新 NetProcess类而不是旧的Shell.

FFProbe 还支持 json 输出(我认为是 xml),这将允许您查询 aJObject以通过“名称”取回参数。命令行参数是-print_format json.

以下将获得您显示的相同属性,但会将它们放在一个漂亮的ListView而不是MsgBox. 由于编码的原因,引用可能有点长,但我将展示如何缩短它们。最重要的是,不涉及更直接的字符串解析。为此,您将需要Newtonsoft 的 JSON.NET

Imports Newtonsoft.Json
Imports Newtonsoft.Json.Linq
'...
' the part to run ffprobe and read the result to a string:

Dim Quote = Convert.ToChar(34)
Dim json As String
Using p As New Process
    p.StartInfo.FileName = "...ffprobe.exe"  ' use your path
    p.StartInfo.Arguments = String.Format(" -v quiet -print_format json -show_streams {0}{1}{0}",
                                          Quote, theFileName)
    p.StartInfo.RedirectStandardOutput = True
    p.StartInfo.CreateNoWindow = True
    p.StartInfo.UseShellExecute = False

    p.Start()

    json = p.StandardOutput.ReadToEnd()
End Using

生成的 json 看起来像这样(部分显示!):

{
    "streams": [{
        "index": 0,
        "codec_name": "h264",
        "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
        "profile": "High",
        "codec_type": "video",
        "codec_time_base": "50/2997",
        "codec_tag_string": "avc1",
        "codec_tag": "0x31637661",
        "width": 720,
        "height": 400,
        ...

“streams”是一个 4 元素数组;显然视频是元素 0,音频是 1。我不知道其他 2 是什么。每一个都可以反序列化为代表媒体属性的名称-值对字典。

解析后,视频编解码器将是myJObj("streams")(0)("codec_name").ToString()

  • ("streams")(0)引用第 0 个流数组元素
  • ("codec_name")是该属性的键或名称。大多数值是字符串,但少数是数字。

以下代码(续上)将通过创建对所需属性集的引用来缩短这些引用。正如我所说,我将这些放在一个ListView并使用组中。

' parse to a temp JObject
Dim jobj= JObject.Parse(json)
' get a list/array of dictionary of prop name/value pairs
Dim medprops = JsonConvert.DeserializeObject( _
            Of List(Of Dictionary(Of String, Object)))(jobj("streams").ToString)

' get Video list:
Dim Props = medprops(0)

AddNewLVItem("Codec", Props("codec_name").ToString, "Video")

Dim secs As Double = Convert.ToDouble(Props("duration").ToString)
Dim ts = TimeSpan.FromSeconds(secs)
AddNewLVItem("Duration", ts.ToString("hh\:mm\:ss"), "Video")

AddNewLVItem("Fr Width", Props("width").ToString, "Video")
AddNewLVItem("Fr Height", Props("height").ToString, "Video")

' get avg fr rate text
Dim afr = Props("avg_frame_rate").ToString
Dim AvgFrRate As String = "???"
' split on "/"
If afr.Contains("/") Then
    Dim split = afr.Split("/"c)
    ' calc by dividing (0) by (1)
    AvgFrRate = (Convert.ToDouble(split(0)) / Convert.ToDouble(split(1))).ToString
End If
AddNewLVItem("Avg Frame Rate", AvgFrRate, "Video")

' NB: audio stream values come from element (1):
Props = medprops(1)
AddNewLVItem("Audio Codec", Props("codec_name").ToString, "Audio")
AddNewLVItem("Audio Sample Rate", Props("sample_rate").ToString, "Audio")

' avg bit rate is apparently decimal (1000) not metric (1024)
Dim abr As Integer = Convert.ToInt32(Props("bit_rate").ToString)
abr = Convert.ToInt32(abr / 1000)
AddNewLVItem("Audio Bit Rate", abr.ToString, "Audio")

几乎所有内容都以字符串形式返回,并且许多都需要执行一些计算或格式化。例如,平均帧速率回来了,"2997/100"因此您可以看到我在哪里拆分它,转换为整数并除以。此外,音频比特率似乎是十进制而不是公制,可能类似于127997.

您将能够在调试器中看到各种键和值以定位其他值。您还可以将生成的 json 字符串粘贴到jsonlint以更好地理解结构并读取键。

LV 助手很简单:

Private Sub AddNewLVItem(text As String, value As String, g As String)
    Dim LVI = New ListViewItem
    LVI.Text = text
    LVI.SubItems.Add(value)
    LVI.Group = myLV.Groups(g)
    myLV.Items.Add(LVI)
End Sub

引用可以是密集的或“罗嗦的”,但它似乎远没有切碎字符串那么乏味和复杂。结果:

在此处输入图像描述在此处输入图像描述


I find MediaInfo.dll often fails to return the frame rate, and almost always provides an inaccurate frame count

相比什么?我对 6 个文件进行了快速测试,MediaInfo 匹配了 Explorer 和 ffprobe。它也有多个条目,并且 CBR/VBR 存在一些问题。

于 2015-12-16T18:58:06.207 回答