5 from tempfile import NamedTemporaryFile
6 from django.conf import settings
9 FILE_CACHE = getattr(settings, 'FILE_CACHE', 'file_cache/')
12 def link_or_copy(src, dst):
13 dstdir = os.path.dirname(dst)
14 if not os.path.exists(dstdir):
16 if os.path.exists(dst):
18 # FIXME: tiny window here when the temp path is not taken.
22 shutil.copyfile(src, dst)
25 def process_to_file(cmdline, prefix='', suffix='', cache_key=None, output_path=None):
27 tmp = NamedTemporaryFile(
28 prefix=prefix, suffix=suffix, delete=False,
29 dir=settings.FILE_UPLOAD_TEMP_DIR
32 output_path = tmp.name
35 cache_path = cache_key.replace('/', '__')
36 if len(cache_path) > 255:
37 parts = cache_path.rsplit('.', 1)
40 limit -= len(parts[1]) + 1
41 cache_path = parts[0][:limit] + '.' + hashlib.sha1(cache_key.encode('utf-8')).hexdigest()[:8]
43 cache_path += '.' + parts[1]
44 cache_path = FILE_CACHE + cache_path
46 if cache_key and os.path.exists(cache_path):
47 link_or_copy(cache_path, output_path)
49 # Actually run the processing.
50 subprocess.run(cmdline + [output_path], check=True)
52 link_or_copy(output_path, cache_path)
57 def video_from_image(img_path, duration, fps=25, cache=True):
58 return process_to_file(
59 ['ffmpeg', '-y', '-loop', '1', '-t', str(duration), '-i', img_path, '-c:v', 'libx264', '-vf', f'fps={fps},format=yuv420p'],
62 f'video_from_image:{img_path}:{duration}:{fps}.mkv' if cache else None
66 def cut_video(video_path, duration):
67 return process_to_file(
68 ['ffmpeg', '-y', '-i', video_path, '-t', str(duration), '-c', 'copy'],
74 def ffmpeg_concat(paths, suffix, copy=False):
75 filelist = NamedTemporaryFile(
76 prefix='concat-', suffix='.txt',
77 dir=settings.FILE_UPLOAD_TEMP_DIR
80 filelist.write(f"file '{path}'\n".encode('utf-8'))
83 args = ['ffmpeg', '-y', '-safe', '0', '-f', 'concat', '-i', filelist.name]
85 args += ['-c', 'copy']
86 outname = process_to_file(args, 'concat-', suffix)
92 def concat_videos(paths):
93 return ffmpeg_concat(paths, '.mkv', copy=True)
96 def concat_audio(paths):
97 return ffmpeg_concat(paths, '.flac')
100 def standardize_audio(p, cache=True):
101 return process_to_file(
102 ['ffmpeg', '-y', '-i', p, '-sample_fmt', 's16', '-acodec', 'flac', '-ac', '2', '-ar', '44100'],
103 'standardize-', '.flac',
104 f'standardize_audio:{p}.flac' if cache else None
108 def standardize_video(p, cache=True):
109 return process_to_file(
110 ['ffmpeg', '-y', '-i', p],
111 'standardize-', '.mkv',
112 f'standardize_video:{p}.mkv' if cache else None
116 def mux(channels, output_path=None):
117 args = ['ffmpeg', '-y']
119 args.extend(['-i', c])
120 args.extend(['-c', 'copy'])
121 return process_to_file(args, 'mux-', '.mkv', output_path=output_path)
124 def get_duration(path):
145 def get_framerate(path):
146 rates = subprocess.run(
152 "stream=r_frame_rate",
161 ).stdout.strip().split('\n')
163 a, b = rate.split('/')