Initial project import
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
"""
|
||||
src/export/edl_writer.py — EditTimeline → CMX 3600 EDL
|
||||
|
||||
Generates a standard CMX 3600 Edit Decision List compatible with
|
||||
Avid, DaVinci Resolve, Premiere Pro, and most NLEs.
|
||||
|
||||
CMX 3600 format reference:
|
||||
https://en.wikipedia.org/wiki/Edit_decision_list#CMX_3600
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from src.core.config import AppConfig
|
||||
from src.core.models import EditClip, EditTimeline
|
||||
from src.export.timecode import seconds_to_smpte
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# EDL line builders
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _edl_header(title: str) -> str:
|
||||
return f"TITLE: {title}\nFCM: NON-DROP FRAME\n"
|
||||
|
||||
|
||||
def _edl_event(
|
||||
event_num: int,
|
||||
clip: EditClip,
|
||||
fps: float,
|
||||
) -> str:
|
||||
"""
|
||||
Build one CMX 3600 event block for a single clip.
|
||||
|
||||
Format:
|
||||
NNN AX V C <SRC_IN> <SRC_OUT> <REC_IN> <REC_OUT>
|
||||
* FROM CLIP NAME: ...
|
||||
* COMMENT: ...
|
||||
"""
|
||||
src_in = seconds_to_smpte(clip.match.in_point_s, fps)
|
||||
source_duration_s = clip.source_timeline_duration_s
|
||||
src_out = seconds_to_smpte(clip.match.in_point_s + source_duration_s, fps)
|
||||
rec_in = seconds_to_smpte(clip.timeline_start_s, fps)
|
||||
rec_out = seconds_to_smpte(clip.timeline_start_s + source_duration_s, fps)
|
||||
|
||||
event_line = f"{event_num:03d} AX V C {src_in} {src_out} {rec_in} {rec_out}"
|
||||
name_line = f"* FROM CLIP NAME: {clip.match.source_path.name}"
|
||||
comment_line = (
|
||||
f"* BEAT {clip.beat.beat_id:03d} | {clip.beat.beat_type.name} | "
|
||||
f"score={clip.match.match_score:.3f}"
|
||||
)
|
||||
|
||||
return "\n".join([event_line, name_line, comment_line, ""])
|
||||
|
||||
|
||||
def _edl_black_tail_event(event_num: int, clip: EditClip, fps: float) -> str:
|
||||
rec_in = seconds_to_smpte(clip.timeline_start_s + clip.source_timeline_duration_s, fps)
|
||||
rec_out = seconds_to_smpte(clip.timeline_end_s, fps)
|
||||
event_line = f"{event_num:03d} BL V C 00:00:00:00 00:00:00:00 {rec_in} {rec_out}"
|
||||
comment_line = (
|
||||
f"* BEAT {clip.beat.beat_id:03d} TRAILER-ONLY TAIL | "
|
||||
"add fade/dissolve to black"
|
||||
)
|
||||
return "\n".join([event_line, "* FROM CLIP NAME: BLACK", comment_line, ""])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def write_edl(
|
||||
timeline: EditTimeline,
|
||||
cfg: AppConfig,
|
||||
output_path: Path | None = None,
|
||||
) -> Path:
|
||||
"""
|
||||
Write the EditTimeline as a CMX 3600 EDL file.
|
||||
|
||||
Args:
|
||||
timeline: EditTimeline from build_timeline().
|
||||
cfg: Application configuration.
|
||||
output_path: Override destination. Defaults to
|
||||
<output_dir>/<project_name>.edl.
|
||||
|
||||
Returns:
|
||||
Path to the written .edl file.
|
||||
"""
|
||||
if output_path is None:
|
||||
output_path = cfg.paths.output_dir / f"{timeline.title}.edl"
|
||||
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
fps = timeline.frame_rate
|
||||
lines = [_edl_header(timeline.title), "\n"]
|
||||
|
||||
event_num = 1
|
||||
for clip in sorted(timeline.clips, key=lambda c: c.clip_index):
|
||||
lines.append(_edl_event(event_num, clip, fps))
|
||||
event_num += 1
|
||||
if clip.trailer_tail_s > 0:
|
||||
lines.append("\n")
|
||||
lines.append(_edl_black_tail_event(event_num, clip, fps))
|
||||
event_num += 1
|
||||
lines.append("\n")
|
||||
|
||||
edl_text = "\n".join(lines)
|
||||
output_path.write_text(edl_text, encoding="utf-8")
|
||||
|
||||
logger.info("EDL written → %s (%d events)", output_path, timeline.clip_count)
|
||||
return output_path
|
||||
Reference in New Issue
Block a user