0

I made a .bat script to watermark videos with the logo slowly sliding across the video from the right to the left. Here is my code:

set ffmpeg="C:\ffmpeg\ffmpeg.exe"
set ffprobe="C:\ffmpeg\ffprobe.exe"

@echo off
setlocal enableextensions enabledelayedexpansion
for %%A in ("H:\4 - Watermark Process\Before\*.mp4") do (
    echo "________________________________________________________________________________________________________________________"
    echo _ Filename _: %%A
    rem Find videoDuration
    for /F "delims=" %%G in ('ffprobe.exe -v error -show_entries format^=duration -of default^=noprint_wrappers^=1:nokey^=1 "%%A" 2^>^&1') do (
        set /A videoDuration=%%G/1
        echo _ Video Duration _: !videoDuration!
    )

    rem Find videoWidth
    for /F "delims=" %%I in ('ffprobe -v error -show_entries stream^=width -of csv^=p^=0:s^=x "%%A"') do (
        set videoWidth=%%I
        echo _ Video Width _: !videoWidth!
    )

    rem Find videoHeight
    for /F "delims=" %%J in ('ffprobe -v error -show_entries stream^=height -of csv^=s^=x:p^=0 "%%A"') do (
        set videoHeight=%%J
        echo _ Video Height _: !videoHeight!
    )

    rem Scale logo watermark according to video Width and Height
    if !videoHeight! LSS !videoWidth! (
        echo "_ videoHeight LESS THAN videoWidth _"
        set /A scaleWidth = !videoWidth!/10
        ffmpeg -i logo.png -y -v verbose -vf scale=!scaleWidth!:-1 scaled.png
    ) else (
        echo "_ videoHeight GREATER THAN videoWidth _"
        set /A scaleWidth = !videoWidth!/4
        ffmpeg -i logo.png -y -v verbose -vf scale=!scaleWidth!:-1 scaled.png
    )

    rem Place logo watermark based on video Duration
    if !videoDuration! LSS 10 (
        echo "_ videoDuration LESS THAN 10 seconds _"
        echo "________________________________________________________________________________________________________________________"
        ffmpeg -i "%%A" -i scaled.png -filter_complex "overlay=10:main_h-overlay_h" "H:\4 - Watermark Process\After\%%~nA.mp4"
        echo "______________________________________________LESS THAN 10SEC CONVERSION COMPLETE__________________________________________________________________________"
    )
    if !videoDuration! GEQ 10 (
        set /A "videoDurationOneThird=(videoDuration*2)/3"
        echo _ videoDuration One Third _: !videoDurationOneThird!
        echo "________________________________________________________________________________________________________________________"
        ffmpeg -i "%%A" -i scaled.png -filter_complex "[0:v][1:v] overlay='if(gte(t,!videoDurationOneThird!), (main_w-(w+(t-!videoDurationOneThird!)*20)), NAN)':(main_h-overlay_h)/2:enable=between'(t,!videoDurationOneThird!,!videoDuration!)'" "H:\4 - Watermark Process\After\%%~nA.mp4"
    )
)

pause 10

Problem is I get a an error on videos with exclamation points in them saying: No such file or directory

I read that enabling delayed expansion removes the exclamation point but I need it for the script to work correctly. How can I fix this?

The failed solution I've tried is below:

set ffmpeg="C:\ffmpeg\ffmpeg.exe"
set ffprobe="C:\ffmpeg\ffprobe.exe"

@echo off
setlocal enableextensions disabledelayedexpansion
for %%A in ("H:\4 - Watermark Process\BeforeWide\*.mp4") do (
    set FILE=%%~A
    set NAME=%%~nA
    echo "________________________________________________________________________________________________________________________"
    echo _ Filename _: !FILE!
    setlocal enabledelayedexpansion
    rem Find videoDuration
    for /F "delims=" %%G in ('ffprobe.exe -v error -show_entries format^=duration -of default^=noprint_wrappers^=1:nokey^=1 !FILE! 2^>^&1') do (
        set /A videoDuration=%%G/1
        echo _ Video Duration _: !videoDuration!
    )

    rem Find videoWidth
    for /F "delims=" %%I in ('ffprobe -v error -show_entries stream^=width -of csv^=p^=0:s^=x !FILE!') do (
        set videoWidth=%%I
        echo _ Video Width _: !videoWidth!
    )

    rem Find videoHeight
    for /F "delims=" %%J in ('ffprobe -v error -show_entries stream^=height -of csv^=s^=x:p^=0 !FILE!') do (
        set videoHeight=%%J
        echo _ Video Height _: !videoHeight!
    )

    rem Scale logo watermark according to video Width and Height
    if !videoHeight! LSS !videoWidth! (
        echo "_ videoHeight LESS THAN videoWidth _"
        set /A scaleWidth = !videoWidth!/10
        ffmpeg -i logo.png -y -v verbose -vf scale=!scaleWidth!:-1 scaled.png
    ) else (
        echo "_ videoHeight GREATER THAN videoWidth _"
        set /A scaleWidth = !videoWidth!/4
        ffmpeg -i logo.png -y -v verbose -vf scale=!scaleWidth!:-1 scaled.png
    )

    rem Place logo watermark based on video Duration
    if !videoDuration! LSS 10 (
        echo "_ videoDuration LESS THAN 10 seconds _"
        echo "________________________________________________________________________________________________________________________"
        ffmpeg -i !FILE! -i scaled.png -filter_complex "overlay=10:main_h-overlay_h" "H:\4 - Watermark Process\After\!NAME!.mp4"
        echo "______________________________________________LESS THAN 10SEC CONVERSION COMPLETE__________________________________________________________________________"
    )
    if !videoDuration! GEQ 10 (
        set /A "videoDurationOneThird=(videoDuration*2)/3"
        echo _ videoDuration One Third _: !videoDurationOneThird!
        echo "________________________________________________________________________________________________________________________"
        ffmpeg -i !FILE! -i scaled.png -filter_complex "[0:v][1:v] overlay='if(gte(t,!videoDurationOneThird!), (main_w-(w+(t-!videoDurationOneThird!)*20)), NAN)':(main_h-overlay_h)/2:enable=between'(t,!videoDurationOneThird!,!videoDuration!)'" "H:\4 - Watermark Process\After\!NAME!.mp4"
    )
    endlocal
)

pause 10
Mofi
  • 46,139
  • 17
  • 80
  • 143
  • 1
    The problem is that you use the `for` meta-variable `%%A` while delayed expansion is enabled, so any `!` are consumed by delayed expansion; to solve that, you need to disable delayed expansion before the loop `for %%A in …` comes, and then in the loop body, first set a few variables (like `set "FILE=%%~A" & set "NAME=%%~nA"`), then enable delayed expansion, then use those variables (so use `!FILE!` instead of `%%A` and `!NAME!` instead of `%%~nA`), and finally, put `endlocal` as the last command in the loop body… – aschipfl Oct 08 '20 at 17:11
  • The command line `set /A videoDuration=%%G/1` is really interesting. It results in an error message output by __SET__ to handle __STDERR__ on a floating point value like `20.43` is assigned to the loop variable `G` and environment variable `videoDuration` is defined next with value `20`. This command line with the arithmetic expression would not be needed at all on using `for /F "delims=. " %%G in ...`. – Mofi Oct 08 '20 at 17:22
  • aschipfl. I've tried your solution but it is giving errors. Can you check my code? I've edited the post – Tevin Mosley Oct 08 '20 at 17:30
  • What do you expect as difference between `pause 10` and just `pause`? The command `pause` does not support an argument other than `/?` which results in printing the small help for this simple command. `timeout` could be better for your task. Run `timeout /?` for help on this command available by default since Windows Vista which is an external command, i.e. is an executable with full qualified file name `%SystemRoot%\System32\timeout.exe`. – Mofi Oct 08 '20 at 17:41
  • 1
    You've effectively used `if !videoHeight! LSS !videoWidth! (echo "_ videoHeight LESS THAN videoWidth _") else echo "_ videoHeight GREATER THAN videoWidth _"` What happens if they're both the same? You have not catered for that scenario, so it will be treated as `GREATER THAN`. If that's what you intended change the comparator/order as required. – Compo Oct 08 '20 at 17:44
  • Compo. Yes I want it to default to greater than because the watermark would be the same size. – Tevin Mosley Oct 08 '20 at 17:50
  • Hint: Do not use `set /A scaleWidth = !videoWidth!/10`, but use instead `set /A scaleWidth=videoWidth/10` which is better and always working even on `videoWidth` not defined at all. Environment variables can be specified inside an arithmetic expression just by their names. There is no need for `%VariableName%` or `!VariableName!` inside the arithmetic expression which is the string after `set /A`. That is explained by help of command __SET__ output on running `set /?` in a command prompt window. – Mofi Oct 08 '20 at 17:51
  • I'll also guess that you designed this to work in Windows 10. I say that because you've `echo`ed `120` underscores, _(cmd.exe's default window character width)_. However you've also included enclosing doublequotes, which means that those lines, which aren't necessary anyhow, will wrap and look bad. You should therefore remove at least two of those in each instance. – Compo Oct 08 '20 at 17:52
  • Mofi. I've found the problem. It is the fact that I need delayedexpansion enabled and disabled for certain parts of the code within the same loop. Aschipfl had the right idea but it still does not work with his solution. – Tevin Mosley Oct 08 '20 at 17:53
  • Two more hints: `echo _ Filename _: !FILE!` in second code does not work as delayed expansion not enabled on `cmd.exe` processing this command line. You have to use here `echo _ Filename _: %%~A`. Do you know that command `echo` outputs also all `"`? Do you really want to output all those `"` at beginning and end of several`echo` argument strings? BTW: I recommend to name one video file `;Development & Test !File!.mp4` and see if the final batch file can process also this video file with very unusual name correct. – Mofi Oct 08 '20 at 17:58
  • Wait, I see a definite syntax error. In all inner __FOR__ loops you have to use `"!FILE!"` instead of just `!FILE!` and you better use at beginning also `set "FILE=%%~A"` and `set "NAME=%%~nA"`. For the reasons see the __issues__ in [this long answer](https://stackoverflow.com/a/60686543/3074564) and take into account that for each command line enclosed in `'` of a `for /F` command line one more command process is started with `%ComSpec% /c` in background with the completely expanded and processed command line appended as additional arguments to run by background `cmd`. – Mofi Oct 08 '20 at 18:03
  • Mofi. That was it! It works now. Thank you and Compo! – Tevin Mosley Oct 08 '20 at 18:09

1 Answers1

0

Here's an untested example for you, based upon the information you provided:

@Echo Off
SetLocal EnableExtensions DisableDelayedExpansion

Set "Source=H:\4 - Watermark Process"
Set "Before=BeforeWide"
Set "After=After"
Set "Glob=*.mp4"
Set "ffmpeg=C:\ffmpeg\ffmpeg.exe"
Set "ffprobe=C:\ffmpeg\ffprobe.exe"
Set "logo=%CD%\logo.png"
Set "scaled=%CD%\scaled.png"

For %%A In ("%Source%\%Before%\%Glob%") Do (
    Set "FullName=%%A"
    Set "NameOnly=%%~nxA"
    If Not Exist "%Source%\%After%\" MD "%Source%\%After%" || GoTo :EOF  2> NUL
    SetLocal EnableDelayedExpansion
    Echo ________________________________________________________________________________________________________________________
    Echo _ Filename _: !FullName!
    Set /A "videoDuration=videoWidth=videoHeight=0"
    Rem Find videoDuration
    For /F Delims^=^ EOL^= %%G In (
        '^""%ffprobe%" -v error -show_entries format^=duration -of default^=noprint_wrappers^=1:nokey^=1 "!FullName!" 2^>^&1^"'
    ) Do Set /A "videoDuration=%%G / 1" 2> NUL
    Echo _ Video Duration _: !videoDuration!
    Rem Find videoWidth
    For /F Delims^=^ EOL^= %%I In (
        '^""%ffprobe%" -v error -show_entries stream^=width -of csv^=p^=0:s^=x "!FullName!"^"'
    ) Do Set /A "videoWidth=%%I" 2> NUL 
    Echo _ Video Width _: !videoWidth!
    Rem Find videoHeight
    For /F Delims^=^ EOL^= %%J In (
        '^""%ffprobe%" -v error -show_entries stream^=height -of csv^=s^=x:p^=0 "!FullName!"^"'
    ) Do Set /A "videoHeight=%%J" 2> NUL
    Echo _ Video Height _: !videoHeight!
    Rem Scale logo watermark according to video Width and Height
    If !videoHeight! Lss !videoWidth! (
        Echo _ videoHeight LESS THAN videoWidth _
        Set /A "scaleWidth=videoWidth / 10"
    ) Else (
        Echo _ videoHeight GREATER THAN videoWidth _
        Set /A "scaleWidth=videoWidth / 4"
    )
    "%ffmpeg%" -i "%logo%" -y -v verbose -vf scale=!scaleWidth!:-1 "%scaled%"
    Rem Place logo watermark based on video Duration
    If !videoDuration! Lss 10 (
        Echo _ videoDuration LESS THAN 10 seconds _
        Echo ________________________________________________________________________________________________________________________
        "%ffmpeg%" -i "!FullName!" -i "%scaled%" -filter_complex "overlay=10:main_h-overlay_h" "%Source%\%After%\!NameOnly!"
        Echo __________________________________________LESS THAN 10SEC CONVERSION COMPLETE___________________________________________
    )
    If !videoDuration! GEq 10 (
        Set /A "videoDurationOneThird = (videoDuration * 2) / 3"
        Echo _ videoDuration One Third _: !videoDurationOneThird!
        Echo ________________________________________________________________________________________________________________________
        "%ffmpeg%" -i "!FullName!" -i "%scaled%" -filter_complex "[0:v][1:v] overlay='if(gte(t,!videoDurationOneThird!), (main_w-(w+(t-!videoDurationOneThird!)*20)), NAN)':(main_h-overlay_h)/2:enable=between'(t,!videoDurationOneThird!,!videoDuration!)'" "%Source%\%After%\!NameOnly!"
    )
    EndLocal
)
%SystemRoot%\System32\timeout.exe /T 10 /NoBreak 1> NUL

You should only need to modify the variable values, in the isolated Set group. Please note that I've used %CD% for the .png files, as you hadn't provided a location. However, as you've not set the current directory either, you may actually have those files in the same directory as the running script. If that is the case you should really change both from %CD% to %~dp0.

Compo
  • 36,585
  • 5
  • 27
  • 39