为什么调用 ffmpeg 会弄乱读取变量?

ber*_*ieb 1 bash

几年前,当我还年轻、无忧无虑、呃、不太了解编写 shell 脚本的良好实践时;我编写了一个快速而肮脏的脚本来协助完成我面临的任务

#!/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
Run Code Online (Sandbox Code Playgroud)

我使用视频文件名和突出显示文件来调用它,其中包含以下制表符分隔的内容:

3:55    4:15    tutorialcomplete
10:50   11:15   firstkill
13:30   14:00   pickpocket
Run Code Online (Sandbox Code Playgroud)

如果我注释掉对 的实际调用ffmpeg,我会得到合理的输出:

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
Run Code Online (Sandbox Code Playgroud)

一切都很好,只是对调试输出有点过分了。

如果我取消对 的调用的注释ffmpeg,我会得到:

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
Run Code Online (Sandbox Code Playgroud)

当然,ffmpeg抱怨“首杀”不是寻求的有效时间。如果我向传入的文件添加更多行,它似乎只会影响循环的第二次传递。还产生额外的输出:

Enter command: <target>|all <time>|-1 <command>[ <argument>]
Run Code Online (Sandbox Code Playgroud)

目前的理论是线路没有正确终止。但是,我似乎无法在脚本或输入中找到它。

我知道这里存在许多不良做法、缺点和其他意想不到的行为,过去的我肯定对此负有责任并羞愧地低下了头!也就是说,为什么在此处的循环中调用 ffmpeg 会导致文件中的值被错误解析,或者:为什么通过调用 ffmpeg 注释掉该行会给变量提供正确的值。另外,为什么它只影响循环的第二遍?从哪里来Enter command

此外,shellcheck.net 不会抱怨代码中的任何内容。

Léa*_*ris 5

您需要防止 FFMpeg 消耗stdin字符流。

看:man ffmpeg.1

  • -stdin

    启用标准输入交互。除非使用标准输入作为输入,否则默认打开。要显式禁用交互,您需要指定-nostdin.

    禁用标准输入上的交互很有用,例如,如果 ffmpeg 位于后台进程组中。可以实现大致相同的结果,ffmpeg ... < /dev/null但它需要一个外壳。

在循环中,ffmpeg正在消耗循环命令块的输入while

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
Run Code Online (Sandbox Code Playgroud)

代码的固定版本:

#!/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 "$@"
Run Code Online (Sandbox Code Playgroud)