【问题标题】:Execute ffmpeg command from python with subprocess使用子进程从python执行ffmpeg命令
【发布时间】:2019-06-21 14:49:53
【问题描述】:

以下命令在命令行中可以正常工作:

ffmpeg -y -threads 4 -i /dev/video0 -filter_complex "[v:0]scale=-2:720:force_original_aspect_ratio=decrease[vout001]" -c:v libx264 -b:v 2800k -maxrate:v 2996k -bufsize:v 4200k -c:a aac -b:a 128k -ac 2 -ar 48000 -preset veryfast -x264opts keyint=25:min-keyint=25:no-scenecut -sc_threshold 0 -r 25 -pix_fmt yuv420p -segment_list_flags +live -map [vout001] -f tee -var_stream_map 'v:0' "[f=hls:hls_time=1:hls_playlist_type=event:strftime=1:hls_flags=independent_segments+program_date_time+second_level_segment_index:hls_segment_filename='segment_%%06d_%Y%m%d%H%M%S.ts']playlist.m3u8|[f=hls:hls_time=1:hls_playlist_type=event:strftime=1:hls_flags=independent_segments+program_date_time+second_level_segment_index:hls_segment_filename=\'http://X.X.X.X:pppp/ABCD/segment_%%06d_%Y%m%d%H%M%S.ts\':method=PUT]http://X.X.X.X:pppp/ABCD/playlist.m3u8"

但是,当我使用以下命令通过子进程从 python 代码执行它时,它会引发错误:

cmd_ffmpeg = ['ffmpeg', '-y', '-threads', '4', '-i', '/dev/video0', '-filter_complex', '[v:0]scale=-2:720:force_original_aspect_ratio=decrease[vout001]', '-c:v', 'libx264', '-b:v', '2800k', '-maxrate:v', '2996k', '-bufsize:v', '4200k', '-c:a', 'aac', '-b:a', '128k', '-ac', '2', '-ar', '48000', '-preset', 'veryfast', '-x264opts', 'keyint=25:min-keyint=25:no-scenecut', '-sc_threshold', '0', '-r', '25', '-pix_fmt', 'yuv420p', '-segment_list_flags', '+live', '-map', '[vout001]', '-f', 'tee', '"[f=hls:hls_time=1:hls_playlist_type=event:strftime=1:hls_flags=independent_segments+program_date_time+second_level_segment_index:hls_segment_filename=\'segment_%%06d_%Y%m%d%H%M%S.ts\']playlist.m3u8|[f=hls:hls_time=1:hls_playlist_type=event:strftime=1:hls_flags=independent_segments+program_date_time+second_level_segment_index:hls_segment_filename=\'http://X.X.X.X:pppp/ABCD/segment_%%06d_%Y%m%d%H%M%S.ts\':method=PUT]http://X.X.X.X:pppp/ABCD/playlist.m3u8"']

错误如下:

No option found near "//X.X.X.X:pppp/ABCD/segment_%%06d_%Y%m%d%H%M%S.ts":method=PUT]http://X.X.X.X:pppp/ABCD/playlist.m3u8"

当它从 python 代码执行时,它认为 'http' 之后的 ':' 作为选项分隔符(转义不起作用),而当直接从 shell 执行时,转义工作正常。

我该如何解决这个问题?

【问题讨论】:

  • 您是否尝试过三重引用?
  • 如果您可以将两个语句分成多行 - 每个选项一个 - 以便我们更容易阅读,那将是很好的。在 shell 语句中,您可以在每行末尾使用尾随反斜杠进行多行处理,在 python 语句中,您可以在列表中的每个逗号之后将其拆分。但除此之外,我想说从最后一个列表项中删除双引号可能会解决您的问题。
  • 考虑使用shlex.quote

标签: python ffmpeg


【解决方案1】:

ffmpeg 看到命令行之前,shell 会丢弃长字符串周围的双引号。您可以在 Python 中简单地将它们替换为单引号。在单引号内使用文字双引号会导致 ffmpeg 选项解析器失效。

【讨论】:

    【解决方案2】:

    谢谢你们的建议和帮助。我已经按照建议尝试了三引号,但它会产生另一个问题,其中包含具有以下文件名的视频片段/播放列表:

    "f=hls:hls_time=1:hls_playlist_type=event:strftime=1:hls_flags=independent_segments+program_date_time+second_level_segment_index:hls_segment_filename=segment_%%06d_%Y%m%d%H%M%S.ts]playlist.m3u8
    
    "f=hls:hls_time=1:hls_playlist_type=event:strftime=1:hls_flags=independent_segments+program_date_time+second_level_segment_index:hls_segment_filename=segment_%%06d_%Y%m0%H%M%S.ts]playlist0.ts
    
    "f=hls:hls_time=1:hls_playlist_type=event:strftime=1:hls_flags=independent_segments+program_date_time+second_level_segment_index:hls_segment_filename=segment_%%06d_%Y%m11%H%M%S.ts]playlist11.ts
    

    ……

    因此,它将参数列表视为片段/播放列表文件名。此外,它生成一个输出而不是两个输出(一个在本地,另一个到远程服务器)。

    事实上,解决方案是保持相同的命令,只在远程服务器 url 中的 ':' 之前添加 ''。因此,在 python 代码中运行良好的最终命令是:

    cmd_ffmpeg = ['ffmpeg', '-y', '-threads', '4', '-i', '/dev/video0', '-filter_complex', '[v:0]scale=-2:720:force_original_aspect_ratio=decrease[vout001]', '-c:v', 'libx264', '-b:v', '2800k', '-maxrate:v', '2996k', '-bufsize:v', '4200k', '-c:a', 'aac', '-b:a', '128k', '-ac', '2', '-ar', '48000', '-preset', 'veryfast', '-x264opts', 'keyint=25:min-keyint=25:no-scenecut', '-sc_threshold', '0', '-r', '25', '-pix_fmt', 'yuv420p', '-segment_list_flags', '+live', '-map', '[vout001]', '-f', 'tee', '-var_stream_map', 'v:0', '[f=hls:hls_time=1:hls_playlist_type=event:strftime=1:hls_flags=independent_segments+program_date_time+second_level_segment_index:hls_segment_filename='segment_%%06d_%Y%m%d%H%M%S.ts']playlist.m3u8|[f=hls:hls_time=1:hls_playlist_type=event:strftime=1:hls_flags=independent_segments+program_date_time+second_level_segment_index:hls_segment_filename='http\://X.X.X.X:pppp/ABCD/segment_%%06d_%Y%m%d%H%M%S.ts']http://X.X.X.X:pppp/ABCD/playlist.m3u8']
    

    关于选项/值:'-var_stream_map' 和 'v:0',我只是错过了。它可以被忽略,因为我们只有一个输入。

    谢谢。

    【讨论】:

      【解决方案3】:

      你是怎么产生的cmd_ffmpeg?手动?它与shlex.split 返回的不同。例如,cmd_ffmpeg后面有双引号filter_complex而什么shlex.split返回没有。

      import shlex
      shell_ffmpeg_cmd = r'''ffmpeg -y -threads 4 -i /dev/video0 -filter_complex "[v:0]scale=-2:720:force_original_aspect_ratio=decrease[vout001]" -c:v libx264 -b:v 2800k -maxrate:v 2996k -bufsize:v 4200k -c:a aac -b:a 128k -ac 2 -ar 48000 -preset veryfast -x264opts keyint=25:min-keyint=25:no-scenecut -sc_threshold 0 -r 25 -pix_fmt yuv420p -segment_list_flags +live -map [vout001] -f tee -var_stream_map 'v:0' "[f=hls:hls_time=1:hls_playlist_type=event:strftime=1:hls_flags=independent_segments+program_date_time+second_level_segment_index:hls_segment_filename='segment_%%06d_%Y%m%d%H%M%S.ts']playlist.m3u8|[f=hls:hls_time=1:hls_playlist_type=event:strftime=1:hls_flags=independent_segments+program_date_time+second_level_segment_index:hls_segment_filename='http://X.X.X.X:pppp/ABCD/segment_%%06d_%Y%m%d%H%M%S.ts':method=PUT]http://X.X.X.X:pppp/ABCD/playlist.m3u8"'''
      popen_args = shlex.split(shell_ffmpeg_cmd)
      print(" ".join(popen_args))
      

      此外,对于它的价值,这两个开关从cmd_ffmpeg,与您的 shell 调用相比:-var_stream_map'v:0'

      不过,您的问题似乎与列表中的最后一个元素有关。正如eatmeimadanish 建议的那样,尝试三重引用它。以下是你的cmd_ffmpeg最后一个元素更正并三重单引号。它打印 ffmpeg 的 stderr,根据我的经验,它派上用场:

      from subprocess import Popen, PIPE
      
      cmd_ffmpeg = ['ffmpeg', '-y', '-threads', '4', '-i', '/dev/video0', '-filter_complex', '[v:0]scale=-2:720:force_original_aspect_ratio=decrease[vout001]', '-c:v', 'libx264', '-b:v', '2800k', '-maxrate:v', '2996k', '-bufsize:v', '4200k', '-c:a', 'aac', '-b:a', '128k', '-ac', '2', '-ar', '48000', '-preset', 'veryfast', '-x264opts', 'keyint=25:min-keyint=25:no-scenecut', '-sc_threshold', '0', '-r', '25', '-pix_fmt', 'yuv420p', '-segment_list_flags', '+live', '-map', '[vout001]', '-f', 'tee', '''"[f=hls:hls_time=1:hls_playlist_type=event:strftime=1:hls_flags=independent_segments+program_date_time+second_level_segment_index:hls_segment_filename='segment_%%06d_%Y%m%d%H%M%S.ts']playlist.m3u8|[f=hls:hls_time=1:hls_playlist_type=event:strftime=1:hls_flags=independent_segments+program_date_time+second_level_segment_index:hls_segment_filename='http://X.X.X.X:pppp/ABCD/segment_%%06d_%Y%m%d%H%M%S.ts':method=PUT]http://X.X.X.X:pppp/ABCD/playlist.m3u8"''']
      with Popen(cmd_ffmpeg, text=True, stdout=PIPE, stderr=PIPE) as p:
          for line in p.stderr:
              print(line, end="") # stderr already includes a newline
      

      【讨论】:

      • 如果您检查来自shlex.split() 的结果,您应该注意到它丢弃了任何带引号的字符串周围的引号。
      【解决方案4】:

      如果你想在 python 代码中运行 ffmpeg 命令,你可以试试这个:

      import os
      cmd_ffmpeg = """ffmpeg -y -threads 4 -i /dev/video0 -filter_complex "[v:0]scale=-2:720:force_original_aspect_ratio=decrease[vout001]" -c:v libx264 -b:v 2800k -maxrate:v 2996k -bufsize:v 4200k -c:a aac -b:a 128k -ac 2 -ar 48000 -preset veryfast -x264opts keyint=25:min-keyint=25:no-scenecut -sc_threshold 0 -r 25 -pix_fmt yuv420p -segment_list_flags +live -map [vout001] -f tee -var_stream_map 'v:0' "[f=hls:hls_time=1:hls_playlist_type=event:strftime=1:hls_flags=independent_segments+program_date_time+second_level_segment_index:hls_segment_filename='segment_%%06d_%Y%m%d%H%M%S.ts']playlist.m3u8|[f=hls:hls_time=1:hls_playlist_type=event:strftime=1:hls_flags=independent_segments+program_date_time+second_level_segment_index:hls_segment_filename='http://X.X.X.X:pppp/ABCD/segment_%%06d_%Y%m%d%H%M%S.ts':method=PUT]http://X.X.X.X:pppp/ABCD/playlist.m3u8""""
      print(cmd_ffmpeg)
      os.system(cmd_ffmpeg)
      

      【讨论】:

        最近更新 更多