Reputation: 33
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
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
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
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