Untitled
unknown
python
2 years ago
5.7 kB
7
Indexable
from moviepy.editor import VideoFileClip, concatenate_videoclips
import yaml
from pprint import pprint
import os
from pytimeparse.timeparse import timeparse as _timeparse
import re
HEIGHT = 1080
def timeparse(inp: str) -> float:
if inp == "end":
return inp
if isinstance(inp, float):
return inp
inp = re.split(r"[:;,\s]", inp)
match len(inp):
case 1:
return _timeparse(f"{inp[0]} sec")
case 2:
return _timeparse(f"{inp[0]} min {inp[1]} sec")
case 3:
return _timeparse(f"{inp[0]} hr {inp[1]} min {inp[2]} sec")
case _:
raise "wtf"
class Edit:
def __new__(cls, inp: dict):
object_types = [
Trim,
Save,
Intro,
Outro,
]
for object_type in object_types:
if object_type._detect(inp):
return object.__new__(object_type)
raise ValueError("bruh")
def __init__(self, inp: dict):
self.start = None
def __str__(self):
return f"-{self.start}"
def __repr__(self):
return str(self)
Edits = list[Edit]
class Trim(Edit):
def __init__(self, inp: dict):
self.start = timeparse(inp.get("start"))
self.end = timeparse(inp.get("end"))
@classmethod
def _detect(cls, inp: dict) -> bool:
if "start" not in inp:
return False
if "end" not in inp:
return False
return True
def __str__(self):
return f"{self.start}-{self.end}"
class Save(Edit):
def __init__(self, inp: dict):
self.start = timeparse(inp.get("save"))
self.name = inp.get("name")
@classmethod
def _detect(cls, inp: dict) -> bool:
if "save" not in inp:
return False
return True
def __str__(self):
return "save"
class Intro(Edit):
clip = VideoFileClip("./intro.mp4").resize(height=HEIGHT)
def __init__(self, inp: dict):
self.start = timeparse(inp.get("intro"))
@classmethod
def _detect(cls, inp: dict) -> bool:
if "intro" not in inp:
return False
return True
def __str__(self):
return "intro"
class Outro(Edit):
clip = VideoFileClip("./outro.mp4").resize(height=HEIGHT)
def __init__(self, inp: dict):
self.start = timeparse(inp.get("outro"))
@classmethod
def _detect(cls, inp: dict) -> bool:
if "outro" not in inp:
return False
return True
def __str__(self):
return "outro"
class Video:
def __init__(self, path: str, inp: dict):
self.path = path
self.exists = os.path.exists(path)
if not self.exists:
return
self.name = os.path.splitext(os.path.basename(path))[0]
self.clip = VideoFileClip(path).resize(height=HEIGHT)
self.__dict__.update(
{
"mp4": False,
"edits": [],
}
)
bad_keys = set(inp.keys()) - set(self.__dict__.keys())
if len(bad_keys):
raise ValueError(" ".join([str(x) for x in bad_keys]))
self.edits: Edits = [Edit(x) for x in inp.pop("edits")]
self.__dict__.update(inp)
def edit(self):
if not self.exists:
print(f"Skipping {self.path}")
return
for episode_name, clip in self.fetch_episodes().items():
print(episode_name, clip)
os.makedirs('./output', exist_ok=True)
if not self.mp4:
clip.audio.write_audiofile(
f"./output/{self.name}.mp3",
fps=44100,
)
else:
clip.write_videofile(
f"./output/{self.name}.mp4",
temp_audiofile=f"./output/{self.name}.mp3",
remove_temp=False,
codec="libx264",
fps=60,
threads=4
)
def fetch_episodes(self) -> dict[VideoFileClip]:
pprint(self.__dict__)
clip = self.clip
episodes = {}
episode_subclips = []
line = 0
edits = (
self.edits
+ [Trim({"start": clip.duration, "end": clip.duration})]
)
for edit in edits:
if edit.start == "end":
edit.start = clip.duration
if hasattr(edit, "end"):
if edit.end == "end":
edit.end = clip.duration
if line < edit.start:
episode_subclips.append(clip.subclip(line, edit.start))
match edit:
case Intro() | Outro():
episode_subclips.append(edit.clip)
line = max(line, edit.start)
case Trim():
line = max(line, edit.end)
case Save():
if edit.name is None:
edit.name = self.name
if edit.name in episodes:
raise "Bad config"
episodes[edit.name] = concatenate_videoclips(
episode_subclips)
current_episode = []
line = max(line, edit.start)
case _:
raise "Okay bro you messed up"
return episodes
def main():
with open("edits.yaml", "r") as f:
data = yaml.safe_load(f)
try:
for path, video_meta in data.items():
Video(path, video_meta).edit()
except Exception:
pass
if __name__ == "__main__":
main()
Editor is loading...