Update app.py
Browse files
app.py
CHANGED
|
@@ -120,11 +120,15 @@ def generate_cmx_edl(edl_title, segments, source_name, fps=25, start_offset=0.0)
|
|
| 120 |
reel_id = source_name.replace(" ", "_")[:8] # EDL reel IDs are traditionally short max 8 chars
|
| 121 |
|
| 122 |
for i, seg in enumerate(segments, 1):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
# Add offset to source time
|
| 124 |
-
src_in = format_timecode(
|
| 125 |
-
src_out = format_timecode(
|
| 126 |
|
| 127 |
-
duration =
|
| 128 |
rec_in = format_timecode(rec_start, fps)
|
| 129 |
rec_out = format_timecode(rec_start + duration, fps)
|
| 130 |
|
|
@@ -132,7 +136,7 @@ def generate_cmx_edl(edl_title, segments, source_name, fps=25, start_offset=0.0)
|
|
| 132 |
edl_lines.append(f"* FROM CLIP NAME: {source_name}")
|
| 133 |
edl_lines.append(f"* {seg.get('note', 'Clip')}\n")
|
| 134 |
|
| 135 |
-
gap = seg.get('gap', 0.0)
|
| 136 |
rec_start += duration + gap
|
| 137 |
|
| 138 |
return "\n".join(edl_lines)
|
|
@@ -177,8 +181,11 @@ def generate_xml(sequence_name, segments, source_name, fps=25, start_offset=0.0)
|
|
| 177 |
|
| 178 |
timeline_head_frames = 0
|
| 179 |
for i, seg in enumerate(segments, 1):
|
| 180 |
-
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
| 182 |
duration_frames = src_out_frames - src_in_frames
|
| 183 |
|
| 184 |
tl_start = timeline_head_frames
|
|
@@ -216,7 +223,7 @@ def generate_xml(sequence_name, segments, source_name, fps=25, start_offset=0.0)
|
|
| 216 |
lines.append('\t\t\t\t\t</file>')
|
| 217 |
lines.append('\t\t\t\t</clipitem>')
|
| 218 |
|
| 219 |
-
gap_frames = seconds_to_frames(seg.get('gap', 0.0), fps)
|
| 220 |
timeline_head_frames = tl_end + gap_frames
|
| 221 |
|
| 222 |
lines.append('\t\t\t</track>')
|
|
@@ -228,8 +235,11 @@ def generate_xml(sequence_name, segments, source_name, fps=25, start_offset=0.0)
|
|
| 228 |
|
| 229 |
timeline_head_frames = 0
|
| 230 |
for i, seg in enumerate(segments, 1):
|
| 231 |
-
|
| 232 |
-
|
|
|
|
|
|
|
|
|
|
| 233 |
duration_frames = src_out_frames - src_in_frames
|
| 234 |
tl_start = timeline_head_frames
|
| 235 |
tl_end = tl_start + duration_frames
|
|
@@ -241,9 +251,9 @@ def generate_xml(sequence_name, segments, source_name, fps=25, start_offset=0.0)
|
|
| 241 |
lines.append(f'\t\t\t\t\t<end>{tl_end}</end>')
|
| 242 |
lines.append(f'\t\t\t\t\t<in>{src_in_frames}</in>')
|
| 243 |
lines.append(f'\t\t\t\t\t<out>{src_out_frames}</out>')
|
| 244 |
-
lines.append('\t\t\t\t\t<sourcetrack><mediatype>audio</mediatype><trackindex>1</trackindex></
|
| 245 |
lines.append('\t\t\t\t</clipitem>')
|
| 246 |
-
timeline_head_frames = tl_end + seconds_to_frames(seg.get('gap', 0.0), fps)
|
| 247 |
|
| 248 |
lines.append('\t\t\t</track>')
|
| 249 |
lines.append('\t\t</audio>')
|
|
@@ -357,52 +367,47 @@ st.markdown("""
|
|
| 357 |
st.divider()
|
| 358 |
|
| 359 |
with st.sidebar:
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
|
|
|
|
|
|
| 383 |
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
"
|
| 389 |
-
"
|
| 390 |
-
"
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
num_speakers = st.number_input("Speakers (0=Auto)", min_value=0, value=0)
|
| 400 |
-
|
| 401 |
-
st.divider()
|
| 402 |
-
if ACTIVE_HF_TOKEN == "PASTE_YOUR_HF_TOKEN_HERE":
|
| 403 |
-
st.warning("⚠️ HF_TOKEN not set in Secrets!")
|
| 404 |
-
else:
|
| 405 |
-
st.success("✅ HF_TOKEN Loaded")
|
| 406 |
|
| 407 |
uploaded_file = st.file_uploader("Upload Video/Audio Clip", type=["mp4", "m4a", "wav", "mp3", "mov"])
|
| 408 |
|
|
@@ -437,9 +442,10 @@ if uploaded_file:
|
|
| 437 |
if device == "cpu": st.warning("⚠️ No GPU detected.")
|
| 438 |
|
| 439 |
# Phase 2
|
| 440 |
-
status_text.markdown(
|
| 441 |
compute_type = "float16" if device == "cuda" else "int8"
|
| 442 |
-
model
|
|
|
|
| 443 |
audio = whisperx.load_audio("temp_audio.wav")
|
| 444 |
result = model.transcribe(audio, batch_size=16, language=target_language)
|
| 445 |
del model
|
|
|
|
| 120 |
reel_id = source_name.replace(" ", "_")[:8] # EDL reel IDs are traditionally short max 8 chars
|
| 121 |
|
| 122 |
for i, seg in enumerate(segments, 1):
|
| 123 |
+
# Fallback to 'start'/'end' if 'src_start'/'src_end' are missing, and force float
|
| 124 |
+
src_start_val = float(seg.get('src_start', seg.get('start', 0.0)))
|
| 125 |
+
src_end_val = float(seg.get('src_end', seg.get('end', 0.0)))
|
| 126 |
+
|
| 127 |
# Add offset to source time
|
| 128 |
+
src_in = format_timecode(src_start_val + start_offset, fps)
|
| 129 |
+
src_out = format_timecode(src_end_val + start_offset, fps)
|
| 130 |
|
| 131 |
+
duration = src_end_val - src_start_val
|
| 132 |
rec_in = format_timecode(rec_start, fps)
|
| 133 |
rec_out = format_timecode(rec_start + duration, fps)
|
| 134 |
|
|
|
|
| 136 |
edl_lines.append(f"* FROM CLIP NAME: {source_name}")
|
| 137 |
edl_lines.append(f"* {seg.get('note', 'Clip')}\n")
|
| 138 |
|
| 139 |
+
gap = float(seg.get('gap', 0.0))
|
| 140 |
rec_start += duration + gap
|
| 141 |
|
| 142 |
return "\n".join(edl_lines)
|
|
|
|
| 181 |
|
| 182 |
timeline_head_frames = 0
|
| 183 |
for i, seg in enumerate(segments, 1):
|
| 184 |
+
src_start_val = float(seg.get('src_start', seg.get('start', 0.0)))
|
| 185 |
+
src_end_val = float(seg.get('src_end', seg.get('end', 0.0)))
|
| 186 |
+
|
| 187 |
+
src_in_frames = seconds_to_frames(src_start_val, fps) + start_offset_frames
|
| 188 |
+
src_out_frames = seconds_to_frames(src_end_val, fps) + start_offset_frames
|
| 189 |
duration_frames = src_out_frames - src_in_frames
|
| 190 |
|
| 191 |
tl_start = timeline_head_frames
|
|
|
|
| 223 |
lines.append('\t\t\t\t\t</file>')
|
| 224 |
lines.append('\t\t\t\t</clipitem>')
|
| 225 |
|
| 226 |
+
gap_frames = seconds_to_frames(float(seg.get('gap', 0.0)), fps)
|
| 227 |
timeline_head_frames = tl_end + gap_frames
|
| 228 |
|
| 229 |
lines.append('\t\t\t</track>')
|
|
|
|
| 235 |
|
| 236 |
timeline_head_frames = 0
|
| 237 |
for i, seg in enumerate(segments, 1):
|
| 238 |
+
src_start_val = float(seg.get('src_start', seg.get('start', 0.0)))
|
| 239 |
+
src_end_val = float(seg.get('src_end', seg.get('end', 0.0)))
|
| 240 |
+
|
| 241 |
+
src_in_frames = seconds_to_frames(src_start_val, fps) + start_offset_frames
|
| 242 |
+
src_out_frames = seconds_to_frames(src_end_val, fps) + start_offset_frames
|
| 243 |
duration_frames = src_out_frames - src_in_frames
|
| 244 |
tl_start = timeline_head_frames
|
| 245 |
tl_end = tl_start + duration_frames
|
|
|
|
| 251 |
lines.append(f'\t\t\t\t\t<end>{tl_end}</end>')
|
| 252 |
lines.append(f'\t\t\t\t\t<in>{src_in_frames}</in>')
|
| 253 |
lines.append(f'\t\t\t\t\t<out>{src_out_frames}</out>')
|
| 254 |
+
lines.append('\t\t\t\t\t<sourcetrack><mediatype>audio</mediatype><trackindex>1</trackindex></sourcetrack>')
|
| 255 |
lines.append('\t\t\t\t</clipitem>')
|
| 256 |
+
timeline_head_frames = tl_end + seconds_to_frames(float(seg.get('gap', 0.0)), fps)
|
| 257 |
|
| 258 |
lines.append('\t\t\t</track>')
|
| 259 |
lines.append('\t\t</audio>')
|
|
|
|
| 367 |
st.divider()
|
| 368 |
|
| 369 |
with st.sidebar:
|
| 370 |
+
# Use containers so we can render Clip Settings visually above Project Settings,
|
| 371 |
+
# but still evaluate Export Settings first (needed to determine dynamic text labels).
|
| 372 |
+
clip_settings_container = st.container()
|
| 373 |
+
project_settings_container = st.container()
|
| 374 |
+
|
| 375 |
+
with project_settings_container:
|
| 376 |
+
st.header("Project Settings")
|
| 377 |
+
export_format = st.radio("Output Format", ["EDL", "XML (Multicam)"], index=0)
|
| 378 |
+
|
| 379 |
+
fps_options = [23.98, 24, 25, 29.97, 30, 50, 59.94, 60]
|
| 380 |
+
fps = st.selectbox("Timeline FPS", fps_options, index=2)
|
| 381 |
+
|
| 382 |
+
language_map = {
|
| 383 |
+
"Auto-Detect": None,
|
| 384 |
+
"English": "en",
|
| 385 |
+
"Spanish": "es",
|
| 386 |
+
"French": "fr",
|
| 387 |
+
"German": "de",
|
| 388 |
+
"Italian": "it",
|
| 389 |
+
"Portuguese": "pt"
|
| 390 |
+
}
|
| 391 |
+
selected_lang_label = st.selectbox("Audio Language", list(language_map.keys()), index=1)
|
| 392 |
+
target_language = language_map[selected_lang_label]
|
| 393 |
+
|
| 394 |
+
num_speakers = st.number_input("Speakers (0=Auto)", min_value=0, value=0)
|
| 395 |
|
| 396 |
+
with clip_settings_container:
|
| 397 |
+
st.header("Clip Settings")
|
| 398 |
+
source_start_tc = st.text_input("Source Start Timecode", value="00:00:00:00", help="Format: HH:MM:SS:FF")
|
| 399 |
+
|
| 400 |
+
input_label = "EDL Reel Name"
|
| 401 |
+
input_help = "Leave empty to use the uploaded file name."
|
| 402 |
+
if export_format == "XML (Multicam)":
|
| 403 |
+
input_label = "Multicam Sequence Name"
|
| 404 |
+
input_help = "EXACT name of your Multicam Clip in Resolve."
|
| 405 |
+
|
| 406 |
+
custom_reel_name = st.text_input(
|
| 407 |
+
input_label,
|
| 408 |
+
placeholder="e.g. Interview_Day1_Multi",
|
| 409 |
+
help=input_help
|
| 410 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 411 |
|
| 412 |
uploaded_file = st.file_uploader("Upload Video/Audio Clip", type=["mp4", "m4a", "wav", "mp3", "mov"])
|
| 413 |
|
|
|
|
| 442 |
if device == "cpu": st.warning("⚠️ No GPU detected.")
|
| 443 |
|
| 444 |
# Phase 2
|
| 445 |
+
status_text.markdown("**Phase 2/4: Transcribing (Whisper)... This is the longest step.**")
|
| 446 |
compute_type = "float16" if device == "cuda" else "int8"
|
| 447 |
+
# Default model size hardcoded to 'large-v2' to keep high quality while removing the sidebar toggle
|
| 448 |
+
model = whisperx.load_model("large-v2", device, compute_type=compute_type)
|
| 449 |
audio = whisperx.load_audio("temp_audio.wav")
|
| 450 |
result = model.transcribe(audio, batch_size=16, language=target_language)
|
| 451 |
del model
|