通过解析ffprobe输出以VB格式填充字段值 [英] Populate field values in a VB form by parsing ffprobe output
问题描述
好的.我正在尝试从视频文件中获取相关的媒体信息.为此,我从脚本运行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屋!