#!/usr/bin/python3
from collections import defaultdict
import subprocess
import lxml.etree
import uuid
import os

_CACHE = {}
_IDS = defaultdict(int)

def get_propery(element, name):
    return element.xpath('property[@name="%s"]' % name)[0].text


def melt_xml(file):
    if file in _CACHE:
        out = _CACHE[file]
    else:
        out = _CACHE[file] = subprocess.check_output(['melt', file, '-consumer', 'xml']).decode()
    return out


class KDEnliveProject:

    def to_xml(self):
        track = self._main_tractor.xpath(".//track")[0]
        duration = self.get_duration()
        values = {
            "in": "0",
            "out": str(duration - 1)
        }
        for key, value in values.items():
            track.attrib[key] = value
            self._sequence.attrib[key] = value
            self._main_tractor.attrib[key] = value
            self._audio_tractor.attrib[key] = value

        self._tree.remove(self._sequence)
        self._tree.append(self._sequence)
        self._tree.remove(self._main_bin)
        self._tree.append(self._main_bin)
        self._tree.remove(self._main_tractor)
        self._tree.append(self._main_tractor)

        xml = lxml.etree.tostring(self._tree, pretty_print=True).decode()
        xml = xml.replace('><', '>\n<')
        return "<?xml version='1.0' encoding='utf-8'?>\n" + xml

    def __init__(
        self, root,
        width="1920", height="1080",
        display_aspect_num="16", display_aspect_den="9",
        frame_rate_num="24", frame_rate_den="1"
    ):
        self._duration = defaultdict(int)
        self._counters = defaultdict(int)
        self._uuid = '{%s}' % str(uuid.uuid1())
        self._width = int(width)
        self._height = int(height)
        self._fps = int(frame_rate_num) / int(frame_rate_den)

        self._tree = self.get_element("mlt", attrib={
            "LC_NUMERIC": "C",
            "producer": "main_bin",
            "version": "7.18.0",
            "root": root
        }, children=[
            self.get_element("profile", attrib={
                "frame_rate_num": str(frame_rate_num),
                "frame_rate_den": str(frame_rate_den),
                "display_aspect_den": str(display_aspect_den),
                "display_aspect_num": str(display_aspect_num),
                "colorspace": "601",
                "progressive": "1",
                "description": "%sx%s %0.2ffps" % (self._width, self._height, self._fps),
                "width": str(width),
                "height": str(height),
                "sample_aspect_num": "1",
                "sample_aspect_den": "1"
            }),
            p0 := self.get_element("producer", attrib={
                "in": "0",
                "out": "2147483647"
            }, children=[
                ["length", "2147483647"],
                ["eof", "continue"],
                ["resource", "black"],
                ["aspect_ratio", "1"],
                ["mlt_service", "color"],
                ["kdenlive:playlistid", "black_track"],
                ["mlt_image_format", "rgba"],
                ["set.test_audio", "0"],
            ]),
            a4 := self.get_element("playlist", children=[
                ["kdenlive:audio_track", "1"],
            ]),
            a4e := self.get_element("playlist", children=[
                ["kdenlive:audio_track", "1"],
            ]),
            t_a4 := self.get_element("tractor", children=[
                ["kdenlive:audio_track", "1"],
                ["kdenlive:trackheight", "69"],
                ["kdenlive:timeline_active", "1"],
                ["kdenlive:collapsed", "0"],
                ["kdenlive:thumbs_format", None],
                ["kdenlive:audio_rec", None],
                self.get_element("track", attrib={"hide": "video", "producer": a4.attrib["id"]}),
                self.get_element("track", attrib={"hide": "video", "producer": a4e.attrib["id"]}),
                self.get_element("filter", [
                    ["window", "75"],
                    ["max_gain", "20dB"],
                    ["mlt_service", "volume"],
                    ["internal_added", "237"],
                    ["disable", "1"],
                ]),
                self.get_element("filter", [
                    ["channel", "-1"],
                    ["mlt_service", "panner"],
                    ["internal_added", "237"],
                    ["start", "0.5"],
                    ["disable", "1"],
                ]),
                self.get_element("filter", [
                    ["iec_scale", "0"],
                    ["mlt_service", "audiolevel"],
                    ["dbpeak", "1"],
                    ["disable", "1"],
                ]),
            ]),
            a3 := self.get_element("playlist", children=[
                ["kdenlive:audio_track", "1"],
            ]),
            a3e := self.get_element("playlist", children=[
                ["kdenlive:audio_track", "1"],
            ]),
            t_a3 := self.get_element("tractor", children=[
                ["kdenlive:audio_track", "1"],
                ["kdenlive:trackheight", "69"],
                ["kdenlive:timeline_active", "1"],
                ["kdenlive:collapsed", "0"],
                ["kdenlive:thumbs_format", None],
                ["kdenlive:audio_rec", None],
                self.get_element("track", attrib={"hide": "video", "producer": a3.attrib["id"]}),
                self.get_element("track", attrib={"hide": "video", "producer": a3e.attrib["id"]}),
                self.get_element("filter", [
                    ["window", "75"],
                    ["max_gain", "20dB"],
                    ["mlt_service", "volume"],
                    ["internal_added", "237"],
                    ["disable", "1"],
                ]),
                self.get_element("filter", [
                    ["channel", "-1"],
                    ["mlt_service", "panner"],
                    ["internal_added", "237"],
                    ["start", "0.5"],
                    ["disable", "1"],
                ]),
                self.get_element("filter", [
                    ["iec_scale", "0"],
                    ["mlt_service", "audiolevel"],
                    ["dbpeak", "1"],
                    ["disable", "1"],
                ]),
            ]),
            a2 := self.get_element("playlist", children=[
                ["kdenlive:audio_track", "1"],
            ]),
            a2e := self.get_element("playlist", children=[
                ["kdenlive:audio_track", "1"],
            ]),
            t_a2 := self.get_element("tractor", children=[
                ["kdenlive:audio_track", "1"],
                ["kdenlive:trackheight", "69"],
                ["kdenlive:timeline_active", "1"],
                ["kdenlive:collapsed", "0"],
                ["kdenlive:thumbs_format", None],
                ["kdenlive:audio_rec", None],
                self.get_element("track", attrib={"hide": "video", "producer": a2.attrib["id"]}),
                self.get_element("track", attrib={"hide": "video", "producer": a2e.attrib["id"]}),
                self.get_element("filter", [
                    ["window", "75"],
                    ["max_gain", "20dB"],
                    ["mlt_service", "volume"],
                    ["internal_added", "237"],
                    ["disable", "1"],
                ]),
                self.get_element("filter", [
                    ["channel", "-1"],
                    ["mlt_service", "panner"],
                    ["internal_added", "237"],
                    ["start", "0.5"],
                    ["disable", "1"],
                ]),
                self.get_element("filter", [
                    ["iec_scale", "0"],
                    ["mlt_service", "audiolevel"],
                    ["dbpeak", "1"],
                    ["disable", "1"],
                ]),
            ]),
            a1 := self.get_element("playlist", children=[
                ["kdenlive:audio_track", "1"],
            ]),
            a1e := self.get_element("playlist", children=[
                ["kdenlive:audio_track", "1"],
            ]),
            t_a1 := self.get_element("tractor", children=[
                ["kdenlive:audio_track", "1"],
                ["kdenlive:trackheight", "69"],
                ["kdenlive:timeline_active", "1"],
                ["kdenlive:collapsed", "0"],
                ["kdenlive:thumbs_format", None],
                ["kdenlive:audio_rec", None],
                self.get_element("track", attrib={"hide": "video", "producer": a1.attrib["id"]}),
                self.get_element("track", attrib={"hide": "video", "producer": a1e.attrib["id"]}),
                self.get_element("filter", [
                    ["window", "75"],
                    ["max_gain", "20dB"],
                    ["mlt_service", "volume"],
                    ["internal_added", "237"],
                    ["disable", "1"],
                ]),
                self.get_element("filter", [
                    ["channel", "-1"],
                    ["mlt_service", "panner"],
                    ["internal_added", "237"],
                    ["start", "0.5"],
                    ["disable", "1"],
                ]),
                self.get_element("filter", [
                    ["iec_scale", "0"],
                    ["mlt_service", "audiolevel"],
                    ["dbpeak", "1"],
                    ["disable", "1"],
                ]),
            ]),
            v1 := self.get_element("playlist", children=[
            ]),
            v1e := self.get_element("playlist", children=[
            ]),
            t2 := self.get_element("tractor", attrib={
                "in": "00:00:00.000",
                "out": "00:00:25.333"
            }, children=[
                ["kdenlive:trackheight", "69"],
                ["kdenlive:timeline_active", "1"],
                ["kdenlive:collapsed", "0"],
                ["kdenlive:thumbs_format", None],
                ["kdenlive:audio_rec", None],
                ["kdenlive:locked_track", None],
                self.get_element("track", attrib={"hide": "audio", "producer": v1.attrib["id"]}),
                self.get_element("track", attrib={"hide": "audio", "producer": v1e.attrib["id"]}),
            ]),
            v2 := self.get_element("playlist", children=[
            ]),
            v2e := self.get_element("playlist", children=[
            ]),
            t3 := self.get_element("tractor", attrib={
                "in": "00:00:00.000"
            }, children=[
                ["kdenlive:trackheight", "69"],
                ["kdenlive:timeline_active", "1"],
                ["kdenlive:collapsed", "0"],
                ["kdenlive:thumbs_format", None],
                ["kdenlive:audio_rec", None],
                ["kdenlive:locked_track", None],
                self.get_element("track", attrib={"hide": "audio", "producer": v2.attrib["id"]}),
                self.get_element("track", attrib={"hide": "audio", "producer": v2e.attrib["id"]}),
            ]),
            sequence := self.get_element("tractor", [
                ["kdenlive:uuid", self._uuid],
                ["kdenlive:clipname", "Sequence 1"],
                ["kdenlive:sequenceproperties.hasAudio", "1"],
                ["kdenlive:sequenceproperties.hasVideo", "1"],
                ["kdenlive:sequenceproperties.activeTrack", "2"],
                ["kdenlive:sequenceproperties.tracksCount", "4"],
                ["kdenlive:sequenceproperties.documentuuid", self._uuid],
                ["kdenlive:duration", "00:00:25:09"],
                ["kdenlive:maxduration", "872"],
                ["kdenlive:producer_type", "17"],
                ["kdenlive:id", self.get_counter("kdenlive:id")],
                ["kdenlive:clip_type", "0"],
                ["kdenlive:folderid", "2"],
                ["kdenlive:sequenceproperties.audioChannels", "2"],
                ["kdenlive:sequenceproperties.audioTarget", "1"],
                ["kdenlive:sequenceproperties.tracks", "4"],
                ["kdenlive:sequenceproperties.verticalzoom", "1"],
                ["kdenlive:sequenceproperties.videoTarget", "2"],
                ["kdenlive:sequenceproperties.zonein", "0"],
                ["kdenlive:sequenceproperties.zoneout", "75"],
                ["kdenlive:sequenceproperties.zoom", "8"],
                ["kdenlive:sequenceproperties.groups", "[]"],
                ["kdenlive:sequenceproperties.guides", "[]"],
                ["kdenlive:sequenceproperties.position", "0"],
                ["kdenlive:sequenceproperties.scrollPos", "0"],
                ["kdenlive:sequenceproperties.disablepreview", "0"],

                self.get_element("track", attrib={"producer": p0.attrib["id"]}),
                self.get_element("track", attrib={"producer": t_a4.attrib["id"]}),
                self.get_element("track", attrib={"producer": t_a3.attrib["id"]}),
                self.get_element("track", attrib={"producer": t_a2.attrib["id"]}),
                self.get_element("track", attrib={"producer": t_a1.attrib["id"]}),
                self.get_element("track", attrib={"producer": t2.attrib["id"]}),
                self.get_element("track", attrib={"producer": t3.attrib["id"]}),
                self.get_element("transition", [
                    ["a_track", "0"],
                    ["b_track", "1"],
                    ["mlt_service", "mix"],
                    ["kdenlive_id", "mix"],
                    ["internal_added", "237"],
                    ["always_active", "1"],
                    ["accepts_blanks", "1"],
                    ["sum", "1"],
                ]),
                self.get_element("transition", [
                    ["a_track", "0"],
                    ["b_track", "2"],
                    ["mlt_service", "mix"],
                    ["kdenlive_id", "mix"],
                    ["internal_added", "237"],
                    ["always_active", "1"],
                    ["accepts_blanks", "1"],
                    ["sum", "1"],
                ]),
                self.get_element("transition", [
                    ["a_track", "0"],
                    ["b_track", "3"],
                    ["mlt_service", "mix"],
                    ["kdenlive_id", "mix"],
                    ["internal_added", "237"],
                    ["always_active", "1"],
                    ["accepts_blanks", "1"],
                    ["sum", "1"],
                ]),
                self.get_element("transition", [
                    ["a_track", "0"],
                    ["b_track", "4"],
                    ["mlt_service", "mix"],
                    ["kdenlive_id", "mix"],
                    ["internal_added", "237"],
                    ["always_active", "1"],
                    ["accepts_blanks", "1"],
                    ["sum", "1"],
                ]),
                self.get_element("transition", [
                    ["a_track", "0"],
                    ["b_track", "5"],
                    ["compositing", "0"],
                    ["distort", "0"],
                    ["rotate_center", "0"],
                    ["mlt_service", "qtblend"],
                    ["kdenlive_id", "qtblend"],
                    ["internal_added", "237"],
                    ["always_active", "1"],
                    ["accepts_blanks", "1"],
                    ["sum", "1"],
                ]),
                self.get_element("transition", [
                    ["a_track", "0"],
                    ["b_track", "6"],
                    ["compositing", "0"],
                    ["distort", "0"],
                    ["rotate_center", "0"],
                    ["mlt_service", "qtblend"],
                    ["kdenlive_id", "qtblend"],
                    ["internal_added", "237"],
                    ["always_active", "1"],
                    ["accepts_blanks", "1"],
                    ["sum", "1"],
                ]),
                self.get_element("filter", [
                    ["window", "75"],
                    ["max_gain", "20dB"],
                    ["mlt_service", "volume"],
                    ["internal_added", "237"],
                    ["disable", "1"],
                ]),
                self.get_element("filter", [
                    ["channel", "-1"],
                    ["mlt_service", "panner"],
                    ["internal_added", "237"],
                    ["start", "0.5"],
                    ["disable", "1"],
                ]),
            ], {
                "id": self._uuid
            }),
            main_bin := self.get_element("playlist", [
              ["kdenlive:folder.-1.2", "Sequences"],
              ["kdenlive:sequenceFolder", "2"],
              ["kdenlive:docproperties.kdenliveversion", "23.08.0"],
              self.get_element("property", attrib={"name": "kdenlive:docproperties.previewextension"}),
              self.get_element("property", attrib={"name": "kdenlive:docproperties.previewparameters"}),
              ["kdenlive:docproperties.seekOffset", "30000"],
              ["kdenlive:docproperties.uuid", self._uuid],
              ["kdenlive:docproperties.version", "1.1"],
              ["kdenlive:expandedFolders", None],
              ["kdenlive:binZoom", "4"],
              self.get_element("property", attrib={"name": "kdenlive:documentnotes"}),
              ["kdenlive:docproperties.opensequences", self._uuid],
              ["kdenlive:docproperties.activetimeline", self._uuid],
              ["xml_retain", "1"],
              self.get_element("entry", attrib={"producer": self._uuid, "in": "0", "out": "0"}),
            ], {
                "id": "main_bin"
            }),
            t4 := self.get_element("tractor", [
                ["kdenlive:projectTractor", "1"],
                self.get_element("track", attrib={"producer": self._uuid}),
            ])
        ])
        self._sequence = sequence
        self._main_bin = main_bin
        self._main_tractor = t4
        self._audio_tractor = t_a1
        self._v1 = v1
        self._v2 = v2
        self._a1 = a1
        self._a2 = a2
        self._a3 = a3
        self._a4 = a4

    def get_counter(self, prefix):
        self._counters[prefix] += 1
        return str(self._counters[prefix] - 1)

    def get_id(self, prefix):
        return prefix + self.get_counter(prefix)

    def get_chain(self, file, kdenlive_id=None):
        out = melt_xml(file)
        chain = lxml.etree.fromstring(out).xpath('producer')[0]
        chain.tag = 'chain'
        chain.attrib['id'] = self.get_id('chain')
        # TBD
        if kdenlive_id is None:
            kdenlive_id = self.get_counter("kdenlive:id")
        for name, value in [
            ("kdenlive:file_size", os.path.getsize(file)),
            ("kdenlive:clipname", None),
            ("kdenlive:clip_type", "0"),
            ("kdenlive:folderid", "-1"),
            ("kdenlive:id", kdenlive_id),
            ("set.test_audio", "0"),
            ("set.test_image", "0"),
            ("xml", "was here"),
        ]:
            chain.append(
                self.get_element(
                    "property",
                    attrib={"name": name},
                    text=str(value) if value is not None else None
                )
            )
        mlt_service = chain.xpath('property[@name="mlt_service"]')[0]
        mlt_service.text = "avformat-novalidate"
        return chain

    def get_duration(self):
        if not self._duration:
            return 0
        return max(self._duration.values())

    def get_element(self, tag, children=[], attrib={}, text=None):
        element = lxml.etree.Element(tag)
        if tag not in (
            "blank",
            "entry",
            "mlt",
            "profile",
            "property",
            "track",
        ) and "id" not in attrib:
            element.attrib['id'] = self.get_id(tag)
        if attrib:
            for key, value in attrib.items():
                element.attrib[key] = value
        for child in children:
            if isinstance(child, list) and len(child) == 2:
                v = child[1]
                if v is not None:
                    v = str(v)
                child = self.get_element("property", attrib={"name": child[0]}, text=v)
            if isinstance(child, dict):
                child = self.get_element(**child)
            elif isinstance(child, list):
                child = self.get_element(*child)
            element.append(child)
        if text is not None:
            element.text = text
        return element

    def get_filter(self, name, value):
        if name == "transparency":
            return [self.get_element("filter", [
                ["version", "0.9"],
                ["mlt_service", "frei0r.transparency"],
                ["kdenlive_id", "frei0r.transparency"],
                ["0", "00:00:00.000=%s" % value],
                ["kdenlive:collapsed", "0"],
            ])]
        elif name == "blur":
            return [self.get_element("filter", [
                ["mlt_service", "avfilter.avgblur"],
                ["kdenlive_id", "avfilter.avgblur"],
                ["av.sizeX", value],
                ["av.sizeY", value],
                ["planes", "7"],
                ["kdenlive:collapsed", "0"],
            ])]
        elif name == "mask":
            mask = [
                self.get_element("filter", [
                    ["mlt_service", "frei0r.saturat0r"],
                    ["kdenlive_id", "frei0r.saturat0r"],
                    ["Saturation", "00:00:00.000=0.001"],
                ]),
                self.get_element("filter", [
                    ["mlt_service", "frei0r.select0r"],
                    ["kdenlive_id", "frei0r.select0r"],
                    ["Color to select", "00:00:00.000=0x000000ff"],
                    ["Invert selection", "1"],
                    ["Selection subspace", "0"],
                    ["Subspace shape", "0.5"],
                    ["Edge mode", "0.9"],
                    ["Delta R / A / Hue", "00:00:00.000=0.381"],
                    ["Delta G / B / Chroma", "00:00:00.000=0.772"],
                    ["Delta B / I / I", "00:00:00.000=0.522"],
                    ["Slope", "00:00:00.000=0.515"],
                    ["Operation", "0.5"],
                ])
            ]
            return mask
        elif name == "volume":
            return [self.get_element("filter", [
                ["window", "75"],
                ["max_gain", "20db"],
                ["mlt_service", "volume"],
                ["kdenlive_id", "volume"],
                ["level", "00:00:00.000=%s" % value],
                ["kdenlive:collapsed", "0"],
            ])]
        else:
            return [
                self.get_element("filter", [
                    ["mlt_service", name],
                    ["kdenlive_id", name],
                ] + value)
            ]


    def properties(self, *props):
        return [
            self.get_element("property", attrib={"name": name}, text=str(value) if value is not None else value)
            for name, value in props
        ]


    def append_clip(self, track_id, clip):
        if track_id == "V1":
            track = self._v1
        elif track_id == "V2":
            track = self._v2
        elif track_id == "A1":
            track = self._a1
        elif track_id == "A2":
            track = self._a2
        elif track_id == "A3":
            track = self._a3
        elif track_id == "A4":
            track = self._a4
        else:
            print('!!', track_id)

        frames = int(self._fps * clip['duration'])
        self._duration[track_id] += frames

        if clip.get("blank"):
            track.append(
                self.get_element("blank", attrib={
                    "length": str(frames),
                })
            )
            return

        path = clip['src']
        filters = clip.get("filter", {})
        #print(path, filters)
        chain = self.get_chain(path)
        id = get_propery(chain, "kdenlive:id")

        if track_id[0] == 'A':
            has_audio = False
            for prop in chain.xpath('property'):
                if prop.attrib['name'].endswith('stream.type') and prop.text == "audio":
                    has_audio = True
        idx = self._tree.index(track) - 1
        self._tree.insert(idx, chain)
        filters_ = []
        if track_id == 'V':
            filters_.append({
                self.get_element("filter", [
                    ["mlt_service", "qtblend"],
                    ["kdenlive_id", "qtblend"],
                    ["rotate_center", "1"],
                    ["rect", "00:00:00.000=0 0 %s %s 1.000000" % (self._width, self.height)],
                    ["rotation", "00:00:00.000=0"],
                    ["compositing", "0"],
                    ["distort", "0"],
                    ["kdenlive:collapsed", "0"],
                    ["disable", "0"],
                ])
            })

        for ft in filters.items():
            filters_ += self.get_filter(*ft)
        if track_id[0] == 'A' and not has_audio:
            track.append(
                self.get_element("blank", attrib={
                    "length": str(frames),
                })
            )
        else:
            track.append(
                self.get_element("entry", attrib={
                    "producer": chain.attrib["id"],
                    "in": chain.attrib["in"],
                    "out": str(frames - 1)
                }, children=[
                    ["kdenlive:id", id],
                ] + filters_),
            )
            chain = self.get_chain(path, id)
            self._tree.append(chain)
            self._main_bin.append(
                self.get_element("entry", attrib={
                    "producer": chain.attrib["id"],
                    "in": chain.attrib["in"],
                    "out": chain.attrib["out"],
                }),
            )