|
|
|
import os
|
|
import os.path as osp
|
|
import subprocess
|
|
import tempfile
|
|
|
|
from annotator.uniformer.mmcv.utils import requires_executable
|
|
|
|
|
|
@requires_executable('ffmpeg')
|
|
def convert_video(in_file,
|
|
out_file,
|
|
print_cmd=False,
|
|
pre_options='',
|
|
**kwargs):
|
|
"""Convert a video with ffmpeg.
|
|
|
|
This provides a general api to ffmpeg, the executed command is::
|
|
|
|
`ffmpeg -y <pre_options> -i <in_file> <options> <out_file>`
|
|
|
|
Options(kwargs) are mapped to ffmpeg commands with the following rules:
|
|
|
|
- key=val: "-key val"
|
|
- key=True: "-key"
|
|
- key=False: ""
|
|
|
|
Args:
|
|
in_file (str): Input video filename.
|
|
out_file (str): Output video filename.
|
|
pre_options (str): Options appears before "-i <in_file>".
|
|
print_cmd (bool): Whether to print the final ffmpeg command.
|
|
"""
|
|
options = []
|
|
for k, v in kwargs.items():
|
|
if isinstance(v, bool):
|
|
if v:
|
|
options.append(f'-{k}')
|
|
elif k == 'log_level':
|
|
assert v in [
|
|
'quiet', 'panic', 'fatal', 'error', 'warning', 'info',
|
|
'verbose', 'debug', 'trace'
|
|
]
|
|
options.append(f'-loglevel {v}')
|
|
else:
|
|
options.append(f'-{k} {v}')
|
|
cmd = f'ffmpeg -y {pre_options} -i {in_file} {" ".join(options)} ' \
|
|
f'{out_file}'
|
|
if print_cmd:
|
|
print(cmd)
|
|
subprocess.call(cmd, shell=True)
|
|
|
|
|
|
@requires_executable('ffmpeg')
|
|
def resize_video(in_file,
|
|
out_file,
|
|
size=None,
|
|
ratio=None,
|
|
keep_ar=False,
|
|
log_level='info',
|
|
print_cmd=False):
|
|
"""Resize a video.
|
|
|
|
Args:
|
|
in_file (str): Input video filename.
|
|
out_file (str): Output video filename.
|
|
size (tuple): Expected size (w, h), eg, (320, 240) or (320, -1).
|
|
ratio (tuple or float): Expected resize ratio, (2, 0.5) means
|
|
(w*2, h*0.5).
|
|
keep_ar (bool): Whether to keep original aspect ratio.
|
|
log_level (str): Logging level of ffmpeg.
|
|
print_cmd (bool): Whether to print the final ffmpeg command.
|
|
"""
|
|
if size is None and ratio is None:
|
|
raise ValueError('expected size or ratio must be specified')
|
|
if size is not None and ratio is not None:
|
|
raise ValueError('size and ratio cannot be specified at the same time')
|
|
options = {'log_level': log_level}
|
|
if size:
|
|
if not keep_ar:
|
|
options['vf'] = f'scale={size[0]}:{size[1]}'
|
|
else:
|
|
options['vf'] = f'scale=w={size[0]}:h={size[1]}:' \
|
|
'force_original_aspect_ratio=decrease'
|
|
else:
|
|
if not isinstance(ratio, tuple):
|
|
ratio = (ratio, ratio)
|
|
options['vf'] = f'scale="trunc(iw*{ratio[0]}):trunc(ih*{ratio[1]})"'
|
|
convert_video(in_file, out_file, print_cmd, **options)
|
|
|
|
|
|
@requires_executable('ffmpeg')
|
|
def cut_video(in_file,
|
|
out_file,
|
|
start=None,
|
|
end=None,
|
|
vcodec=None,
|
|
acodec=None,
|
|
log_level='info',
|
|
print_cmd=False):
|
|
"""Cut a clip from a video.
|
|
|
|
Args:
|
|
in_file (str): Input video filename.
|
|
out_file (str): Output video filename.
|
|
start (None or float): Start time (in seconds).
|
|
end (None or float): End time (in seconds).
|
|
vcodec (None or str): Output video codec, None for unchanged.
|
|
acodec (None or str): Output audio codec, None for unchanged.
|
|
log_level (str): Logging level of ffmpeg.
|
|
print_cmd (bool): Whether to print the final ffmpeg command.
|
|
"""
|
|
options = {'log_level': log_level}
|
|
if vcodec is None:
|
|
options['vcodec'] = 'copy'
|
|
if acodec is None:
|
|
options['acodec'] = 'copy'
|
|
if start:
|
|
options['ss'] = start
|
|
else:
|
|
start = 0
|
|
if end:
|
|
options['t'] = end - start
|
|
convert_video(in_file, out_file, print_cmd, **options)
|
|
|
|
|
|
@requires_executable('ffmpeg')
|
|
def concat_video(video_list,
|
|
out_file,
|
|
vcodec=None,
|
|
acodec=None,
|
|
log_level='info',
|
|
print_cmd=False):
|
|
"""Concatenate multiple videos into a single one.
|
|
|
|
Args:
|
|
video_list (list): A list of video filenames
|
|
out_file (str): Output video filename
|
|
vcodec (None or str): Output video codec, None for unchanged
|
|
acodec (None or str): Output audio codec, None for unchanged
|
|
log_level (str): Logging level of ffmpeg.
|
|
print_cmd (bool): Whether to print the final ffmpeg command.
|
|
"""
|
|
tmp_filehandler, tmp_filename = tempfile.mkstemp(suffix='.txt', text=True)
|
|
with open(tmp_filename, 'w') as f:
|
|
for filename in video_list:
|
|
f.write(f'file {osp.abspath(filename)}\n')
|
|
options = {'log_level': log_level}
|
|
if vcodec is None:
|
|
options['vcodec'] = 'copy'
|
|
if acodec is None:
|
|
options['acodec'] = 'copy'
|
|
convert_video(
|
|
tmp_filename,
|
|
out_file,
|
|
print_cmd,
|
|
pre_options='-f concat -safe 0',
|
|
**options)
|
|
os.close(tmp_filehandler)
|
|
os.remove(tmp_filename)
|
|
|