annotate create.py @ 136:da4f7200665f default tip

buncha shit
author Paper <paper@tflc.us>
date Sat, 07 Mar 2026 18:04:10 -0500
parents 0d8eabdd12ab
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
1 #!/usr/bin/env python3
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
2 # Simple python script to create a video from a bunch of wav files
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
3 # and an image. Also creates a cue-like track listing that contains
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
4 # track numbers etc.
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
5
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
6 import argparse
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
7 import json
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
8 import subprocess
136
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
9 import os
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
10 import math #ceil
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
11 import typing
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
12
133
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
13 def build_vf(blur) -> list[str]:
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
14 if blur:
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
15 return ["-filter_complex", "[0:v]split=2[bg][fg];[bg]scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080,boxblur=luma_radius=10:chroma_radius=10[bg_final];[fg]scale=1920:1080:force_original_aspect_ratio=decrease[fg_final];[bg_final][fg_final]overlay=(W-w)/2:(H-h)/2"]
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
16
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
17 return ["-vf", "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2"]
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
18
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
19 # Build filter string
136
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
20 def build_filter_string(length: int, offset: int) -> str:
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
21 f = ""
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
22 i = offset
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
23 for x in range(length):
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
24 f += ("[%d:0]" % (x + i))
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
25 f += ("concat=n=%d:v=0:a=1[out]" % length)
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
26 return f
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
27
136
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
28 def build_arguments_list(audio_files: list[str], cover: typing.Optional[str], output: str, blur: bool, duration: float) -> list:
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
29 args = ["ffmpeg", "-y"]
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
30 # I want you to play that song again
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
31 if not cover is None:
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
32 args.extend(["-r", "1", "-loop", "1", "-i", cover])
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
33 for f in audio_files:
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
34 args.extend(["-i", f])
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
35 args.extend(["-filter_complex", build_filter_string(len(audio_files), 1 if cover else 0), "-map", "[out]"])
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
36 if not cover is None:
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
37 # Letterbox to 1920x1080
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
38 args.extend(["-map", "0:v",
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
39 "-tune", "stillimage",
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
40 "-c:v", "libx264",
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
41 # Add a second to account for inconsistencies in durations
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
42 "-t", "%f" % (duration + 1.0)])
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
43 args.extend(build_vf(blur))
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
44 args.append(output + ".mkv")
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
45 return args
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
46
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
47
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
48 # Returns a Popen object that should be waited on
136
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
49 def run_ffmpeg(audio_files: list[str], cover: typing.Optional[str], output: str, blur: bool, duration: float) -> subprocess.Popen:
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
50 args = build_arguments_list(audio_files, cover, output, blur, duration)
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
51 print(args)
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
52 return subprocess.Popen(args)
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
53
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
54
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
55 # Returns a Popen object that should be waited on.
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
56 # stdout is also piped...
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
57 def run_ffprobe(audio_file: str) -> subprocess.Popen:
136
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
58 return subprocess.Popen(["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", audio_file], stdout=subprocess.PIPE, text=True)
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
59
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
60
136
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
61 def get_title_from_tags(j: list, default: str) -> str:
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
62 # Is there an easier way to do this...?
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
63 for tag in j:
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
64 if tag.lower() == "title":
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
65 return j[tag]
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
66
136
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
67 # Ugh
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
68 return default
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
69
133
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
70 def doit(audio_files: list[str], cover, output, blur) -> int:
136
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
71 dur = 0.0
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
72
136
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
73 ffprobes = [run_ffprobe(x) for x in audio_files]
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
74
136
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
75 # Create output txt file with timestamps; we also use this duration
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
76 # to force ffmpeg to do the right thing instead of elongating our
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
77 # video
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
78 with open(output + ".txt", "w", encoding="utf-8") as fw:
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
79 for f in ffprobes:
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
80 f.wait()
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
81 j = json.load(f.stdout)
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
82 fw.write("%d:%02d:%02d %s\n" % (int(dur) // 3600, int(dur) % 3600 // 60, int(dur) % 60, get_title_from_tags(j["format"]["tags"], "OOPS....")))
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
83 dur += float(j["format"]["duration"])
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
84
136
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
85 ffmpeg = run_ffmpeg(audio_files, cover, output, blur, dur)
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
86
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
87 # Finally, wait on the big ffmpeg process
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
88 ffmpeg.wait()
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
89
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
90 def main() -> int:
136
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
91 parser = argparse.ArgumentParser(prog='create', description='Creates album videos', epilog='(c) paper 2025 all rights reversed')
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
92 parser.add_argument('-c', '--cover', required=True)
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
93 parser.add_argument('-o', '--output', required=True)
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
94 parser.add_argument('-b', '--blur', action='store_true')
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
95 parser.add_argument('audio', action='extend', nargs='+', type=str)
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
96 args = parser.parse_args()
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
97 doit(args.audio, args.cover, args.output, args.blur)
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
98
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
99 if __name__ == "__main__":
136
da4f7200665f buncha shit
Paper <paper@tflc.us>
parents: 133
diff changeset
100 exit(main())