2

I have a rmvb file path list, and want to convert this files to mp4 files. So I hope to use bash pipeline to handle it. The code is

Convert() {
    ffmpeg -i "$1" -vcodec mpeg4 -sameq -acodec aac -strict experimental "$1.mp4"
}

Convert_loop(){
    while read line; do
       Convert $line
    done
}

cat list.txt | Convert_loop

However, it only handle the first file and the pipe exits.

So, does ffmpeg affect the bash pipe?

Martin Wang
  • 957
  • 14
  • 18
  • I replaced the "smart" quotes in your question with regular ASCII quotes, since it didn't seem they were a source of your problem. – chepner Oct 26 '13 at 14:26
  • There is no `mpe4` codec. [`-sameq` does not mean "same quality"](http://superuser.com/a/478550/110524). Use `-qscale:v` with a value of 2 to 5 instead (lower is higher quality). – llogan Oct 26 '13 at 20:38

3 Answers3

4

Caveat: I've never used ffmpeg, but in working with other questions concerning the program, it appears that, like ssh, ffmpeg reads from standard input without actually using it, so the first call to Convert is consuming the rest of the file list after read gets the first line. Try this

Convert() {
    ffmpeg -i "$1" -vcodec mpe4 -sameq -acodec aac \
           -strict experimental "$1.mp4" < /dev/null
}

This way, ffmpeg will not "hijack" data from standard input intended for read command.

chepner
  • 497,756
  • 71
  • 530
  • 681
3

[...]

for i in `cat list.txt`

Never use this syntax :

for i in $(command); do ...; done # or
for i in `command`; do ...; done

This syntax read the output of a command word by word and not row by row which often creates unexpected problems (like when row contain some spaces and when you want read a row like an item for example).

There always is a smarter solution :

command|while read -r; do ...; done # better general case to read command output in a loop
while read -r; do ...; done <<< "$(command)" # alternative to the previous solution
while read -r; do ...; done < <(command) # another alternative to the previous solution
for i in $DIR/*; do ...; done # instead of "for i in $(ls $DIR); do ...; done
for i in {1..10}; do ...; done # instead of "for i in $(seq 1 10); do ...; done
for (( i=1 ; i<=10 ; i++ )); do ...; done # such that the previous command
while read -r; do ...; done < file # instead of "cat file|while read -r; do ...; done"
while read -r || [[ -n $REPLY ]]; do ...; done < file # Same as before but also deal with files that doesn't have EOF.

# dealing with xargs or find -exec sometimes...
# ...

I wrote a course with more details about this subject and recurring errors, but in French, unfortunately :)

To answer the original question, you could use something like :

Convert() {
    ffmpeg -i “$1” -vcodec mpe4 -sameq -acodec aac -strict experimental “$1.mp4”
}

Convert_loop(){
   while read -r; do
       Convert $REPLY
   done < $1
}

Convert_loop list.txt
Idriss Neumann
  • 3,760
  • 2
  • 23
  • 32
  • 1
    Thanks for your patience, and really good bash programming practice. – Martin Wang Oct 27 '13 at 01:41
  • `for i in $(command)` is perfectly fine if you set `IFS` to only *newline* and *tabulator*, like so: `IFS=$'\n\t'` (which, along with `set -eu`, one should do in every shell script). Also, `read` should **always** be used as `read -r`, or else it will interpret backslashes, and you're even worse off than with a `for` loop. – Sigi May 05 '14 at 05:19
  • @Sigi Most of the time, people don't change the IFS for such kind of loop. Moreover, it's unnecessary given that other syntaxes based on `while` already work fine (see examples of my answer). Why change the behavior of a `for` loop to reproduce the behavior of the `while` loop ? We have to be pragmatic and use the tools that are best suited... – Idriss Neumann May 05 '14 at 07:05
  • @Sigi However, for the use of `-r` I agree, and I've edited my answer ;) – Idriss Neumann May 05 '14 at 07:08
  • @IdrissNeumann It is **much** less error prone to set `IFS` once at the beginning of the script, and then be able to rely on `for` loops, compared to having to use the `while read -r` construction all the time (which is awkward in itself **and** you have to remember `-r`). On the other hand, `for i in $(cmd)` is natural and easy. Also see http://www.dwheeler.com/essays/filenames-in-shell.html for a great article on this topic. – Sigi May 05 '14 at 16:50
  • @Sigi It's a way of seeing things with which I'm not quite agree (I explained why and I don't think that `for` syntax is much easier than `while`). However, when we know what we do, I have nothing to complain about. I write that kind of answer when I see that the `for` loop is incorrectly used (and from my point of view, use incorrectly a `for` loop is worse than forgetting the `-r` option, but everything is relative). – Idriss Neumann May 05 '14 at 19:38
  • @IdrissNeumann You were right in your objection with the `for` loop in this context, and you gave the right explanation for it. However, I think it's wrong to then advise to "never, ever use it", when there is a great solution in form of the `IFS` trick (which is extremely useful if your filenames never contain `\n` or `\t`, i.e. in almost all cases). Also, it should be obvious that `for i in $(cmd)` is easier than `while read -r` (because if it wasn't, the latter would be far more popular with novice script programmers). Oh, and `for` + `IFS=$'\n\t'` is more solid than `while read -r`. – Sigi May 05 '14 at 20:06
1

KISS ! =)

convert() {
    ffmpeg -i "$1" \
           -vcodec mpe4 \
           -sameq -acodec aac \
           -strict experimental "${1%.*}.mp4"
}

while read line; do
    convert "$line"
done < list.txt
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223