43

I have a bash script looping through the results of a find and performing an ffmpeg encoding of some FLV files. Whilst the script is running the ffmpeg output seems to be interupted and is outputting some strange looking errors like the one below. I've no idea what is going on here. Can anyone point me in the right direction?

It's as though the loop is still running when it shouldn't be and interupting the ffmpeg process.

The specific error is:

frame=   68 fps= 67 q=28.0 00000000000000000000000000001000size=      22kB time=00:00:00.50 bitrate= 363.2kbits/s dup=1 drop=0    
Enter command: <target> <time> <command>[ <argument>]
Parse error, at least 3 arguments were expected, only 1 given in string 'om/pt_br/nx/R3T4N2_HD3D_demoCheckedOut.flv'

Some more details from the ffmpeg output:

[buffer @ 0xa30e1e0] w:800 h:600 pixfmt:yuv420p tb:1/1000000 sar:0/1 sws_param:flags=2
[libx264 @ 0xa333240] using cpu capabilities: MMX2 SSE2Fast SSSE3 FastShuffle SSE4.1 Cache64
[libx264 @ 0xa333240] profile High, level 3.1
[libx264 @ 0xa333240] 264 - core 122 r2184 5c85e0a - H.264/MPEG-4 AVC codec - Copyleft 2003-2012 - http://www.videolan.org/x264.html - options: cabac=1 ref=5 deblock=1:0:0 analyse=0x3:0x113 me=umh subme=8 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=2 b_bias=0 direct=3 weightb=1 open_gop=0 weightp=2 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=50 rc=cbr mbtree=1 bitrate=500 ratetol=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 vbv_maxrate=500 vbv_bufsize=1000 nal_hrd=none ip_ratio=1.40 aq=1:1.00
Output #0, mp4, to './mp4s/pt_br/teamcenter/tc8_interactive/videos/8_SRM_EN.mp4':
  Metadata:
    audiodelay      : 0
    canSeekToEnd    : true
    encoder         : Lavf54.3.100
    Stream #0:0: Video: h264 (![0][0][0] / 0x0021), yuv420p, 800x600, q=-1--1, 500 kb/s, 30k tbn, 29.97 tbc
    Stream #0:1: Audio: aac (@[0][0][0] / 0x0040), 44100 Hz, mono, s16, 128 kb/s
Stream mapping:
  Stream #0:1 -> #0:0 (vp6f -> libx264)
  Stream #0:0 -> #0:1 (mp3 -> libfaac)
Press [q] to stop, [?] for help
error parsing debug value0 00000000000000000000000000000000size=      13kB time=00:00:00.-3 bitrate=-3165.5kbits/s dup=1 drop=0    
debug=0
frame=   68 fps= 67 q=28.0 00000000000000000000000000001000size=      22kB time=00:00:00.50 bitrate= 363.2kbits/s dup=1 drop=0    
Enter command: <target> <time> <command>[ <argument>]
Parse error, at least 3 arguments were expected, only 1 given in string 'om/pt_br/nx/R3T4N2_HD3D_demoCheckedOut.flv'

The script is as follows

#!/bin/bash
LOGFILE=encodemp4ize.log
echo '' > $LOGFILE
STARTTIME=date
echo "Started at `$STARTTIME`" >> $LOGFILE
rsync -avz flvs/ mp4s/ --exclude '*.flv'
#find flvs/ -name "*.flv" > flv-files
# The loop
find flvs/ -name "*.flv" | while read f
do
FILENAME=`echo $f | sed 's#flvs/##'`
MP4FILENAME=`echo $FILENAME | sed 's#.flv#.mp4#'`
ffmpeg -i "$f" -vcodec libx264 -vprofile high -preset slow -b:v 500k -maxrate 500k -bufsize 1000k -threads 0 -acodec libfaac -ab 128k "./mp4s/$MP4FILENAME"
echo "$f MP4 done" >> $LOGFILE
done

4 Answers4

84

Your question is actually Bash FAQ #89: just add </dev/null to prevent ffmpeg from reading its standard input.


I've taken the liberty of fixing up your script for you because it contains a lot of potential errors. A few of the important points:

  • Filenames are tricky to handle, because most filesystems allow them to contain all sorts of unprintable characters normal people would see as garbage. Making simplifying assumptions like "file names contain only 'normal' characters" tends to result fragile shell scripts that appear to work on "normal" file names and then break the day they run into a particularly nasty file name that doesn't follow the script's assumptions. On the other hand, correctly handling file names can be such a bother that you may find it not worth the effort if the chance of encountering a weird file name is expected to be near zero (i.e. you only use the script on your own files and you give your own files "simple" names). Sometimes it is possible to avoid this decision altogether by not parsing file names at all. Fortunately, that is possible with find(1)'s -exec option. Just put {} in the argument to -exec and you don't have to worry about parsing find output.

  • Using sed or other external processes to do simple string operations like stripping extensions and prefixes is inefficient. Instead, use parameter expansions which are part of the shell (no external process means it will be faster). Some helpful articles on the subject are listed below:

  • Use $( ), and don't use `` anymore: Bash FAQ 82.

  • Avoid using UPPERCASE variable names. That namespace is generally reserved by the shell for special purposes (like PATH), so using it for your own variables is a bad idea.

And now, without further ado, here's a cleaned up script for you:

#!/bin/sh

logfile=encodemp4ize.log
echo "Started at $(date)." > "$logfile"
rsync -avz --exclude '*.flv' flvs/ mp4s/

find flvs/ -type f -name '*.flv' -exec sh -c '
for flvsfile; do
    file=${flvsfile#flvs/}
    < /dev/null ffmpeg -i "$flvsfile" -vcodec libx264 -vprofile high \
        -preset slow -b:v 500k -maxrate 500k -bufsize 1000k \
        -threads 0 -acodec libfaac -ab 128k \
        "mp4s/${file%flv}"mp4
    printf %s\\n "$flvsfile MP4 done." >> "$logfile"
done
' _ {} +

Note: I used POSIX sh because you didn't use or need any bash-specific features in your original.

jw013
  • 51,212
  • 4
    It's a brilliant answer! Thank you for the effort writing up corrected script. Just wondering is there a similar to Greg's Wiki guide to zsh? Thanks! – Art Oct 24 '12 at 05:05
  • 1
    @Art Sorry, I don't know very much about zsh. Maybe some of the zsh people on the site would know. – jw013 Oct 24 '12 at 13:09
  • The problem is that I need to check whether ffmpeg is producing an error to later down the script decide whether or not to erase the previous version of the converted file. I am converting mkv to mp4 for a Plex Media Server. I have stuttering with big mkv files, so I decided to convert all mkv to mp4. Another problem is that I need to check subtitles stream conversion failure for picture based formats, in which case I use another proccess to extract the subs. So, how do I run ffmpeg, get its output, and do not run into this issue? – dacabdi Nov 24 '16 at 15:52
  • 3
    Perhaps since @jw013 excellent answer was written, ffmpeg has added the -nostdin option which achieves the same result as redirecting from /dev/null and, as the ffmpeg man page notes, doesn't require a shell. Bash FAQ 089 also now uses this option. – MikeV Mar 21 '20 at 22:22
  • this answer (and the -nostdin comment) just saved me hours of headache. No sure why ffmpeg suddenly decides to read from stdin when called in a loop though. It is also not a bash issue, since it happened on BusyBox's sh. It also doesn't seem to be related to the file name nor its contents, since individually processing the file where ffmpeg decided to stop does not cause any issue. But the moment I loop it 1000+ times (1166 to be exact) – user1593842 Oct 08 '23 at 14:26
  • i thought i was taking crazy pills, i didn't know ffmpeg does that, thank you so much – ufk Nov 25 '23 at 08:02
24

I've found the solution. The bash script seems to produce input (Namely the 'c' key) which interferes with the ffmpeg process.

Adding < /dev/null to the ffmpeg command line, like so:

ffmpeg -i "./$f" -vcodec libx264 -vprofile high -preset slow -b:v 500k -maxrate 500k -bufsize 1000k -threads 0 -acodec libfaac -ab 128k "./mp4s/$MP4FILENAME" < /dev/null

fixes the issue.

13

As an alternative solution to ffmpeg [...] < /dev/null, you can use:

ffmpeg -nostdin [...]

Details from the ffmpeg documentation:

To explicitly disable interaction you need to specify -nostdin.

Disabling interaction on standard input is useful, for example, if ffmpeg is in the background process group. Roughly the same result can be achieved with ffmpeg ... < /dev/null but it requires a shell.

Pamphile
  • 231
-1

In the while loop, I have faced similar problems. You can use for loop, and in for loop, you can avoid taking the list of audios and running it in the loop using the find command.

I have used find and sed to take the list of audios in my directory because I have seen situations where using wildcard characters throws "argument list too long" error.

Now if we just do the find then it will give the absolute path of the files which will cause the ffmpeg to fail, so we have removed the absolute path using sed.

You can use a command like this (have used it a lot of times and it is working perfectly)

for f1 in `find . -maxdepth 1 -name "*.mkv" | sed 's/^\.\///g'`; do ffmpeg -i "$f1" -q:a 0 -map a ../wav/"${f1%.*}.wav"; done
AdminBee
  • 22,803
Arani
  • 1
  • Hi, can you check now, I have added more explanations? – Arani Jul 11 '22 at 09:13
  • Looks much clearer now, I just added syntax highlighting at a few more places. Please note, however, that looping over find's output is discouraged as it can stumble on "unusual" characters in filenames; you may want to add a notice of caution about that circumstance (although it can work if you can guarantee that all filenames are "well-behaved"). – AdminBee Jul 11 '22 at 09:19