Created
December 13, 2024 17:44
-
-
Save turicas/7b1ed8982da59d8db71033b94d47c1c3 to your computer and use it in GitHub Desktop.
Python script with Tkinter interface to extract clip/timeline data from Kdenlive project file and create a "cue sheet" (no external dependencies)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import csv | |
import os | |
import tkinter as tk | |
import xml.etree.ElementTree as ET | |
from pathlib import Path | |
from tkinter import filedialog, messagebox | |
def get_properties(element): | |
props = {} | |
for prop in element.findall("property"): | |
props[prop.get("name")] = prop.text if prop.text is not None else "" | |
return props | |
def parse_timecode(tc): | |
return tc | |
def infer_file_type(filepath, props): | |
stream_type = props.get("meta.media.0.stream.type", "").lower() | |
if stream_type in ["video", "audio", "image"]: | |
return stream_type | |
ext = os.path.splitext(filepath)[1].lower() | |
if ext in [".jpg", ".jpeg", ".png", ".tif", ".tiff", ".bmp"]: | |
return "image" | |
elif ext in [".mp4", ".mov", ".mkv", ".avi", ".mxf", ".wmv"]: | |
return "video" | |
elif ext in [".wav", ".mp3", ".aac", ".flac", ".ogg", ".m4a"]: | |
return "audio" | |
return "unknown" | |
def parse_kdenlive_file(filename: Path): | |
tree = ET.parse(filename) | |
root = tree.getroot() | |
id_to_props = {} | |
for elem in root.findall("./chain"): | |
cid = elem.get("id") | |
if cid: | |
id_to_props[cid] = get_properties(elem) | |
for elem in root.findall("./producer"): | |
pid = elem.get("id") | |
if pid: | |
id_to_props[pid] = get_properties(elem) | |
entries_info = [] | |
for playlist in root.findall("./playlist"): | |
for entry in playlist.findall("./entry"): | |
producer_id = entry.get("producer") | |
if not producer_id: | |
continue | |
in_time = entry.get("in", "00:00:00.000") | |
out_time = entry.get("out", "00:00:00.000") | |
if producer_id in id_to_props: | |
props = id_to_props[producer_id] | |
filepath = props.get("kdenlive:originalurl", props.get("resource", "")) | |
if not os.path.isabs(filepath): | |
project_root = root.get("root", "") | |
if project_root: | |
filepath = os.path.join(project_root, filepath) | |
else: | |
filepath = filename.parent / filepath | |
filetype = infer_file_type(filepath, props) | |
entries_info.append( | |
{ | |
"full_filename": str(filepath), | |
"name": Path(filepath).name, | |
"file_type": filetype, | |
"start": in_time, | |
"end": out_time, | |
} | |
) | |
return entries_info | |
def run_extraction(kdenlive_file, csv_file): | |
entries = parse_kdenlive_file(Path(kdenlive_file)) | |
with open(csv_file, "w", newline="", encoding="utf-8") as fobj: | |
writer = None | |
for entry in entries: | |
if writer is None: | |
writer = csv.DictWriter(fobj, fieldnames=list(entry.keys())) | |
writer.writeheader() | |
writer.writerow(entry) | |
messagebox.showinfo("Concluído", f"O arquivo CSV foi gerado em:\n{csv_file}") | |
def select_kdenlive(): | |
filename = filedialog.askopenfilename( | |
title="Selecione o arquivo .kdenlive", | |
filetypes=[("Kdenlive Projects", "*.kdenlive"), ("All files", "*.*")] | |
) | |
if filename: | |
kdenlive_var.set(filename) | |
def select_csv(): | |
filename = filedialog.asksaveasfilename( | |
title="Selecione o arquivo CSV de saída", | |
defaultextension=".csv", | |
filetypes=[("CSV Files", "*.csv"), ("All files", "*.*")] | |
) | |
if filename: | |
csv_var.set(filename) | |
def execute(): | |
kdenlive_file = kdenlive_var.get() | |
csv_file = csv_var.get() | |
if not kdenlive_file or not os.path.exists(kdenlive_file): | |
messagebox.showerror("Erro", "Por favor, selecione um arquivo .kdenlive válido.") | |
return | |
if not csv_file: | |
messagebox.showerror("Erro", "Por favor, selecione o arquivo CSV de saída.") | |
return | |
run_extraction(kdenlive_file, csv_file) | |
root = tk.Tk() | |
root.title("Extrair informações do Kdenlive") | |
kdenlive_var = tk.StringVar() | |
csv_var = tk.StringVar() | |
frame = tk.Frame(root, padx=10, pady=10) | |
frame.pack() | |
tk.Label( | |
frame, | |
text=( | |
"Este programa lê um arquivo de projeto do Kdenlive, identifica tudo que está na timeline \n" | |
"e cria uma planilha 'cue sheet' com as seguintes informações das mídias presentes:\n" | |
"- Nome completo do arquivo\n" | |
"- Nome do arquivo\n" | |
"- Tipo (áudio, vídeo, imagem)\n" | |
"- Horário de início na timeline\n" | |
"- Horário de fim na timeline" | |
), | |
font=("Helvetica", 20) | |
).grid(row=0, column=0, columnspan=3, pady=(0, 10)) | |
tk.Label(frame, text="Arquivo .kdenlive:").grid(row=1, column=0, sticky="e") | |
tk.Entry(frame, textvariable=kdenlive_var, width=50).grid(row=1, column=1, padx=5, pady=5) | |
tk.Button(frame, text="Selecionar...", command=select_kdenlive).grid(row=1, column=2, padx=5, pady=5) | |
tk.Label(frame, text="Arquivo CSV de saída:").grid(row=2, column=0, sticky="e") | |
tk.Entry(frame, textvariable=csv_var, width=50).grid(row=2, column=1, padx=5, pady=5) | |
tk.Button(frame, text="Selecionar...", command=select_csv).grid(row=2, column=2, padx=5, pady=5) | |
tk.Button(frame, text="Executar", command=execute, bg="#5f5", width=20).grid(row=3, column=1, pady=10) | |
root.mainloop() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment