Batch - 如何对时间码进行数学运算?
Batch - How to perform mathematical operations on time codes?
我一直在处理批处理脚本,以更改一些 .xml
文件 format/type。
快完成了,但我在更改时间码时遇到了问题。
这是一个例子
<Events>
<Event In="00:00:20.650" Out="00:00:22.970"
<Event In="00:00:23.050" Out="00:00:26.300"
此时间格式 hh:mm:ss.ms
应更改为 hh:mm:ss:ff
这意味着,将 Milliseconds
更改为 Frames
。
这样做的公式如下:ms*25/1000
或为简单起见 ms*0.025
输出应该是
<Events>
<Event In="00:00:20:16" Out="00:00:22:24"
<Event In="00:00:23:01" Out="00:00:26:08"
备注
- 0.025 是每毫秒的帧数。
- 帧数必须是整数w/o任何小数部分。
- 我正在使用
GNUWin
和 Cygwin
库工具,所以可以使用 bc
expr
, tr
, awk
sed
或完成工作的任何解决方案。
使用 bc
$ ms="650"
$ ff=$(echo ${ms} "*0.025/1" |bc)
$ echo $ff
16
当结果小于 10 时,我留给你添加额外的“0”
您可以使用 awk 脚本来完成:
script.awk
{
re = "[0-9]{2}:[0-9]{2}:[0-9]{2}.([0-9]{3})"
while( match( [=10=], re, grps) ) {
frames = sprintf("%02.0f",( grps[1] *0.025 ) )
gsub( "." grps[1], ":" frames)
}
print
}
运行 像这样 awk -f script.awk yourfile
。
它尝试匹配这样的时间戳并将最后一部分(毫秒)捕获到 grps[1]
中。然后计算完成,sprintf
用于格式化帧。
之后gsub
用帧替换毫秒。
PowerShell:
[xml]$xml = gc events.xml
function fix([timespan]$ts) {
"{0}:{1:D2}" -f $ts.ToString("hh\:mm\:ss"),[int]($ts.Milliseconds * 0.025)
}
$xml.SelectNodes('//Event') | %{
$_.In = fix $_.In
$_.Out = fix $_.Out
}
$xml.Save("events_fixed.xml")
如果您更愿意将其作为批处理脚本,您可以将其转换为批处理 + PowerShell 多语言,方法是将其插入顶部并使用 .bat 扩展名保存:
<# : batch portion
@echo off & setlocal
powershell "iex (${%~f0} | out-string)"
goto :EOF
: end Batch / begin PowerShell hybrid code #>
这是一个纯粹的 batch-file 解决方案。此脚本对 cmd
具有特殊含义的所有字符都是安全的。基本上,它会在给定的输入文件中搜索关键字或属性名称 In
和 Out
,然后是 =
和用引号括起来的时间代码 ""
。如果找到,则毫秒部分被拆分,转换为帧并附加到剩余的时间代码,以 :
分隔。每行可以有任意数量的时间码,只要它们的格式与示例数据中给定的格式相同并且给定关键字之一位于前面,所有这些时间码都会被识别和转换。
该脚本支持对转换后的值进行四舍五入——请参阅文件开头变量定义 FORMULA
处的备注 (rem
)(在带有备注 [= 的块中查找) =22=]).
这是代码:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set KEYWORDS="In","Out" & rem // (provide a list of attribute names)
set "PATTERN=[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]"
set /A PLENGTH=12 & rem // (char. length of resolved `PATTERN` string)
set "FORMULA=(MS*25+0)/1000" & rem /* (`+0` means to round frames down;
rem use `+999` instead to round up, or `+500` to round to nearest) */
set /A PADDING=2 & rem // (number of digits of resulting frame value)
set "FILE=%~1"
if not defined FILE exit /B 1
for /F "delims=" %%L in ('findstr /N /R "^" "%FILE%"') do (
set "LINE=%%L"
setlocal EnableDelayedExpansion
set "LINE=!LINE:*:=!"
if defined LINE (
for /F delims^=^ eol^= %%E in ("!LINE!") do (
endlocal
set "LINE=%%E"
setlocal EnableDelayedExpansion
)
) else (
endlocal
set "LINE="
setlocal EnableDelayedExpansion
)
for %%P in (!KEYWORDS!) do (
endlocal
set "KEYWORD=%%~P"
call :PROCESS LINE LINE KEYWORD
setlocal EnableDelayedExpansion
)
echo(!LINE!
endlocal
)
endlocal
exit /B
:PROCESS rtn_string ref_string ref_key
setlocal EnableDelayedExpansion
set "COLL="
set "LINE=!%~2!"
set "KEYW=!%~3!"
:LOOP
if defined LINE (
call :LEN LLENGTH LINE
call :LEN KLENGTH KEYW
for /F delims^=^ eol^= %%T in ("!KEYW!") do (
set "SEEK=!LINE:*%%T=!"
)
if defined SEEK (
if not "!SEEK!"=="!LINE!" (
call :LEN SLENGTH SEEK
set /A TLENGTH=LLENGTH-^(SLENGTH+KLENGTH^)
for /F "tokens=1,2" %%M in ("!TLENGTH! !KLENGTH!") do (
set "DONE= !LINE:~,%%M!"
set "TEST=!LINE:~%%M,%%N!"
)
set "CHAR=!DONE:~-1!" & set "CHAR=!CHAR: = !"
set "DONE=!DONE:~1!"
if "!TEST!"=="!KEYW!" (
if "!CHAR!"==" " (
set "DONE=!DONE!!KEYW!"
set "TEST=!SEEK!"
set /A TLENGTH=PLENGTH+3
for /F %%N in ("!TLENGTH!") do (
set "TEST=!SEEK:~,%%N!" & set "SEEK=!SEEK:~%%N!"
)
echo(!TEST! | > nul findstr /R /C:^^^"^^^^^^^=\"!PATTERN!\"^^^ $^^^"
if not ErrorLevel 1 (
for /F "tokens=1,2 delims=." %%S in ("!TEST:~1!") do (
set "HHMMSS=%%~S" & set "MS=%%~T"
set /A "FF=%FORMULA%"
set "FF=0000!FF!" & set "FF=!FF:~-%PADDING%!"
)
set "COLL=!COLL!!DONE!="!HHMMSS!:!FF!"" & set "LINE=!SEEK!"
) else (
set "COLL=!COLL!!DONE!!TEST!" & set "LINE=!SEEK!"
)
) else (
set "COLL=!COLL!!DONE!!TEST!" & set "LINE=!SEEK!"
)
) else (
set "COLL=!COLL!!DONE!!TEST!" & set "LINE=!SEEK!"
)
) else (
set "COLL=!COLL!!LINE!" & set "LINE="
)
) else (
set "COLL=!COLL!!LINE!" & set "LINE="
)
goto :LOOP
)
if defined COLL (
for /F delims^=^ eol^= %%E in ("!COLL!") do (
endlocal
set "%~1=%%E"
setlocal EnableDelayedExpansion
)
) else (
endlocal
set "%~1="
setlocal EnableDelayedExpansion
)
endlocal
exit /B
:LEN rtn_length ref_string
setlocal EnableDelayedExpansion
set "STR=!%~2!"
if not defined STR (set /A LEN=0) else (set /A LEN=1)
for %%L in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if defined STR (
set "INT=!STR:~%%L!"
if not "!INT!"=="" set /A LEN+=%%L & set "STR=!INT!"
)
)
endlocal & set "%~1=%LEN%"
exit /B
假设脚本保存为convert-ms-to-frames.bat
,输入文件名为sample.xml
,输出文件名为return.xml
,使用以下命令行:
convert-ms-to-frames.bat "sample.xml" > "return.xml"
只需去掉> "return.xml"
部分即可在控制台显示输出数据进行测试
假设输入文件sample.xml
包含以下数据...:
<?xml version="1.0"?>
<Events>
<Event In="00:00:20.650" Out="00:00:22.970" />
<Event In="00:00:23.050" Out="00:00:26.300" />
</Events>
...输出文件 return.xml
将包含以下数据:
<?xml version="1.0"?>
<Events>
<Event In="00:00:20:16" Out="00:00:22:24" />
<Event In="00:00:23:01" Out="00:00:26:07" />
</Events>
我一直在处理批处理脚本,以更改一些 .xml
文件 format/type。
快完成了,但我在更改时间码时遇到了问题。
这是一个例子
<Events>
<Event In="00:00:20.650" Out="00:00:22.970"
<Event In="00:00:23.050" Out="00:00:26.300"
此时间格式 hh:mm:ss.ms
应更改为 hh:mm:ss:ff
这意味着,将 Milliseconds
更改为 Frames
。
这样做的公式如下:ms*25/1000
或为简单起见 ms*0.025
输出应该是
<Events>
<Event In="00:00:20:16" Out="00:00:22:24"
<Event In="00:00:23:01" Out="00:00:26:08"
备注
- 0.025 是每毫秒的帧数。
- 帧数必须是整数w/o任何小数部分。
- 我正在使用
GNUWin
和Cygwin
库工具,所以可以使用bc
expr
,tr
,awk
sed
或完成工作的任何解决方案。
使用 bc
$ ms="650"
$ ff=$(echo ${ms} "*0.025/1" |bc)
$ echo $ff
16
当结果小于 10 时,我留给你添加额外的“0”
您可以使用 awk 脚本来完成:
script.awk
{
re = "[0-9]{2}:[0-9]{2}:[0-9]{2}.([0-9]{3})"
while( match( [=10=], re, grps) ) {
frames = sprintf("%02.0f",( grps[1] *0.025 ) )
gsub( "." grps[1], ":" frames)
}
print
}
运行 像这样 awk -f script.awk yourfile
。
它尝试匹配这样的时间戳并将最后一部分(毫秒)捕获到 grps[1]
中。然后计算完成,sprintf
用于格式化帧。
之后gsub
用帧替换毫秒。
PowerShell:
[xml]$xml = gc events.xml
function fix([timespan]$ts) {
"{0}:{1:D2}" -f $ts.ToString("hh\:mm\:ss"),[int]($ts.Milliseconds * 0.025)
}
$xml.SelectNodes('//Event') | %{
$_.In = fix $_.In
$_.Out = fix $_.Out
}
$xml.Save("events_fixed.xml")
如果您更愿意将其作为批处理脚本,您可以将其转换为批处理 + PowerShell 多语言,方法是将其插入顶部并使用 .bat 扩展名保存:
<# : batch portion
@echo off & setlocal
powershell "iex (${%~f0} | out-string)"
goto :EOF
: end Batch / begin PowerShell hybrid code #>
这是一个纯粹的 batch-file 解决方案。此脚本对 cmd
具有特殊含义的所有字符都是安全的。基本上,它会在给定的输入文件中搜索关键字或属性名称 In
和 Out
,然后是 =
和用引号括起来的时间代码 ""
。如果找到,则毫秒部分被拆分,转换为帧并附加到剩余的时间代码,以 :
分隔。每行可以有任意数量的时间码,只要它们的格式与示例数据中给定的格式相同并且给定关键字之一位于前面,所有这些时间码都会被识别和转换。
该脚本支持对转换后的值进行四舍五入——请参阅文件开头变量定义 FORMULA
处的备注 (rem
)(在带有备注 [= 的块中查找) =22=]).
这是代码:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set KEYWORDS="In","Out" & rem // (provide a list of attribute names)
set "PATTERN=[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]"
set /A PLENGTH=12 & rem // (char. length of resolved `PATTERN` string)
set "FORMULA=(MS*25+0)/1000" & rem /* (`+0` means to round frames down;
rem use `+999` instead to round up, or `+500` to round to nearest) */
set /A PADDING=2 & rem // (number of digits of resulting frame value)
set "FILE=%~1"
if not defined FILE exit /B 1
for /F "delims=" %%L in ('findstr /N /R "^" "%FILE%"') do (
set "LINE=%%L"
setlocal EnableDelayedExpansion
set "LINE=!LINE:*:=!"
if defined LINE (
for /F delims^=^ eol^= %%E in ("!LINE!") do (
endlocal
set "LINE=%%E"
setlocal EnableDelayedExpansion
)
) else (
endlocal
set "LINE="
setlocal EnableDelayedExpansion
)
for %%P in (!KEYWORDS!) do (
endlocal
set "KEYWORD=%%~P"
call :PROCESS LINE LINE KEYWORD
setlocal EnableDelayedExpansion
)
echo(!LINE!
endlocal
)
endlocal
exit /B
:PROCESS rtn_string ref_string ref_key
setlocal EnableDelayedExpansion
set "COLL="
set "LINE=!%~2!"
set "KEYW=!%~3!"
:LOOP
if defined LINE (
call :LEN LLENGTH LINE
call :LEN KLENGTH KEYW
for /F delims^=^ eol^= %%T in ("!KEYW!") do (
set "SEEK=!LINE:*%%T=!"
)
if defined SEEK (
if not "!SEEK!"=="!LINE!" (
call :LEN SLENGTH SEEK
set /A TLENGTH=LLENGTH-^(SLENGTH+KLENGTH^)
for /F "tokens=1,2" %%M in ("!TLENGTH! !KLENGTH!") do (
set "DONE= !LINE:~,%%M!"
set "TEST=!LINE:~%%M,%%N!"
)
set "CHAR=!DONE:~-1!" & set "CHAR=!CHAR: = !"
set "DONE=!DONE:~1!"
if "!TEST!"=="!KEYW!" (
if "!CHAR!"==" " (
set "DONE=!DONE!!KEYW!"
set "TEST=!SEEK!"
set /A TLENGTH=PLENGTH+3
for /F %%N in ("!TLENGTH!") do (
set "TEST=!SEEK:~,%%N!" & set "SEEK=!SEEK:~%%N!"
)
echo(!TEST! | > nul findstr /R /C:^^^"^^^^^^^=\"!PATTERN!\"^^^ $^^^"
if not ErrorLevel 1 (
for /F "tokens=1,2 delims=." %%S in ("!TEST:~1!") do (
set "HHMMSS=%%~S" & set "MS=%%~T"
set /A "FF=%FORMULA%"
set "FF=0000!FF!" & set "FF=!FF:~-%PADDING%!"
)
set "COLL=!COLL!!DONE!="!HHMMSS!:!FF!"" & set "LINE=!SEEK!"
) else (
set "COLL=!COLL!!DONE!!TEST!" & set "LINE=!SEEK!"
)
) else (
set "COLL=!COLL!!DONE!!TEST!" & set "LINE=!SEEK!"
)
) else (
set "COLL=!COLL!!DONE!!TEST!" & set "LINE=!SEEK!"
)
) else (
set "COLL=!COLL!!LINE!" & set "LINE="
)
) else (
set "COLL=!COLL!!LINE!" & set "LINE="
)
goto :LOOP
)
if defined COLL (
for /F delims^=^ eol^= %%E in ("!COLL!") do (
endlocal
set "%~1=%%E"
setlocal EnableDelayedExpansion
)
) else (
endlocal
set "%~1="
setlocal EnableDelayedExpansion
)
endlocal
exit /B
:LEN rtn_length ref_string
setlocal EnableDelayedExpansion
set "STR=!%~2!"
if not defined STR (set /A LEN=0) else (set /A LEN=1)
for %%L in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if defined STR (
set "INT=!STR:~%%L!"
if not "!INT!"=="" set /A LEN+=%%L & set "STR=!INT!"
)
)
endlocal & set "%~1=%LEN%"
exit /B
假设脚本保存为convert-ms-to-frames.bat
,输入文件名为sample.xml
,输出文件名为return.xml
,使用以下命令行:
convert-ms-to-frames.bat "sample.xml" > "return.xml"
只需去掉> "return.xml"
部分即可在控制台显示输出数据进行测试
假设输入文件sample.xml
包含以下数据...:
<?xml version="1.0"?> <Events> <Event In="00:00:20.650" Out="00:00:22.970" /> <Event In="00:00:23.050" Out="00:00:26.300" /> </Events>
...输出文件 return.xml
将包含以下数据:
<?xml version="1.0"?> <Events> <Event In="00:00:20:16" Out="00:00:22:24" /> <Event In="00:00:23:01" Out="00:00:26:07" /> </Events>