Update app.py
Browse files
app.py
CHANGED
|
@@ -11,8 +11,8 @@ import gc
|
|
| 11 |
import math
|
| 12 |
import uuid
|
| 13 |
import re
|
| 14 |
-
from urllib.parse import quote
|
| 15 |
from datetime import timedelta
|
|
|
|
| 16 |
|
| 17 |
# --- CRITICAL ENVIRONMENT FIXES ---
|
| 18 |
if os.environ.get("OMP_NUM_THREADS", "").endswith("m"):
|
|
@@ -127,17 +127,12 @@ def generate_cmx_edl(edl_title, segments, source_name, fps=25):
|
|
| 127 |
return "\n".join(edl_lines)
|
| 128 |
|
| 129 |
def generate_xml(sequence_name, segments, source_name, fps=25):
|
| 130 |
-
"""Constructs a
|
| 131 |
-
|
| 132 |
-
master_id = "masterfile-1"
|
| 133 |
|
| 134 |
# FPS Logic
|
| 135 |
timebase = int(round(fps))
|
| 136 |
is_ntsc = "TRUE" if fps % 1 != 0 else "FALSE"
|
| 137 |
|
| 138 |
-
# NOTE: We intentionally OMIT pathurl for Multicam workflows.
|
| 139 |
-
# This forces Premiere/Resolve to match by NAME in the project bin.
|
| 140 |
-
|
| 141 |
lines = []
|
| 142 |
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
|
| 143 |
lines.append('<!DOCTYPE xmeml>')
|
|
@@ -173,9 +168,13 @@ def generate_xml(sequence_name, segments, source_name, fps=25):
|
|
| 173 |
tl_end = tl_start + duration_frames
|
| 174 |
|
| 175 |
raw_note = seg.get('note', 'Junior Editor Selection')
|
|
|
|
| 176 |
clip_note = (raw_note[:75] + '..') if len(raw_note) > 75 else raw_note
|
|
|
|
|
|
|
|
|
|
| 177 |
|
| 178 |
-
lines.append(f'\t\t\t\t<clipitem id="
|
| 179 |
lines.append(f'\t\t\t\t\t<name>{clip_note}</name>')
|
| 180 |
lines.append(f'\t\t\t\t\t<duration>{duration_frames}</duration>')
|
| 181 |
lines.append(f'\t\t\t\t\t<rate><timebase>{timebase}</timebase></rate>')
|
|
@@ -183,12 +182,11 @@ def generate_xml(sequence_name, segments, source_name, fps=25):
|
|
| 183 |
lines.append(f'\t\t\t\t\t<end>{tl_end}</end>')
|
| 184 |
lines.append(f'\t\t\t\t\t<in>{src_in_frames}</in>')
|
| 185 |
lines.append(f'\t\t\t\t\t<out>{src_out_frames}</out>')
|
| 186 |
-
lines.append(f'\t\t\t\t\t<masterclipid>{master_id}</masterclipid>')
|
| 187 |
|
| 188 |
-
# ---
|
| 189 |
-
|
|
|
|
| 190 |
lines.append(f'\t\t\t\t\t\t<name>{source_name}</name>')
|
| 191 |
-
# NO PATHURL - Forces bin search
|
| 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>')
|
| 194 |
lines.append(f'\t\t\t\t\t\t<timecode>')
|
|
@@ -197,6 +195,8 @@ 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<media>')
|
| 201 |
lines.append(f'\t\t\t\t\t\t\t<video>')
|
| 202 |
lines.append(f'\t\t\t\t\t\t\t\t<samplecharacteristics><width>1920</width><height>1080</height></samplecharacteristics>')
|
|
@@ -207,7 +207,7 @@ def generate_xml(sequence_name, segments, source_name, fps=25):
|
|
| 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 |
|
|
@@ -233,8 +233,11 @@ def generate_xml(sequence_name, segments, source_name, fps=25):
|
|
| 233 |
|
| 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 |
-
lines.append(f'\t\t\t\t<clipitem id="
|
| 238 |
lines.append(f'\t\t\t\t\t<name>{clip_note}</name>')
|
| 239 |
lines.append(f'\t\t\t\t\t<duration>{duration_frames}</duration>')
|
| 240 |
lines.append(f'\t\t\t\t\t<rate><timebase>{timebase}</timebase></rate>')
|
|
@@ -242,10 +245,29 @@ def generate_xml(sequence_name, segments, source_name, fps=25):
|
|
| 242 |
lines.append(f'\t\t\t\t\t<end>{tl_end}</end>')
|
| 243 |
lines.append(f'\t\t\t\t\t<in>{src_in_frames}</in>')
|
| 244 |
lines.append(f'\t\t\t\t\t<out>{src_out_frames}</out>')
|
| 245 |
-
lines.append(f'\t\t\t\t\t<masterclipid>{master_id}</masterclipid>')
|
| 246 |
|
| 247 |
-
# Reuse same file definition
|
| 248 |
-
lines.append(f'\t\t\t\t\t<file id="{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
|
| 250 |
# SOURCE TRACK MAPPING
|
| 251 |
lines.append('\t\t\t\t\t<sourcetrack>')
|
|
@@ -316,7 +338,9 @@ st.markdown("""
|
|
| 316 |
* Set your timeline FPS and transcription quality.
|
| 317 |
* Junior Editor will transcribe and separate speakers. You can then instruct it to find engaging bits or construct a narrative.
|
| 318 |
* It will create an EDL or XML to import back into your editing software (Resolve, Premiere).
|
| 319 |
-
* **For Multicam Workflows:** Use the **XML** option in the sidebar and enter the **exact name** of your Multicam Sequence.
|
|
|
|
|
|
|
| 320 |
""")
|
| 321 |
|
| 322 |
st.divider()
|
|
|
|
| 11 |
import math
|
| 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))
|
| 134 |
is_ntsc = "TRUE" if fps % 1 != 0 else "FALSE"
|
| 135 |
|
|
|
|
|
|
|
|
|
|
| 136 |
lines = []
|
| 137 |
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
|
| 138 |
lines.append('<!DOCTYPE xmeml>')
|
|
|
|
| 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}">')
|
| 178 |
lines.append(f'\t\t\t\t\t<name>{clip_note}</name>')
|
| 179 |
lines.append(f'\t\t\t\t\t<duration>{duration_frames}</duration>')
|
| 180 |
lines.append(f'\t\t\t\t\t<rate><timebase>{timebase}</timebase></rate>')
|
|
|
|
| 182 |
lines.append(f'\t\t\t\t\t<end>{tl_end}</end>')
|
| 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>')
|
| 192 |
lines.append(f'\t\t\t\t\t\t<timecode>')
|
|
|
|
| 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>')
|
| 202 |
lines.append(f'\t\t\t\t\t\t\t\t<samplecharacteristics><width>1920</width><height>1080</height></samplecharacteristics>')
|
|
|
|
| 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 |
|
|
|
|
| 233 |
|
| 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}">')
|
| 241 |
lines.append(f'\t\t\t\t\t<name>{clip_note}</name>')
|
| 242 |
lines.append(f'\t\t\t\t\t<duration>{duration_frames}</duration>')
|
| 243 |
lines.append(f'\t\t\t\t\t<rate><timebase>{timebase}</timebase></rate>')
|
|
|
|
| 245 |
lines.append(f'\t\t\t\t\t<end>{tl_end}</end>')
|
| 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>')
|
| 254 |
+
lines.append(f'\t\t\t\t\t\t<timecode>')
|
| 255 |
+
lines.append(f'\t\t\t\t\t\t\t<rate><timebase>{timebase}</timebase></rate>')
|
| 256 |
+
lines.append(f'\t\t\t\t\t\t\t<string>00:00:00:00</string>')
|
| 257 |
+
lines.append(f'\t\t\t\t\t\t\t<frame>0</frame>')
|
| 258 |
+
lines.append(f'\t\t\t\t\t\t\t<displayformat>NDF</displayformat>')
|
| 259 |
+
lines.append(f'\t\t\t\t\t\t</timecode>')
|
| 260 |
+
lines.append(f'\t\t\t\t\t\t<reel><name>{source_name}</name></reel>')
|
| 261 |
+
lines.append(f'\t\t\t\t\t\t<media>')
|
| 262 |
+
lines.append(f'\t\t\t\t\t\t\t<video>')
|
| 263 |
+
lines.append(f'\t\t\t\t\t\t\t\t<samplecharacteristics><width>1920</width><height>1080</height></samplecharacteristics>')
|
| 264 |
+
lines.append(f'\t\t\t\t\t\t\t</video>')
|
| 265 |
+
lines.append(f'\t\t\t\t\t\t\t<audio>')
|
| 266 |
+
lines.append(f'\t\t\t\t\t\t\t\t<samplecharacteristics><depth>16</depth><samplerate>48000</samplerate></samplecharacteristics>')
|
| 267 |
+
lines.append(f'\t\t\t\t\t\t\t\t<channelcount>2</channelcount>')
|
| 268 |
+
lines.append(f'\t\t\t\t\t\t\t</audio>')
|
| 269 |
+
lines.append(f'\t\t\t\t\t\t</media>')
|
| 270 |
+
lines.append(f'\t\t\t\t\t</file>')
|
| 271 |
|
| 272 |
# SOURCE TRACK MAPPING
|
| 273 |
lines.append('\t\t\t\t\t<sourcetrack>')
|
|
|
|
| 338 |
* Set your timeline FPS and transcription quality.
|
| 339 |
* Junior Editor will transcribe and separate speakers. You can then instruct it to find engaging bits or construct a narrative.
|
| 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()
|