def convert_subtitles_to_srt(i: str, o: str): ext = os.path.splitext(i)[1] if ext == '.srt': import shutil shutil.copy(i, o) elif ext in ('.ttml', '.xml', '.dfxp', '.tt'): # TTML from media_management_scripts.support.ttml2srt import convert_to_srt convert_to_srt(i, o) else: # VTT, SCC, etc from pycaption import detect_format, SRTWriter subtitle_str = _read_file(i) reader = detect_format(subtitle_str) if reader: subtitle_str = SRTWriter().write(reader().read(subtitle_str)) with open(o, 'w') as file: file.write(subtitle_str) else: # Attempt to use FFMPEG from media_management_scripts.support.executables import ffmpeg from media_management_scripts.support.executables import execute_with_output args = [ ffmpeg(), '-loglevel', 'fatal', '-y', '-i', i, '-c:s', 'srt', o ] ret, output = execute_with_output(args) if ret != 0: raise Exception( 'Exception during subtitle conversion: {}'.format(output))
def _execute_ffmpeg(input_file: str, frames: int, start: int = 0): args = [ ffmpeg(), '-ss', str(start), '-i', input_file, '-filter:v', 'idet', '-frames:v', str(frames), '-an', '-f', 'rawvideo', '-y', '/dev/null' ] ret, output = execute_with_output(args, print_output=False) if ret != 0: raise Exception('Non-zero ffmpeg return code: {}. Output={}'.format( ret, output)) return _parse_output(output)
def _temp_convert(input, output, print_output=False): # ffmpeg -i input1.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate1.ts print('Converting {}'.format(input)) args = [ ffmpeg(), '-loglevel', 'fatal', '-i', input, '-c:v', 'copy', '-c:a', 'copy', '-c:s', 'copy', '-bsf:v', 'h264_mp4toannexb', '-f', 'mpegts', output ] ret, r = execute_with_output(args, print_output=print_output) if ret != 0: raise Exception('Error during ffmpeg: {}'.format(r))
def _concat(files, output, print_output=False): # ffmpeg -i "concat:intermediate1.ts|intermediate2.ts" -c copy -bsf:a aac_adtstoasc output.mp4 concat_str = 'concat:' for f in files: concat_str += f + '|' concat_str = concat_str[0:-1] print(concat_str) args = [ ffmpeg(), '-loglevel', 'fatal', '-y', '-i', concat_str, '-c:v', 'copy', '-c:a', 'copy', '-c:s', 'copy', '-bsf:a', 'aac_adtstoasc', output ] ret, r = execute_with_output(args, print_output=print_output) if ret != 0: raise Exception('Error during ffmpeg: {}'.format(r))
def cut(input, output, start=None, end=None): if check_exists(output): return -1 create_dirs(output) args = [ffmpeg(), '-i', input] args.extend(['-c:v', 'copy']) args.extend(['-c:a', 'copy']) args.extend(['-c:s', 'copy']) args.extend(['-map', '0']) if start: args.extend(['-ss', str(start)]) if end: args.extend(['-to', str(end)]) args.append(output) return execute(args)
def combine(video, srt, output, lang=None, overwrite=False, convert=False, crf=DEFAULT_CRF, preset=DEFAULT_PRESET, skip_eia_608=True): if not overwrite and check_exists(output): return -1 if srt.endswith('.ttml') or srt.endswith('.xml'): logger.debug('Converting ttml/xml to srt') name, _ = os.path.splitext(srt) srt_out = name + '.srt' convert_to_srt(srt, srt_out) srt = srt_out create_dirs(output) args = [ffmpeg(), '-i', video] if overwrite: args.append('-y') args.extend(['-i', srt]) args.extend(['-map', '0:v', '-map', '0:a']) if skip_eia_608: metadata = extract_metadata(video) for i in (s.index for s in metadata.subtitle_streams if s.codec != 'eia_608'): args.extend(['-map', '0:{}'.format(i)]) else: args.extend(['-map', '0:s?']) args.extend(['-map', '1:0']) if convert: args.extend(['-c:v', 'libx264', '-crf', str(crf), '-preset', preset]) args.extend(['-c:a', 'aac']) else: args.extend(['-c:v', 'copy']) args.extend(['-c:a', 'copy']) args.extend(['-c:s', 'copy']) # -metadata:s:s:0 language=eng if lang: args.extend(['-metadata:s:s:0', 'language=' + lang]) args.append(output) return execute(args)
def convert(self, in_file, out_file, commercials, metadata): invert = invert_commercial(commercials) temp_files = [] wo_ext = os.path.basename(in_file).replace('.wtv', '') try: args = [ffmpeg(), '-i', in_file] for i in invert: temp_file = os.path.join( self.temp_dir, wo_ext + '.' + str(len(temp_files)) + '.ts') temp_files.append(temp_file) if os.path.isfile(temp_file): os.remove(temp_file) args.extend(self.cut_args(i, temp_file)) ret, output = execute_with_output(args) if ret != 0: logger.error('Nonzero return code from ffmpeg: {}'.format(ret)) return False else: input = 'concat:{}'.format('|'.join(temp_files)) ret = convert_with_config(input, out_file, config=self.convert_config, print_output=False, overwrite=True, metadata=metadata) if ret != 0: logger.error( 'Nonzero return code from ffmpeg: {}'.format(ret)) # Cleanup temp files if not self.debug: for f in temp_files: os.remove(f) except Exception as e: logger.exception('Exception') return False return True
def create_remux_args(input_files: List[str], output_file: str, mappings: List, overwrite=False, metadata={}): """ :param input_files: :param output_file: :param mappings: :param overwrite: :param metadata: {'s:0': {'language':'eng'}, '': {'title': 'A Great Movie'}} :return: """ if not overwrite and check_exists(output_file): return -1 args = [ffmpeg()] if overwrite: args.append('-y') for input_file in input_files: args.extend(['-i', input_file]) args.extend(['-c', 'copy']) for m in mappings: if type(m) == int: args.extend(['-map', '0:{}'.format(m)]) else: args.extend(['-map', m]) for key, value in metadata.items(): if key: key = ':s:' + key for meta_key, meta_val in value.items(): args.append('-metadata{}'.format(key)) args.append('{}={}'.format(meta_key, meta_val)) args.append(output_file) return args
def create_test_video( length: int = 30, video_def: VideoDefinition = VideoDefinition(), audio_defs: List[AudioDefition] = [AudioDefition()], output_file=None, metadata: Dict[str, str] = None) -> _TemporaryFileWrapper: """ Creates a video file matching the given video & audio definitions. If an output_file is not provided, a NamedTemporaryFile is used and returned :param length: the length of the file in seconds (Note, depending on codecs, the exact length may vary slightly) :param video_def: the video definition (codec, resolution, etc) :param audio_defs: the list of audio tracks :param output_file: the output file (or None for a NamedTemporaryFile) :return: the NamedTemporaryFile if no output_file was provided """ audio_files = [] if len(audio_defs) > 0: with NamedTemporaryFile(suffix='.wav') as raw_audio_file: # Create raw audio args = [ ffmpeg(), '-y', '-ar', '48000', '-f', 's16le', '-i', '/dev/urandom', '-t', str(length), raw_audio_file.name ] _execute(args) for audio_def in audio_defs: audio_file = NamedTemporaryFile( suffix='.{}'.format(audio_def.codec.extension)) audio_files.append(audio_file) args = [ ffmpeg(), '-y', '-i', raw_audio_file.name, '-strict', '-2', '-c:a', audio_def.codec.ffmpeg_codec_name, '-ac', str(audio_def.channels.num_channels), '-t', str(length), audio_file.name ] _execute(args) # ffmpeg -f lavfi -i testsrc=duration=10:size=1280x720:rate=30 testsrc.mpg if not output_file: file = NamedTemporaryFile( suffix='.{}'.format(video_def.container.extension)) args = [ ffmpeg(), '-y', '-f', 'lavfi', '-i', 'testsrc=duration={}:size={}x{}:rate=30'.format( length, video_def.resolution.width, video_def.resolution.height) ] for f in audio_files: args.extend(['-i', f.name]) args.extend(['-c:v', video_def.codec.ffmpeg_encoder_name]) if video_def.interlaced: args.extend(['-vf', 'tinterlace=6']) if len(audio_defs) > 0: args.extend(['-c:a', 'copy']) for i in range(len(audio_defs) + 1): args.extend(['-map', str(i)]) if metadata: for key, value in metadata.items(): args.extend(['-metadata', '{}={}'.format(key, value)]) if output_file: args.extend(['-t', str(length), output_file]) else: args.extend(['-t', str(length), file.name]) _execute(args) for f in audio_files: f.close() if not output_file: return file
def convert_with_config(input, output, config: ConvertConfig, print_output=True, overwrite=False, metadata=None, mappings=None, use_nice=True): """ :param input: :param output: :param config: :param print_output: :param overwrite: :param metadata: :param mappings: List of mappings (for example ['0:0', '0:1']) :return: """ if not overwrite and check_exists(output): return -1 if print_output: print('Converting {} -> {}'.format(input, output)) print('Using config: {}'.format(config)) if not metadata: metadata = create_metadata_extractor().extract( input, detect_interlace=config.deinterlace) elif config.deinterlace and not metadata.interlace_report: raise Exception( 'Metadata provided without interlace report, but convert requires deinterlace checks' ) if metadata.resolution not in (Resolution.LOW_DEF, Resolution.STANDARD_DEF, Resolution.MEDIUM_DEF, Resolution.HIGH_DEF) and not config.scale: print('{}: Resolution not supported for conversion: {}'.format( input, metadata.resolution)) # TODO Handle converting 4k content in H.265/HVEC return -2 if use_nice and nice_exe: args = [nice_exe, ffmpeg()] else: args = [ffmpeg()] if overwrite: args.append('-y') args.extend(['-i', input]) if config.scale: args.extend(['-vf', 'scale=-1:{}'.format(config.scale)]) args.extend(['-c:v', config.video_codec]) crf = config.crf bitrate = config.bitrate if VideoCodec.H264.equals( config.video_codec ) and config.bitrate is not None and config.bitrate != 'disabled': crf = 1 # -x264-params vbv-maxrate=1666:vbv-bufsize=3332:crf-max=22:qpmax=34 if config.bitrate == 'auto': bitrate = auto_bitrate_from_config(metadata.resolution, config) params = 'vbv-maxrate={}:vbv-bufsize={}:crf-max=25:qpmax=34'.format( str(bitrate), str(bitrate * 2)) args.extend(['-x264-params', params]) elif VideoCodec.H265.equals( config.video_codec ) and config.bitrate is not None and config.bitrate != 'disabled': raise Exception('Avg Bitrate not supported for H265') args.extend(['-crf', str(crf), '-preset', config.preset]) if config.deinterlace: is_interlaced = metadata.interlace_report.is_interlaced( config.deinterlace_threshold) if print_output: print('{} - Interlaced: {}'.format(metadata.interlace_report, is_interlaced)) if is_interlaced: # Video is interlaced, so add the deinterlace filter args.extend(['-vf', 'yadif']) args.extend(['-c:a', config.audio_codec]) index = 0 for audio in metadata.audio_streams: if audio.channels == 7: # 6.1 sound, so mix it up to 7.1 args.extend(['-ac:a:{}'.format(index), '8']) index += 1 if config.include_subtitles: args.extend(['-c:s', 'copy']) if not mappings: args.extend(['-map', '0']) else: for m in mappings: if type(m) == int: args.extend(['-map', '0:{}'.format(m)]) else: args.extend(['-map', m]) if config.include_meta: args.extend(['-metadata', 'ripped=true']) args.extend(['-metadata:s:v:0', 'ripped=true']) args.append(output) return execute(args, print_output)