Update app.py
Browse files
app.py
CHANGED
|
@@ -12,6 +12,7 @@ import math
|
|
| 12 |
import uuid
|
| 13 |
import re
|
| 14 |
from datetime import timedelta
|
|
|
|
| 15 |
|
| 16 |
# --- CRITICAL ENVIRONMENT FIXES ---
|
| 17 |
if os.environ.get("OMP_NUM_THREADS", "").endswith("m"):
|
|
@@ -126,11 +127,7 @@ def generate_cmx_edl(edl_title, segments, source_name, fps=25):
|
|
| 126 |
return "\n".join(edl_lines)
|
| 127 |
|
| 128 |
def generate_xml(sequence_name, segments, source_name, fps=25):
|
| 129 |
-
"""Constructs a
|
| 130 |
-
|
| 131 |
-
# CONSTANT ID for the source file.
|
| 132 |
-
# This ensures Premiere creates only ONE offline clip in the bin.
|
| 133 |
-
unified_source_id = "source-file-1"
|
| 134 |
|
| 135 |
# FPS Logic
|
| 136 |
timebase = int(round(fps))
|
|
@@ -171,9 +168,10 @@ def generate_xml(sequence_name, segments, source_name, fps=25):
|
|
| 171 |
tl_end = tl_start + duration_frames
|
| 172 |
|
| 173 |
raw_note = seg.get('note', 'Junior Editor Selection')
|
|
|
|
| 174 |
clip_note = (raw_note[:75] + '..') if len(raw_note) > 75 else raw_note
|
| 175 |
|
| 176 |
-
# Unique ID for
|
| 177 |
clip_id = f"clip-v-{i}-{uuid.uuid4()}"
|
| 178 |
|
| 179 |
lines.append(f'\t\t\t\t<clipitem id="{clip_id}">')
|
|
@@ -185,9 +183,9 @@ def generate_xml(sequence_name, segments, source_name, fps=25):
|
|
| 185 |
lines.append(f'\t\t\t\t\t<in>{src_in_frames}</in>')
|
| 186 |
lines.append(f'\t\t\t\t\t<out>{src_out_frames}</out>')
|
| 187 |
|
| 188 |
-
# ---
|
| 189 |
-
#
|
| 190 |
-
lines.append(f'\t\t\t\t\t<file id="{
|
| 191 |
lines.append(f'\t\t\t\t\t\t<name>{source_name}</name>')
|
| 192 |
lines.append(f'\t\t\t\t\t\t<rate><timebase>{timebase}</timebase></rate>')
|
| 193 |
lines.append(f'\t\t\t\t\t\t<duration>8640000</duration>')
|
|
@@ -197,6 +195,7 @@ def generate_xml(sequence_name, segments, source_name, fps=25):
|
|
| 197 |
lines.append(f'\t\t\t\t\t\t\t<frame>0</frame>')
|
| 198 |
lines.append(f'\t\t\t\t\t\t\t<displayformat>NDF</displayformat>')
|
| 199 |
lines.append(f'\t\t\t\t\t\t</timecode>')
|
|
|
|
| 200 |
lines.append(f'\t\t\t\t\t\t<reel><name>{source_name}</name></reel>')
|
| 201 |
lines.append(f'\t\t\t\t\t\t<media>')
|
| 202 |
lines.append(f'\t\t\t\t\t\t\t<video>')
|
|
@@ -208,7 +207,7 @@ def generate_xml(sequence_name, segments, source_name, fps=25):
|
|
| 208 |
lines.append(f'\t\t\t\t\t\t\t</audio>')
|
| 209 |
lines.append(f'\t\t\t\t\t\t</media>')
|
| 210 |
lines.append(f'\t\t\t\t\t</file>')
|
| 211 |
-
# -------------------------------
|
| 212 |
|
| 213 |
lines.append(f'\t\t\t\t</clipitem>')
|
| 214 |
|
|
@@ -235,7 +234,7 @@ def generate_xml(sequence_name, segments, source_name, fps=25):
|
|
| 235 |
raw_note = seg.get('note', 'Junior Editor Selection')
|
| 236 |
clip_note = (raw_note[:75] + '..') if len(raw_note) > 75 else raw_note
|
| 237 |
|
| 238 |
-
# Unique ID for audio clip
|
| 239 |
clip_id = f"clip-a-{i}-{uuid.uuid4()}"
|
| 240 |
|
| 241 |
lines.append(f'\t\t\t\t<clipitem id="{clip_id}">')
|
|
@@ -247,8 +246,8 @@ def generate_xml(sequence_name, segments, source_name, fps=25):
|
|
| 247 |
lines.append(f'\t\t\t\t\t<in>{src_in_frames}</in>')
|
| 248 |
lines.append(f'\t\t\t\t\t<out>{src_out_frames}</out>')
|
| 249 |
|
| 250 |
-
#
|
| 251 |
-
lines.append(f'\t\t\t\t\t<file id="{
|
| 252 |
lines.append(f'\t\t\t\t\t\t<name>{source_name}</name>')
|
| 253 |
lines.append(f'\t\t\t\t\t\t<rate><timebase>{timebase}</timebase></rate>')
|
| 254 |
lines.append(f'\t\t\t\t\t\t<duration>8640000</duration>')
|
|
@@ -341,7 +340,7 @@ st.markdown("""
|
|
| 341 |
* It will create an EDL or XML to import back into your editing software (Resolve, Premiere).
|
| 342 |
* **For Multicam Workflows:** Use the **XML** option in the sidebar and enter the **exact name** of your Multicam Sequence.
|
| 343 |
* **Resolve Users:** Uncheck "Automatically import source clips into media pool" during import.
|
| 344 |
-
* **Premiere Users:** Import the XML.
|
| 345 |
""")
|
| 346 |
|
| 347 |
st.divider()
|
|
|
|
| 12 |
import uuid
|
| 13 |
import re
|
| 14 |
from datetime import timedelta
|
| 15 |
+
from urllib.parse import quote
|
| 16 |
|
| 17 |
# --- CRITICAL ENVIRONMENT FIXES ---
|
| 18 |
if os.environ.get("OMP_NUM_THREADS", "").endswith("m"):
|
|
|
|
| 127 |
return "\n".join(edl_lines)
|
| 128 |
|
| 129 |
def generate_xml(sequence_name, segments, source_name, fps=25):
|
| 130 |
+
"""Constructs a 'Dumb' XML that forces Premiere to respect timeline In/Outs on replace."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
|
| 132 |
# FPS Logic
|
| 133 |
timebase = int(round(fps))
|
|
|
|
| 168 |
tl_end = tl_start + duration_frames
|
| 169 |
|
| 170 |
raw_note = seg.get('note', 'Junior Editor Selection')
|
| 171 |
+
# Truncate for safety
|
| 172 |
clip_note = (raw_note[:75] + '..') if len(raw_note) > 75 else raw_note
|
| 173 |
|
| 174 |
+
# Unique ID for every single clip item to prevent "Master Clip" logic grouping
|
| 175 |
clip_id = f"clip-v-{i}-{uuid.uuid4()}"
|
| 176 |
|
| 177 |
lines.append(f'\t\t\t\t<clipitem id="{clip_id}">')
|
|
|
|
| 183 |
lines.append(f'\t\t\t\t\t<in>{src_in_frames}</in>')
|
| 184 |
lines.append(f'\t\t\t\t\t<out>{src_out_frames}</out>')
|
| 185 |
|
| 186 |
+
# --- DUMB FILE DEFINITION (No MasterClipID, No Path) ---
|
| 187 |
+
# This forces the NLE to treat it as a standalone event needing a source.
|
| 188 |
+
lines.append(f'\t\t\t\t\t<file id="file-v-{i}">')
|
| 189 |
lines.append(f'\t\t\t\t\t\t<name>{source_name}</name>')
|
| 190 |
lines.append(f'\t\t\t\t\t\t<rate><timebase>{timebase}</timebase></rate>')
|
| 191 |
lines.append(f'\t\t\t\t\t\t<duration>8640000</duration>')
|
|
|
|
| 195 |
lines.append(f'\t\t\t\t\t\t\t<frame>0</frame>')
|
| 196 |
lines.append(f'\t\t\t\t\t\t\t<displayformat>NDF</displayformat>')
|
| 197 |
lines.append(f'\t\t\t\t\t\t</timecode>')
|
| 198 |
+
# Reel name helps Resolve link if file name fails
|
| 199 |
lines.append(f'\t\t\t\t\t\t<reel><name>{source_name}</name></reel>')
|
| 200 |
lines.append(f'\t\t\t\t\t\t<media>')
|
| 201 |
lines.append(f'\t\t\t\t\t\t\t<video>')
|
|
|
|
| 207 |
lines.append(f'\t\t\t\t\t\t\t</audio>')
|
| 208 |
lines.append(f'\t\t\t\t\t\t</media>')
|
| 209 |
lines.append(f'\t\t\t\t\t</file>')
|
| 210 |
+
# ----------------------------------------------
|
| 211 |
|
| 212 |
lines.append(f'\t\t\t\t</clipitem>')
|
| 213 |
|
|
|
|
| 234 |
raw_note = seg.get('note', 'Junior Editor Selection')
|
| 235 |
clip_note = (raw_note[:75] + '..') if len(raw_note) > 75 else raw_note
|
| 236 |
|
| 237 |
+
# Unique ID for audio clip too
|
| 238 |
clip_id = f"clip-a-{i}-{uuid.uuid4()}"
|
| 239 |
|
| 240 |
lines.append(f'\t\t\t\t<clipitem id="{clip_id}">')
|
|
|
|
| 246 |
lines.append(f'\t\t\t\t\t<in>{src_in_frames}</in>')
|
| 247 |
lines.append(f'\t\t\t\t\t<out>{src_out_frames}</out>')
|
| 248 |
|
| 249 |
+
# Reuse same file definition (No Master ID)
|
| 250 |
+
lines.append(f'\t\t\t\t\t<file id="file-a-{i}">')
|
| 251 |
lines.append(f'\t\t\t\t\t\t<name>{source_name}</name>')
|
| 252 |
lines.append(f'\t\t\t\t\t\t<rate><timebase>{timebase}</timebase></rate>')
|
| 253 |
lines.append(f'\t\t\t\t\t\t<duration>8640000</duration>')
|
|
|
|
| 340 |
* It will create an EDL or XML to import back into your editing software (Resolve, Premiere).
|
| 341 |
* **For Multicam Workflows:** Use the **XML** option in the sidebar and enter the **exact name** of your Multicam Sequence.
|
| 342 |
* **Resolve Users:** Uncheck "Automatically import source clips into media pool" during import.
|
| 343 |
+
* **Premiere Users:** Import the XML. Select all clips on the timeline. Hold **Alt (Win) / Option (Mac)** and drag your Multicam Sequence from the bin onto the selected clips to link them instantly.
|
| 344 |
""")
|
| 345 |
|
| 346 |
st.divider()
|