2

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?

boardrider
  • 5,882
  • 7
  • 49
  • 86
  • Could you, please, [**edit**](https://stackoverflow.com/posts/44644890/edit) your question and add the intended output (a small example)? – Scheff's Cat Jun 20 '17 at 07:48
  • @Scheff : I explain my question more detail. Hopefully will be able to give you a better picture. – Steven Foong Jun 20 '17 at 10:13
  • I just read in the [ffmpeg](https://ffmpeg.org/ffmpeg.html#Subtitle-options) doc. that the "format is normally auto detected for input files". Auto detect sounds for me like evaluating the magic code. But what if the extension is evaluated and ".ass" is recognized but ".chi.ass" not? Probably not the reason, but easy to exclude (modifying your 1st test command respectively.) – Scheff's Cat Jun 20 '17 at 10:17
  • May be, you could edit the title of the question. For me, it sounds like an bash issue but as I understood now it's rather an ffmpeg issue. (Hence my misleading answer.) – Scheff's Cat Jun 20 '17 at 10:20
  • @Scheff: thank you for your suggestion. I believe my issue is my script not correct. It has nothing to do with ffmpeg as well because chi.ass extension subtitle is working fine, if I type it manually at the console. I have problem only when it is an variable in the script and I believe it is double quote is needed at the subtitle parameter. – Steven Foong Jun 20 '17 at 10:53
  • If this would be the case then it would be easy: just use `subtitles="\"$subtitle\""`. What I do not understand: This issue should be regardless of whether it is typed in directly on command line or in script... – Scheff's Cat Jun 20 '17 at 10:56
  • 1
    That is because the file name contain special character and space. Then ffmpeg command , the subtitles parameter must have double quote. I give you an example of the command that I use in console. `ffmpeg -i \[アニメ\]\ FAIRY\ TAIL\ 第002話「火竜と猿と牛(かりゅうとうしとさる)」\(1280x720\ x264\ AAC\).mp4 -vf subtitles="\[アニメ\]\ FAIRY\ TAIL\ 第002話「火竜と猿と牛(かりゅうとうしとさる)」\(1280x720\ x264\ AAC\).chi.ass" 002.mp4` I try your suggestion, still not workable. – Steven Foong Jun 20 '17 at 11:01
  • Space should not be a problem for the bash, as you "safed" everything in quotes. Non-ASCII characters _could_ be a problem. You may have a look into `$LANG`. Actually, linux should use UTF-8 for filenames to prevent this issue. About ffmpeg I don't know... – Scheff's Cat Jun 20 '17 at 11:02
  • Found this: [What charset encoding is used for filenames and paths on Linux?](https://unix.stackexchange.com/questions/2089/what-charset-encoding-is-used-for-filenames-and-paths-on-linux) by googling "bash file name encoding" – Scheff's Cat Jun 20 '17 at 11:16
  • I just saw: `subtitles="\[アニメ\]\ ..."`. This is quoting _and_ backslash escaping mixed. This will probably not work. Is this a result of `subtitles="\"$subtitle\""`? – Scheff's Cat Jun 20 '17 at 11:19
  • I think subtitles="\"$subtitle\"" is not working , and it is where I stuck. I tried to add in the backslash escaping , but I dont know in bash shell script , how to do that. – Steven Foong Jun 20 '17 at 11:34
  • Where does the escaping come from? I just tried this: `touch "Hello World.test" ; for i in *.test; do echo $i ; done` and got: `Hello World.test`. Thus, no escaping. – Scheff's Cat Jun 20 '17 at 11:38
  • I read some where, "$f" will handle the backslash escaping automatically. `ffmpeg -i "$f"` working fine ($f also a variable with backslash escaping). `ffmpeg -i "$f" -vf subtitles="$subtitle" ` not working , because subtitle parameter need double quote. But `subtitles="\"$subtitle\""` somehow don't quote correctly. – Steven Foong Jun 20 '17 at 11:45
  • Well, providing command line parameters on shell which include spaces: these spaces have to be saved _either_ by backslash escaping _or_ by quotes. The only exception is "double saving" (i.e. 1st replacement needs quoting, 2nd replacement needs backslash escaping e.g. when using sed in a bash script). This shouldn't be the case here. IMHO, in your 1st working sample: `ffmpeg -i orgvideo.mp4 -vf subtitles="subtitle.ass" newvideo.mp4`, the bash should remove the quotes before passing the command line args. to ffmpeg. I'm a little bit confused now... – Scheff's Cat Jun 20 '17 at 12:04
  • Btw. `アニメ` looks for me like Japanese. (Not, that I could read this...) – Scheff's Cat Jun 20 '17 at 12:35
  • 1
    Now, (knowing what the problem was) I googled "ffmpeg subtitles file escaping" and found: [Escaping and quoting not working with subtitles video filter](https://trac.ffmpeg.org/ticket/3334) which is referring to [FFmpeg Doc.: 2.1 Quoting and escaping](https://ffmpeg.org/ffmpeg-utils.html#Quoting-and-escaping). – Scheff's Cat Jun 20 '17 at 15:27
  • @Scheff, I read the link you gave, good find, that is rather nasty. Upvoted this Q, since it focuses on a bug that may affect many. – agc Jun 20 '17 at 19:31

3 Answers3

1

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.

agc
  • 7,973
  • 2
  • 29
  • 50
  • The change in 3rd line (`${subtitle}") seems (for me) no significant. "The parameter name or symbol to be expanded may be enclosed in braces, which are optional but serve to protect the variable to be expanded from characters immediately following it which could be interpreted as part of the name." – Scheff's Cat Jun 20 '17 at 13:16
  • @Scheff, Very probably it's not significant. But it's harmless and I've assumed the worst case scenario, where there might be some forgotten `${subtitl}` (no "*e*" at the end) left around to make things worse. – agc Jun 20 '17 at 13:22
  • @agc: Thank you for your suggestion. But still no luck. ffmpeg still not able to capture the subtitle file location. – Steven Foong Jun 20 '17 at 13:59
  • @StevenFoong, please specify any errors printed by `ffmpeg`. – agc Jun 20 '17 at 14:38
  • `[Parsed_subtitles_0 @ 0x564083549800] No filename provided!` – Steven Foong Jun 20 '17 at 14:53
  • I had a noob way to solve the problem, which I added as answer, can you help to shorten it? – Steven Foong Jun 20 '17 at 14:54
  • The parameter retrieve, but ffmpeg not able to open the file. Error: `[Parsed_subtitles_0 @ 0x563a02ede780] Unable to open /tmp/tmp.RqC5t48hGL.ass` – Steven Foong Jun 21 '17 at 05:20
  • Maybe `ffmpeg` doesn't like symlinks. Code changed to use hard links in current directory, please give it a try. – agc Jun 21 '17 at 07:38
1

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

agc
  • 7,973
  • 2
  • 29
  • 50
  • Please, see my answer. I added a note to the end. – Scheff's Cat Jun 20 '17 at 14:57
  • I've added a kludge to my answer, in light of `ffmpeg`'s surprising syntax for subtitle files, as pointed out by @Scheff. Please check if the kludge works or not. – agc Jun 20 '17 at 19:50
0

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'

$
Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
  • Hope this might help to troubleshoot the issue. This is one of the output. `./test-subtitle.sh argv[0]: './print-arg' argv[1]: 'ffmpeg' argv[2]: '-i' argv[3]: '[アニメ] FAIRY TAIL 第001話「妖精の尻尾(ようせいのしっぽ)」(1280x720 x264 AAC).mp4' argv[4]: '-vf' argv[5]: 'subtitles=[アニメ] FAIRY TAIL 第001話「妖精の尻尾(ようせいのしっぽ)」(1280x720 x264 AAC).chi.ass' argv[6]: '[アニメ] FAIRY TAIL 第001話「妖精の尻尾(ようせいのしっぽ)」(1280x720 x264 AAC) (CHT).mp4' ` – Steven Foong Jun 20 '17 at 13:57
  • Actually, this does not look that bad. Did you try to convert file `[アニメ] FAIRY TAIL 第001話「妖精の尻尾(ようせいのしっぽ)」(1280x720 x264 AAC).mp4` "by hand" on command line (providing the exact parameters as it should be done in the script). If you copy/paste the output of print-arg to the command line it is probably worth to copy them with the surrounding single-quotes (`'`) as these disable rather any replacement by bash. – Scheff's Cat Jun 20 '17 at 14:04
  • First test result: `./print-arg ffmpeg -i \[アニメ\]\ FAIRY\ TAIL\ 第002話「火竜と猿と牛(かり ゅうとうしとさる)」\(1280x720\ x264\ AAC\).mp4 -vf subtitles="\[アニメ\]\ FAIRY\ TAIL\ 第002話「火竜と 猿と牛(かりゅうとうしとさる)」\(1280x720\ x264\ AAC\).chi.ass" 002.mp4 argv[0]: './print-arg' argv[1]: 'ffmpeg' argv[2]: '-i' argv[3]: '[アニメ] FAIRY TAIL 第002話「火竜と猿と牛(かりゅうとうしとさる)」(1280x720 x264 AAC).mp4' argv[4]: '-vf' argv[5]: 'subtitles=\[アニメ\]\ FAIRY\ TAIL\ 第002話「火竜と猿と牛(かりゅうとうしとさる)」\(1280x720\ x264\ AAC\).chi.ass' argv[6]: '002.mp4'` – Steven Foong Jun 20 '17 at 14:12
  • Second test result: `./print-arg ffmpeg -i [アニメ] FAIRY TAIL 第002話「火竜と猿と牛(かりゅうとうしとさる)」(1280x720 x264 AAC).mp4 -vf subtitles="[アニメ] FAIRY TAIL 第002話「火竜と猿と牛(かりゅうとうしとさる)」(1280x720 x264 AAC).chi.ass" 002.mp4 bash: syntax error near unexpected token `(' ` – Steven Foong Jun 20 '17 at 14:12
  • In the second test, you didn't save the file names with neither single nor double quotes (as I recommended before). With "convert file" I meant to call ffmpeg (without preceding print-arg). I believe that the arguments are passed in the bash script as expected (according to the output you posted in the first comment). Therefore, I believe that really ffmpeg is somehow not able to resolve the file name after `subtitles=`. – Scheff's Cat Jun 20 '17 at 14:18
  • ./print-arg ffmpeg -i '[アニメ] FAIRY TAIL 第002話「火竜と猿と牛(かりゅう とうしとさる)」(1280x720 x264 AAC).mp4' -vf subtitles="[アニメ] FAIRY TAIL 第002話「火竜と猿と牛(かり ゅうとうしとさる)」(1280x720 x264 AAC).chi.ass" 002.mp4 argv[0]: './print-arg' argv[1]: 'ffmpeg' argv[2]: '-i' argv[3]: '[アニメ] FAIRY TAIL 第002話「火竜と猿と牛(かりゅうとうしとさる)」(1280x720 x264 AAC).mp4' argv[4]: '-vf' argv[5]: 'subtitles=[アニメ] FAIRY TAIL 第002話「火竜と猿と牛(かりゅうとうしとさる)」(1280x720 x264 AAC).chi.ass' argv[6]: '002.mp4' – Steven Foong Jun 20 '17 at 14:24
  • Please, see my answer. I added the statement to try at the end. – Scheff's Cat Jun 20 '17 at 14:25
  • Nope. I try your updated command still not working. I manage to figure out the script which is working, if you can help to shorten , then it will be perfect. – Steven Foong Jun 20 '17 at 14:50
  • Here the result [Parsed_subtitles_0 @ 0x5635143c4800] Shaper: FriBidi 0.19.7 (SIMPLE) HarfBuzz-ng 1.2.7 (COMPLEX) [Parsed_subtitles_0 @ 0x5635143c4800] Unable to open " [AVFilterGraph @ 0x5635143c45e0] Error initializing filter 'subtitles' with args '"' – Steven Foong Jun 20 '17 at 15:02