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"

备注

使用 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 #>

这是一个纯粹的 解决方案。此脚本对 cmd 具有特殊含义的所有字符都是安全的。基本上,它会在给定的输入文件中搜索关键字或属性名称 InOut,然后是 = 和用引号括起来的时间代码 ""。如果找到,则毫秒部分被拆分,转换为帧并附加到剩余的时间代码,以 : 分隔。每行可以有任意数量的时间码,只要它们的格式与示例数据中给定的格式相同并且给定关键字之一位于前面,所有这些时间码都会被识别和转换。

该脚本支持对转换后的值进行四舍五入——请参阅文件开头变量定义 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>