Steven Foong
Steven Foong

Reputation: 33

Correcting the variable name in an ffmpeg subtitle script

I have 175 mp4 video files and subtitle files with the extension .ass. Unfortunately, my smart TV is not able to read those subtitles. I plan to burn (hardcode) the subtitles into the video.

I use this command:

ffmpeg -i orgvideo.mp4 -vf subtitles="subtitle.ass" newvideo.mp4     <br>

It works. So I plan to use a bash script to automate the process.

Everything in the script is working but the ffmpeg command line isn't able to retrieve the subtitle variable.

After googling around, I found that my file name has special character and space, that causes my script to fail. If the video file name and the subtitle file is simple, then the script should be no problem.

This is my script:

for f in *.mp4
do
    new="${f%%.mp4} (CHT).mp4"
    subtitle="${f%%.mp4}.chi.ass"
    < /dev/null ffmpeg -i "$f" -vf subtitles="$subtitle" "$new"
done

The ffmpeg line is having problems reading the subtitle file variable. Can anyone help?

Upvotes: 2

Views: 1033

Answers (3)

agc
agc

Reputation: 8406

Try changing:

new="${f%%.mp4} (CHT).mp4"
subtitle="${f%%.mp4}.chi.ass"
< /dev/null ffmpeg -i "$f" -vf subtitles="$subtitle" "$new"

To:

new="${f%%.mp4}.CHT.mp4"
subtitle="${f%%.mp4}.chi.ass"
# make a temp hardlink to evade `ffmpeg`'s 
# onerous quoting requirements.
x=`mktemp -u -p . --suffix=.ass`
ln "$subtitle" $x
< /dev/null ffmpeg -i "$f" -vf subtitles=$x "${new}"
rm $x

How it works. Since ffmpeg makes using subtitles files with escapes difficult, just link the subtitle file to a name $x without escapes, and let ffmpeg chew that over, then remove the link.

A cleaner method would be to use a tool called ffescape to translate the $subtitle variable; unfortunately that tool is currently not packaged outside of ffmpeg's source tree.

Upvotes: 1

Steven Foong
Steven Foong

Reputation: 33

I manage to solve the issue using this script. If you can help to shorten it, it will be perfect.

for f in *.mp4
do
    new="${f%%.mp4} (CHT).mp4"
    subtitle="${f%%.mp4}.chi.ass"
    newsubtitle="${subtitle// /\\ }"
    echo $newsubtitle
    secsubtitle="${newsubtitle//[/\\[}"
    echo $secsubtitle
    thirdtitle="${secsubtitle//]/\\]}"
    echo $thirdtitle
    fourthtitle="${thirdtitle//(/\\(}"
    echo $fourthtitle
    fifthtitle="${fourthtitle//)/\\)}"
    echo $fifthtitle
    ffmpeg -i "$f" -vf subtitles="$fifthtitle" "$new"
done

Thank you

Upvotes: 1

Scheff&#39;s Cat
Scheff&#39;s Cat

Reputation: 20141

May be, the following could help to shed some light on it:

A small test program which reflects how command line arguments are passed:

$ cat >print-arg.c <<EOF
#include <stdio.h>

int main(int argc, char **argv)
{
  for (int i = 0; i < argc; ++i) printf("argv[%d]: '%s'\n", i, argv[i]);
  return 0;
}
EOF

$ gcc -o print-arg print-arg.c

$

This can be used to reflect arguments:

$ ./print-arg Hello\ World "Hello World"
argv[0]: './print-arg'
argv[1]: 'Hello World'
argv[2]: 'Hello World'

$

To be sure that the terminal does not fool us, we can even do this:

$ ./print-arg Hello\ World "Hello World" | hexdump -C
00000000  61 72 67 76 5b 30 5d 3a  20 27 2e 2f 70 72 69 6e  |argv[0]: './prin|
00000010  74 2d 61 72 67 27 0a 61  72 67 76 5b 31 5d 3a 20  |t-arg'.argv[1]: |
00000020  27 48 65 6c 6c 6f 20 57  6f 72 6c 64 27 0a 61 72  |'Hello World'.ar|
00000030  67 76 5b 32 5d 3a 20 27  48 65 6c 6c 6f 20 57 6f  |gv[2]: 'Hello Wo|
00000040  72 6c 64 27 0a                                    |rld'.|
00000045

$

Now, this print-arg tool can be used to debug the bash script test-subtitle.sh:

#/bin/bash
for f in *.mp4
do
  new="${f%%.mp4} (CHT).mp4"
  subtitle="${f%%.mp4}.chi.ass"
  ./print-arg < /dev/null ffmpeg -i "$f" -vf subtitles="$subtitle" "$new"
done

Performing the test:

$ touch file1.mp4 file\ 2.mp4 "file 3.mp4"

$ ./test-subtitle.sh
argv[0]: './print-arg'
argv[1]: 'ffmpeg'
argv[2]: '-i'
argv[3]: 'file 2.mp4'
argv[4]: '-vf'
argv[5]: 'subtitles=file 2.chi.ass'
argv[6]: 'file 2 (CHT).mp4'
argv[0]: './print-arg'
argv[1]: 'ffmpeg'
argv[2]: '-i'
argv[3]: 'file 3.mp4'
argv[4]: '-vf'
argv[5]: 'subtitles=file 3.chi.ass'
argv[6]: 'file 3 (CHT).mp4'
argv[0]: './print-arg'
argv[1]: 'ffmpeg'
argv[2]: '-i'
argv[3]: 'file1.mp4'
argv[4]: '-vf'
argv[5]: 'subtitles=file1.chi.ass'
argv[6]: 'file1 (CHT).mp4'

$

This test should provide clearness how exactly ffmpeg would get the command line arguments.

Update

Please, try this on your command line:

$ < /dev/null ffmpeg -i '[アニメ] FAIRY TAIL 第001話「妖精の尻尾(ようせいのしっぽ)」(1280x720 x264 AAC).mp4' -vf 'subtitles=[アニメ] FAIRY TAIL 第001話「妖精の尻尾(ようせいのしっぽ)」(1280x720 x264 AAC).chi.ass' '[アニメ] FAIRY TAIL 第001話「妖精の尻尾(ようせいのしっぽ)」(1280x720 x264 AAC) (CHT).mp4'

After reading your answer I believe the subtitle has really to be provided backslash escaped. I'm curious whether a quoting would work instead.

So, please, do a last test on your command line:

$ < /dev/null ffmpeg -i '[アニメ] FAIRY TAIL 第001話「妖精の尻尾(ようせいのしっぽ)」(1280x720 x264 AAC).mp4' -vf 'subtitles="[アニメ] FAIRY TAIL 第001話「妖精の尻尾(ようせいのしっぽ)」(1280x720 x264 AAC).chi.ass"' '[アニメ] FAIRY TAIL 第001話「妖精の尻尾(ようせいのしっぽ)」(1280x720 x264 AAC) (CHT).mp4'

According to your comment, backslash escaping seems to be the only way to pass the subtitle file. In fact, I found a shorter (and probably more robust) solution in Stackoverflow: Escape FileNames Using The Same Way Bash Do It.

Applied to your script:

#/bin/bash
for f in *.mp4
do
  new="${f%%.mp4} (CHT).mp4"
  subtitle=$(printf '%q' "${f%%.mp4}.chi.ass")
  < /dev/null ffmpeg -i "$f" -vf subtitles="$subtitle" "$new"
done

I changed the base script test-subtitle.sh respectively:

#/bin/bash
for f in *.mp4
do
  new="${f%%.mp4} (CHT).mp4"
  subtitle=$(printf '%q' "${f%%.mp4}.chi.ass")
  ./print-arg < /dev/null ffmpeg -i "$f" -vf subtitles="$subtitle" "$new"
done

and got:

$ touch file1.mp4 file\ 2.mp4 "file 3.mp4" 'file[4].mp4'

$ ./test-subtitle.sh 
argv[0]: './print-arg'
argv[1]: 'ffmpeg'
argv[2]: '-i'
argv[3]: 'file 2.mp4'
argv[4]: '-vf'
argv[5]: 'subtitles=file\ 2.chi.ass'
argv[6]: 'file 2 (CHT).mp4'
argv[0]: './print-arg'
argv[1]: 'ffmpeg'
argv[2]: '-i'
argv[3]: 'file 3.mp4'
argv[4]: '-vf'
argv[5]: 'subtitles=file\ 3.chi.ass'
argv[6]: 'file 3 (CHT).mp4'
argv[0]: './print-arg'
argv[1]: 'ffmpeg'
argv[2]: '-i'
argv[3]: 'file1.mp4'
argv[4]: '-vf'
argv[5]: 'subtitles=file1.chi.ass'
argv[6]: 'file1 (CHT).mp4'
argv[0]: './print-arg'
argv[1]: 'ffmpeg'
argv[2]: '-i'
argv[3]: 'file[4].mp4'
argv[4]: '-vf'
argv[5]: 'subtitles=file\[4\].chi.ass'
argv[6]: 'file[4] (CHT).mp4'

$

Upvotes: 0

Related Questions