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(prefix=prefix, suffix=suffix, delete=False)
 
  28         output_path = tmp.name
 
  31         cache_path = FILE_CACHE + cache_key.replace('/', '__')
 
  33     if cache_key and os.path.exists(cache_path):
 
  34         link_or_copy(cache_path, output_path)
 
  36         # Actually run the processing.
 
  37         subprocess.run(cmdline + [output_path], check=True)
 
  39             link_or_copy(output_path, cache_path)
 
  44 def video_from_image(img_path, duration, fps=25, cache=True):
 
  45     return process_to_file(
 
  46         ['ffmpeg', '-y', '-loop', '1', '-t', str(duration), '-i', img_path, '-c:v', 'libx264', '-vf', f'fps={fps},format=yuv420p'],
 
  49         f'video_from_image:{img_path}:{duration}:{fps}.mkv' if cache else None
 
  53 def cut_video(video_path, duration):
 
  54     return process_to_file(
 
  55         ['ffmpeg', '-y', '-i', video_path, '-t', str(duration)],
 
  61 def ffmpeg_concat(paths, suffix, copy=False):
 
  62     filelist = NamedTemporaryFile(prefix='concat-', suffix='.txt')
 
  64         filelist.write(f"file '{path}'\n".encode('utf-8'))
 
  67     args = ['ffmpeg', '-y', '-safe', '0', '-f', 'concat', '-i', filelist.name]
 
  69         args += ['-c', 'copy']
 
  70     outname = process_to_file(args, 'concat-', suffix)
 
  76 def concat_videos(paths):
 
  77     return ffmpeg_concat(paths, '.mkv', copy=True)
 
  80 def concat_audio(paths):
 
  81     return ffmpeg_concat(paths, '.flac')
 
  84 def standardize_audio(p, cache=True):
 
  85     return process_to_file(
 
  86         ['ffmpeg', '-y', '-i', p, '-sample_fmt', 's16', '-acodec', 'flac', '-ac', '2', '-ar', '44100'],
 
  87         'standardize-', '.flac',
 
  88         f'standardize_audio:{p}.flac' if cache else None
 
  92 def standardize_video(p, cache=True):
 
  93     return process_to_file(
 
  94         ['ffmpeg', '-y', '-i', p],
 
  95         'standardize-', '.mkv',
 
  96         f'standardize_video:{p}.mkv' if cache else None
 
 100 def mux(channels, output_path=None):
 
 101     args = ['ffmpeg', '-y']
 
 103         args.extend(['-i', c])
 
 104     args.extend(['-c', 'copy'])
 
 105     return process_to_file(args, 'mux-', '.mkv', output_path=output_path)
 
 108 def get_duration(path):
 
 129 def get_framerate(path):
 
 130     rates = subprocess.run(
 
 136                 "stream=r_frame_rate",
 
 145         ).stdout.strip().split('\n')
 
 147         a, b = rate.split('/')