Untitled

mail@pastecode.io avatar
unknown
python
7 months ago
5.7 kB
2
Indexable
Never
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()