4 from tempfile import NamedTemporaryFile
5 from django.conf import settings
8 FILE_CACHE = getattr(settings, 'FILE_CACHE', 'file_cache/')
11 def link_or_copy(src, dst):
12 dstdir = os.path.dirname(dst)
13 if not os.path.exists(dstdir):
15 if os.path.exists(dst):
17 # FIXME: tiny window here when the temp path is not taken.
21 shutil.copyfile(src, dst)
24 def process_to_file(cmdline, prefix='', suffix='', cache_key=None, output_path=None):
26 tmp = NamedTemporaryFile(
27 prefix=prefix, suffix=suffix, delete=False,
28 dir=settings.FILE_UPLOAD_TEMP_DIR
31 output_path = tmp.name
34 cache_path = FILE_CACHE + cache_key.replace('/', '__')
36 if cache_key and os.path.exists(cache_path):
37 link_or_copy(cache_path, output_path)
39 # Actually run the processing.
40 subprocess.run(cmdline + [output_path], check=True)
42 link_or_copy(output_path, cache_path)
47 def video_from_image(img_path, duration, fps=25, cache=True):
48 return process_to_file(
49 ['ffmpeg', '-y', '-loop', '1', '-t', str(duration), '-i', img_path, '-c:v', 'libx264', '-vf', f'fps={fps},format=yuv420p'],
52 f'video_from_image:{img_path}:{duration}:{fps}.mkv' if cache else None
56 def cut_video(video_path, duration):
57 return process_to_file(
58 ['ffmpeg', '-y', '-i', video_path, '-t', str(duration), '-c', 'copy'],
64 def ffmpeg_concat(paths, suffix, copy=False):
65 filelist = NamedTemporaryFile(
66 prefix='concat-', suffix='.txt',
67 dir=settings.FILE_UPLOAD_TEMP_DIR
70 filelist.write(f"file '{path}'\n".encode('utf-8'))
73 args = ['ffmpeg', '-y', '-safe', '0', '-f', 'concat', '-i', filelist.name]
75 args += ['-c', 'copy']
76 outname = process_to_file(args, 'concat-', suffix)
82 def concat_videos(paths):
83 return ffmpeg_concat(paths, '.mkv', copy=True)
86 def concat_audio(paths):
87 return ffmpeg_concat(paths, '.flac')
90 def standardize_audio(p, cache=True):
91 return process_to_file(
92 ['ffmpeg', '-y', '-i', p, '-sample_fmt', 's16', '-acodec', 'flac', '-ac', '2', '-ar', '44100'],
93 'standardize-', '.flac',
94 f'standardize_audio:{p}.flac' if cache else None
98 def standardize_video(p, cache=True):
99 return process_to_file(
100 ['ffmpeg', '-y', '-i', p],
101 'standardize-', '.mkv',
102 f'standardize_video:{p}.mkv' if cache else None
106 def mux(channels, output_path=None):
107 args = ['ffmpeg', '-y']
109 args.extend(['-i', c])
110 args.extend(['-c', 'copy'])
111 return process_to_file(args, 'mux-', '.mkv', output_path=output_path)
114 def get_duration(path):
135 def get_framerate(path):
136 rates = subprocess.run(
142 "stream=r_frame_rate",
151 ).stdout.strip().split('\n')
153 a, b = rate.split('/')