Untitled
unknown
python
2 years ago
5.7 kB
4
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...