bertieb
bertieb

Reputation: 198

Why does calling ffmpeg mess up reading variables?

A few years ago when I was younger, more carefree, and, uh, less cognisant of good practice in writing shell scripts; I wrote a quick-and-dirty script to assist with a task I was facing:

#!/bin/bash                                                                                                         

# autohighlighter which generates highlights from a video clip and file                                             

process_highlight_file() {                                                                                          
        n=0                                                                                                         
        while read -r line; do                                                                                      
                begin=$(echo "$line" | awk '{ print $1 }' )                                                         
                end=$(echo "$line" | awk '{ print $2 }')                                                            
                hilightname=$(echo "$line" | awk '{ print $3 }')                                                    
                printf "Begin highlight called %s at %s, to %s\n" "$hilightname" "$begin" "$end"                   
                echo "$begin $end"                                                                                 
                sleep 2                                                                                             
                echo "ffmpeg -y -ss $begin -i $videofile -to $end -c copy -avoid_negative_ts 1 $hilightname.mkv"    
        ffmpeg -loglevel quiet -hide_banner -y -ss "$begin" -i "$videofile" -to "$end" -c copy -avoid_negative_ts 1 "$hilightname.mkv"                                                                                                                                                                   
                if [ "$n" -eq 0 ]; then                                                                             
                        echo -n "melt $hilightname.mkv " > constructed.melt                                         
                else                                                                                                
                        echo -n "$hilightname.mkv -mix 120 -mixer luma " >> constructed.melt                        
                fi                                                                                                  
                (( n++ ))                                                                                           
        done < $highlightfile                                                                                       
        echo -n "-consumer avformat:$videofile-highlights.mkv crf=18" >> constructed.melt                           
}                                                                                                                   

highlightfile=$2                                                                                                    
videofile=$1                                                                                                        
process_highlight_file                                                                                              
exit 0

I call it with the video file name and a highlight file with the following tab-separated contents:

3:55    4:15    tutorialcomplete
10:50   11:15   firstkill
13:30   14:00   pickpocket

If I comment out the actual call to ffmpeg, I get sensible output:

Begin highlight called tutorialcomplete at 3:55, to 4:15
3:55 4:15
ffmpeg -y -ss 3:55 -i 2019-08-27 20-31-27.mkv -to 4:15 -c copy -avoid_negative_ts 1 tutorialcomplete.mkv
Begin highlight called firstkill at 10:50, to 11:15
10:50 11:15
ffmpeg -y -ss 10:50 -i /tmp/footage/gamefootage/pending/sor/2019-08-27 20-31-27.mkv -to 11:15 -c copy -avoid_negative_ts 1 firstkill.mkv
Begin highlight called pickpocket at 13:30, to 14:00
13:30 14:00
ffmpeg -y -ss 13:30 -i /tmp/footage/gamefootage/pending/sor/2019-08-27 20-31-27.mkv -to 14:00 -c copy -avoid_negative_ts 1 pickpocket.mkv

All good, if rather overkill on the debug output.

If I uncomment the call to ffmpeg, I get:

Begin highlight called tutorialcomplete at 3:55, to 4:15
3:55 4:15
ffmpeg -y -ss 3:55 -i /tmp/footage/gamefootage/pending/sor/2019-08-27 20-31-27.mkv -to 4:15 -c copy -avoid_negative_ts 1 tutorialcomplete.mkv
Begin highlight called  at firstkill, to 
firstkill 
ffmpeg -y -ss firstkill -i /tmp/footage/gamefootage/pending/sor/2019-08-27 20-31-27.mkv -to  -c copy -avoid_negative_ts 1 .mkv
Begin highlight called pickpocket at 13:30, to 14:00
13:30 14:00
ffmpeg -y -ss 13:30 -i /tmp/footage/gamefootage/pending/sor/2019-08-27 20-31-27.mkv -to 14:00 -c copy -avoid_negative_ts 1 pickpocket.mkv

Naturally, ffmpeg complains that "firstkill" is not a valid time to seek to. If I add further lines to the file I am passing in, it seems to only affect the second pass through the loop. Additional output is also produced:

Enter command: <target>|all <time>|-1 <command>[ <argument>]

The going theory is that a line isn't properly terminated. However, I can't seem to track that down in the script or the input.

I'm aware there are a number of poor practices, foibles and other unexpected behaviour here, for which past-me definitely takes the blame and hangs their head in shame! That said, why does calling ffmpeg in the loop here cause values from a file to be parsed incorrectly, or: why does commenting out the line with the call to ffmpeg give the variables the correct value. Also, why does it only affect the second pass of the loop? Where does Enter command come from?

Additionally, shellcheck.net doesn't complain about anything in the code.

Upvotes: 1

Views: 369

Answers (1)

L&#233;a Gris
L&#233;a Gris

Reputation: 19555

You need to prevent FFMpeg from consuming the stdin characters stream.

See: man ffmpeg.1

  • -stdin

    Enable interaction on standard input. On by default unless standard input is used as an input. 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.

Here in your loop, ffmpeg is consuming the input from your while loop commands block.

while read -r line; do                                                                                      
...
  # here ffmpeg defaults to consuming the same input
  # $highlightfile that is fed to the while loop commands block.
  ffmpeg -loglevel quiet -hide_banner -y -ss "$begin" -i "$videofile" -to "$end" -c copy -avoid_negative_ts 1 "$hilightname.mkv"                                                                                                                                                                   
done < $highlightfile

A fixed version of your code:

#!/usr/bin/env bash

# autohighlighter which generates highlights from a video clip and file

process_highlight_file() {
  videofile="$1"
  highlightfile="$2"

  n=0
  melt_caller=(melt)

  while read -r begin end hilightname; do
    printf 'Begin highlight called %s at %s, to %s\n' "$hilightname" "$begin" "$end"
    ffmpeg_caller=(ffmpeg -nostdin -loglevel quiet -hide_banner -y -ss "$begin" -i "$videofile" -to "$end" -c copy -avoid_negative_ts 1 "$hilightname.mkv")
    env printf '%q ' "${ffmpeg_caller[@]}" && echo
    "${ffmpeg_caller[@]}"
    ((n++)) && melt_caller+=(-mix 120 -mixer luma)
    melt_caller+=("$hilightname.mkv")
  done <"$highlightfile"
  melt_caller+=(-consumer "avformat:$videofile-highlights.mkv" 'crf=18')
  env printf '%q ' "${melt_caller[@]}" && echo
  "${melt_caller[@]}"
}

process_highlight_file "$@"

Upvotes: 5

Related Questions