通过解析ffprobe输出以VB格式填充字段值 [英] Populate field values in a VB form by parsing ffprobe output

查看:94
本文介绍了通过解析ffprobe输出以VB格式填充字段值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Ok. I am attempting to grab pertinent media information from video files. To do this, I run ffprobe from my script. Something like this:

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

哪个创建的文件包含以下内容:

Which creates a file containing something like this:

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

我说类似",因为流#0:0(und):行上的数据并不总是顺序相同.我需要从日志中获取以下信息:

I say, "something like this," since the data on the Stream #0:0(und): line is not always in the same order. I need to grab the following information from the log:

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

在批处理脚本中,我将使用令牌和定界符扫描我的信息,并检查是否已获取正确的数据;如果没有,请尝试下一个令牌.

In a batch script, I would use tokens and delimiters to scan for my information, and check to see that I grabbed the correct data; if not, try the next token.

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

Here is an example of how I performed the task in batch script:

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用于桌面.

How can I do this in VB? I am using Visual Studio Express 2015 for desktop.

好吧,在进行了更多的挖掘和玩耍之后,这就是我设法完成任务的方式:

Ok, after doing some more digging and playing, here is how I managed to accomplish the task:

 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]填充表单字段,这应该很好.

All I need to do now is to populate my form's fields using the variables in the form's code: Me.[field].text = [variable] and it should be good to go.

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

As you can see, I took advantage of the curRow() array to narrow down the fields using mid() and InStr() functions. Though it seems to have accomplished the task, I'm not sure if it was the best way to do it, or if there might be another way.

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

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

推荐答案

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

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

There is a more direct way to get the info rather than using the old Shell command, and a slightly more organized way to access the info. The following will read the result from ffprobe directly from standard output. To do this, use the shiny new Net Process class instead of the legacy Shell.

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

FFProbe also supports json output (and xml, I think), which will allow you to query a JObject to fetch back params by "name". The command line argument for this is -print_format json.

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

The following will get the same properties you show but will put them in a spiffy ListView rather than a MsgBox. Because of the encoding, the references can be a bit long, but I'll show how to shorten them. Most important, there is no string parsing involved which will be more direct. You will need Newtonsoft's JSON.NET for this:

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看起来像这样(部分显示!):

The resulting json looks like this (partial display!):

{
    "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,
        ...

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

"streams" is a 4 element array; apparently Video is element 0 and Audio is 1. I have no idea what the other 2 are. Each of those can be deserialized to a Dictionary of Name-Value pairs representing the media properties.

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

After Parsing it, the video codec would be: myJObj("streams")(0)("codec_name").ToString():

  • ("streams")(0)引用第0个流数组元素
  • ("codec_name")是该属性的键或名称.大多数值是字符串,但有些是数字.
  • ("streams")(0) references the 0th streams array element
  • ("codec_name") is the key or name of that property. Most values are string, but a few are numeric.

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

The following code (continued from above), will shorten those references by creating a reference to the desired property set. As I said, I am putting these in a ListView and using Groups.

' 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.

Almost everything is returned as string, and many need some calculation or formatting performed. For instance, avg frame rate comes back as "2997/100" so you can see where I split it, convert to integer and divide. Also the audio bit rate appears to be decimal not metric and can be something like 127997.

您将能够在调试器中查看各种键和值以查找其他值.您也可以将生成的json字符串粘贴到 jsonlint 中,以更好地理解结构并读取密钥.

You will be able to see the various keys and values in the debugger to locate other values. You can also paste the resulting json string to jsonlint to better understand the structure and read the keys.

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

引用可以是密集的或冗长的",但看起来比切碎字符串要少很多乏味和繁琐.结果:

The references can be dense or "wordy" but it seems far less tedious and involved than chopping up strings. The result:

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存在一些问题.

Compared to what? I did a quick test on 6 files and MediaInfo matched both Explorer and ffprobe. It too has multiple entries and there is some issue with CBR/VBR.

这篇关于通过解析ffprobe输出以VB格式填充字段值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆