Update app.py
Browse files
app.py
CHANGED
|
@@ -119,16 +119,19 @@ def generate_cmx_edl(edl_title, segments, source_name, fps=25, start_offset_fram
|
|
| 119 |
reel_id = source_name.replace(" ", "_")[:8] # EDL reel IDs are traditionally short max 8 chars
|
| 120 |
|
| 121 |
for i, seg in enumerate(segments, 1):
|
| 122 |
-
|
|
|
|
|
|
|
| 123 |
|
| 124 |
-
if is_vo:
|
| 125 |
-
|
| 126 |
-
|
|
|
|
| 127 |
duration_frames = seconds_to_frames(duration_sec, fps)
|
| 128 |
src_in_frames = 0
|
| 129 |
src_out_frames = duration_frames
|
| 130 |
-
current_source = "GEN_VO"
|
| 131 |
-
note_text = f"VO SCRIPT: {
|
| 132 |
else:
|
| 133 |
src_start_val = float(seg.get('src_start', seg.get('start', 0.0)))
|
| 134 |
src_end_val = float(seg.get('src_end', seg.get('end', 0.0)))
|
|
@@ -145,7 +148,7 @@ def generate_cmx_edl(edl_title, segments, source_name, fps=25, start_offset_fram
|
|
| 145 |
rec_out = frames_to_timecode(rec_start_frames + duration_frames, fps)
|
| 146 |
|
| 147 |
edl_lines.append(f"{i:03} {current_source:8} V C {src_in} {src_out} {rec_in} {rec_out}")
|
| 148 |
-
if not is_vo:
|
| 149 |
edl_lines.append(f"* FROM CLIP NAME: {source_name}")
|
| 150 |
edl_lines.append(f"* {note_text}\n")
|
| 151 |
|
|
@@ -192,26 +195,31 @@ def generate_xml(sequence_name, segments, source_name, fps=25, start_offset_fram
|
|
| 192 |
|
| 193 |
file_defined = False
|
| 194 |
vo_file_defined = False
|
|
|
|
| 195 |
timeline_head_frames = 0
|
| 196 |
|
| 197 |
for i, seg in enumerate(segments, 1):
|
| 198 |
-
|
|
|
|
|
|
|
| 199 |
|
| 200 |
-
if is_vo:
|
| 201 |
-
|
| 202 |
-
|
|
|
|
| 203 |
duration_frames = seconds_to_frames(duration_sec, fps)
|
| 204 |
src_in_frames = 0
|
| 205 |
src_out_frames = duration_frames
|
| 206 |
-
master_id_to_use = "masterfile-vo"
|
| 207 |
-
source_name_to_use = "Generated_VO_Placeholder"
|
| 208 |
-
|
|
|
|
| 209 |
clip_name_disp = (clip_note[:75] + '..') if len(clip_note) > 75 else clip_note
|
| 210 |
else:
|
| 211 |
src_start_val = float(seg.get('src_start', seg.get('start', 0.0)))
|
| 212 |
src_end_val = float(seg.get('src_end', seg.get('end', 0.0)))
|
| 213 |
-
src_in_frames = seconds_to_frames(src_start_val, fps)
|
| 214 |
-
src_out_frames = seconds_to_frames(src_end_val, fps)
|
| 215 |
duration_frames = src_out_frames - src_in_frames
|
| 216 |
master_id_to_use = master_id
|
| 217 |
source_name_to_use = source_name
|
|
@@ -254,6 +262,27 @@ def generate_xml(sequence_name, segments, source_name, fps=25, start_offset_fram
|
|
| 254 |
vo_file_defined = True
|
| 255 |
else:
|
| 256 |
lines.append(f'\t\t\t\t\t<file id="{master_id_to_use}"/>')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
else:
|
| 258 |
if not file_defined:
|
| 259 |
lines.append(f'\t\t\t\t\t<file id="{master_id_to_use}">')
|
|
@@ -279,7 +308,13 @@ def generate_xml(sequence_name, segments, source_name, fps=25, start_offset_fram
|
|
| 279 |
lines.append('\t\t\t\t\t<marker>')
|
| 280 |
lines.append(f'\t\t\t\t\t\t<name>{clip_note}</name>')
|
| 281 |
lines.append(f'\t\t\t\t\t\t<in>{src_in_frames}</in>')
|
| 282 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
lines.append('\t\t\t\t\t</marker>')
|
| 284 |
|
| 285 |
lines.append('\t\t\t\t</clipitem>')
|
|
@@ -296,8 +331,18 @@ def generate_xml(sequence_name, segments, source_name, fps=25, start_offset_fram
|
|
| 296 |
|
| 297 |
timeline_head_frames = 0
|
| 298 |
for i, seg in enumerate(segments, 1):
|
| 299 |
-
|
|
|
|
|
|
|
| 300 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
if is_vo:
|
| 302 |
vo_text = seg.get('text', 'VO')
|
| 303 |
duration_sec = float(seg.get('duration', max(1.0, len(vo_text.split()) / 2.5)))
|
|
@@ -345,13 +390,20 @@ def generate_transcript_txt(sequence_name, segments, fps=25, start_offset_frames
|
|
| 345 |
lines = [f"Sequence Transcript: {sequence_name}", "=" * 50, ""]
|
| 346 |
|
| 347 |
for i, seg in enumerate(segments, 1):
|
| 348 |
-
|
|
|
|
|
|
|
| 349 |
|
| 350 |
if is_vo:
|
| 351 |
vo_text = seg.get('text', 'VO')
|
| 352 |
duration = float(seg.get('duration', max(1.0, len(vo_text.split()) / 2.5)))
|
| 353 |
lines.append(f"Clip {i:02} | [GENERATED VOICE-OVER] | Est. Duration: {duration:.1f}s")
|
| 354 |
lines.append(f"Script: \"{vo_text}\"")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
else:
|
| 356 |
src_start_val = float(seg.get('src_start', seg.get('start', 0.0)))
|
| 357 |
src_end_val = float(seg.get('src_end', seg.get('end', 0.0)))
|
|
@@ -388,9 +440,11 @@ def call_gemini_for_edl(transcript_data, story_prompt, api_key, previous_edit=No
|
|
| 388 |
system_prompt = (
|
| 389 |
"You are an expert Documentary Senior Editor. Use the provided transcript JSON "
|
| 390 |
"(which includes Speaker IDs and word-level timestamps) to create a condensed story.\n\n"
|
| 391 |
-
"NEW
|
| 392 |
-
"
|
| 393 |
-
"
|
|
|
|
|
|
|
| 394 |
"For 'clip' segments (extracting from the subject):\n"
|
| 395 |
"{\"type\": \"clip\", \"src_start\": 12.5, \"src_end\": 25.0, \"note\": \"Subject talks about X\", \"gap\": 0.0}\n"
|
| 396 |
"- IGNORE ALL INTERVIEWER COMMENTS.\n"
|
|
@@ -400,6 +454,8 @@ def call_gemini_for_edl(transcript_data, story_prompt, api_key, previous_edit=No
|
|
| 400 |
"{\"type\": \"vo\", \"text\": \"The journey began years ago...\", \"duration\": 3.5, \"gap\": 0.0}\n"
|
| 401 |
"- DURATION: Estimate duration accurately based on reading speed (approx 2.5 words per second).\n"
|
| 402 |
"- Keep VO concise and in the style of a documentary narrator.\n\n"
|
|
|
|
|
|
|
| 403 |
"PACING: Group related clips. Add a 'gap' (in seconds) between distinct ideas."
|
| 404 |
)
|
| 405 |
|
|
@@ -499,7 +555,7 @@ st.markdown("""
|
|
| 499 |
* Upload your file here (video or audio).
|
| 500 |
* Set your timeline FPS, Source Timecode, and transcription quality in the sidebar.
|
| 501 |
* Junior Editor will transcribe and separate speakers. You can then instruct it to find engaging bits or construct a narrative.
|
| 502 |
-
* **Note: If you want me to write voice over suggestions, please request it.**
|
| 503 |
* It will create an EDL or XML to import back into your editing software (Resolve, Premiere).
|
| 504 |
* **For Multicam Workflows:** Use the **XML** option in the sidebar and enter the **exact name** of your Multicam Sequence.
|
| 505 |
* **Resolve Users:** Uncheck "Automatically import source clips into media pool" during import.
|
|
@@ -641,7 +697,7 @@ if uploaded_file:
|
|
| 641 |
st.markdown(f"**{seg['speaker']}:** {seg['text']}")
|
| 642 |
|
| 643 |
st.subheader("Your Instruction")
|
| 644 |
-
brief = st.text_area("What should the Junior Editor do?", placeholder="e.g. Find the most engaging bits. If you want me to write voice over suggestions, please request it.")
|
| 645 |
|
| 646 |
if st.button("Generate Edit"):
|
| 647 |
if not ACTIVE_GEMINI_KEY:
|
|
@@ -672,6 +728,8 @@ if uploaded_file:
|
|
| 672 |
|
| 673 |
# Save to session state so buttons don't disappear on click
|
| 674 |
st.session_state.edit_generated = True
|
|
|
|
|
|
|
| 675 |
st.session_state.final_output = final_output
|
| 676 |
st.session_state.ext = ext
|
| 677 |
st.session_state.transcript_txt = transcript_txt
|
|
@@ -694,7 +752,7 @@ if uploaded_file:
|
|
| 694 |
|
| 695 |
st.divider()
|
| 696 |
st.subheader("Revise Edit")
|
| 697 |
-
revision_brief = st.text_area("Want changes? Tell Junior Editor:", placeholder="e.g. Make it twice as long, add
|
| 698 |
|
| 699 |
if st.button("Apply Revisions"):
|
| 700 |
with st.spinner("Junior Editor is revising the edit..."):
|
|
@@ -709,14 +767,18 @@ if uploaded_file:
|
|
| 709 |
)
|
| 710 |
|
| 711 |
if new_edl_segments:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 712 |
if export_format == "EDL":
|
| 713 |
-
final_output = generate_cmx_edl(
|
| 714 |
ext = "edl"
|
| 715 |
else:
|
| 716 |
-
final_output = generate_xml(
|
| 717 |
ext = "xml"
|
| 718 |
|
| 719 |
-
transcript_txt = generate_transcript_txt(
|
| 720 |
|
| 721 |
st.session_state.final_output = final_output
|
| 722 |
st.session_state.ext = ext
|
|
@@ -724,6 +786,11 @@ if uploaded_file:
|
|
| 724 |
st.session_state.used_model = used_model
|
| 725 |
st.session_state.used_endpoint = used_endpoint
|
| 726 |
st.session_state.edl_segments = new_edl_segments
|
|
|
|
|
|
|
|
|
|
|
|
|
| 727 |
st.rerun()
|
| 728 |
|
| 729 |
|
|
|
|
|
|
| 119 |
reel_id = source_name.replace(" ", "_")[:8] # EDL reel IDs are traditionally short max 8 chars
|
| 120 |
|
| 121 |
for i, seg in enumerate(segments, 1):
|
| 122 |
+
seg_type = seg.get('type', 'clip')
|
| 123 |
+
is_vo = seg_type == 'vo' or ('text' in seg and seg_type != 'graphic')
|
| 124 |
+
is_graphic = seg_type == 'graphic'
|
| 125 |
|
| 126 |
+
if is_vo or is_graphic:
|
| 127 |
+
text_content = seg.get('text', 'Placeholder')
|
| 128 |
+
default_dur = max(1.0, len(text_content.split()) / 2.5) if is_vo else 4.0
|
| 129 |
+
duration_sec = float(seg.get('duration', default_dur))
|
| 130 |
duration_frames = seconds_to_frames(duration_sec, fps)
|
| 131 |
src_in_frames = 0
|
| 132 |
src_out_frames = duration_frames
|
| 133 |
+
current_source = "GEN_VO" if is_vo else "GEN_GFX"
|
| 134 |
+
note_text = f"{'VO SCRIPT' if is_vo else 'GRAPHIC'}: {text_content}"
|
| 135 |
else:
|
| 136 |
src_start_val = float(seg.get('src_start', seg.get('start', 0.0)))
|
| 137 |
src_end_val = float(seg.get('src_end', seg.get('end', 0.0)))
|
|
|
|
| 148 |
rec_out = frames_to_timecode(rec_start_frames + duration_frames, fps)
|
| 149 |
|
| 150 |
edl_lines.append(f"{i:03} {current_source:8} V C {src_in} {src_out} {rec_in} {rec_out}")
|
| 151 |
+
if not is_vo and not is_graphic:
|
| 152 |
edl_lines.append(f"* FROM CLIP NAME: {source_name}")
|
| 153 |
edl_lines.append(f"* {note_text}\n")
|
| 154 |
|
|
|
|
| 195 |
|
| 196 |
file_defined = False
|
| 197 |
vo_file_defined = False
|
| 198 |
+
gfx_file_defined = False
|
| 199 |
timeline_head_frames = 0
|
| 200 |
|
| 201 |
for i, seg in enumerate(segments, 1):
|
| 202 |
+
seg_type = seg.get('type', 'clip')
|
| 203 |
+
is_vo = seg_type == 'vo' or ('text' in seg and seg_type != 'graphic')
|
| 204 |
+
is_graphic = seg_type == 'graphic'
|
| 205 |
|
| 206 |
+
if is_vo or is_graphic:
|
| 207 |
+
text_content = seg.get('text', 'Placeholder')
|
| 208 |
+
default_dur = max(1.0, len(text_content.split()) / 2.5) if is_vo else 4.0
|
| 209 |
+
duration_sec = float(seg.get('duration', default_dur))
|
| 210 |
duration_frames = seconds_to_frames(duration_sec, fps)
|
| 211 |
src_in_frames = 0
|
| 212 |
src_out_frames = duration_frames
|
| 213 |
+
master_id_to_use = "masterfile-vo" if is_vo else "masterfile-gfx"
|
| 214 |
+
source_name_to_use = "Generated_VO_Placeholder" if is_vo else "Generated_Graphic_Placeholder"
|
| 215 |
+
prefix = "VO SCRIPT" if is_vo else "GRAPHIC CARD"
|
| 216 |
+
clip_note = f"{prefix}: {text_content}"
|
| 217 |
clip_name_disp = (clip_note[:75] + '..') if len(clip_note) > 75 else clip_note
|
| 218 |
else:
|
| 219 |
src_start_val = float(seg.get('src_start', seg.get('start', 0.0)))
|
| 220 |
src_end_val = float(seg.get('src_end', seg.get('end', 0.0)))
|
| 221 |
+
src_in_frames = seconds_to_frames(src_start_val, fps) + start_offset_frames
|
| 222 |
+
src_out_frames = seconds_to_frames(src_end_val, fps) + start_offset_frames
|
| 223 |
duration_frames = src_out_frames - src_in_frames
|
| 224 |
master_id_to_use = master_id
|
| 225 |
source_name_to_use = source_name
|
|
|
|
| 262 |
vo_file_defined = True
|
| 263 |
else:
|
| 264 |
lines.append(f'\t\t\t\t\t<file id="{master_id_to_use}"/>')
|
| 265 |
+
elif is_graphic:
|
| 266 |
+
if not gfx_file_defined:
|
| 267 |
+
lines.append(f'\t\t\t\t\t<file id="{master_id_to_use}">')
|
| 268 |
+
lines.append(f'\t\t\t\t\t\t<name>{source_name_to_use}</name>')
|
| 269 |
+
lines.append(f'\t\t\t\t\t\t<pathurl>file://localhost/{source_name_to_use}.mov</pathurl>')
|
| 270 |
+
lines.append(f'\t\t\t\t\t\t<rate><timebase>{timebase}</timebase></rate>')
|
| 271 |
+
lines.append(f'\t\t\t\t\t\t<duration>8640000</duration>')
|
| 272 |
+
lines.append(f'\t\t\t\t\t\t<timecode>')
|
| 273 |
+
lines.append(f'\t\t\t\t\t\t\t<rate><timebase>{timebase}</timebase></rate>')
|
| 274 |
+
lines.append(f'\t\t\t\t\t\t\t<string>00:00:00:00</string>')
|
| 275 |
+
lines.append(f'\t\t\t\t\t\t\t<frame>0</frame>')
|
| 276 |
+
lines.append(f'\t\t\t\t\t\t\t<displayformat>NDF</displayformat>')
|
| 277 |
+
lines.append(f'\t\t\t\t\t\t</timecode>')
|
| 278 |
+
lines.append('\t\t\t\t\t\t<media>')
|
| 279 |
+
lines.append('\t\t\t\t\t\t\t<video><samplecharacteristics><width>1920</width><height>1080</height></samplecharacteristics></video>')
|
| 280 |
+
# Intentionally omitting audio block for graphics placeholder to keep NLE track structure clean
|
| 281 |
+
lines.append('\t\t\t\t\t\t</media>')
|
| 282 |
+
lines.append('\t\t\t\t\t</file>')
|
| 283 |
+
gfx_file_defined = True
|
| 284 |
+
else:
|
| 285 |
+
lines.append(f'\t\t\t\t\t<file id="{master_id_to_use}"/>')
|
| 286 |
else:
|
| 287 |
if not file_defined:
|
| 288 |
lines.append(f'\t\t\t\t\t<file id="{master_id_to_use}">')
|
|
|
|
| 308 |
lines.append('\t\t\t\t\t<marker>')
|
| 309 |
lines.append(f'\t\t\t\t\t\t<name>{clip_note}</name>')
|
| 310 |
lines.append(f'\t\t\t\t\t\t<in>{src_in_frames}</in>')
|
| 311 |
+
|
| 312 |
+
# Extends the marker duration to span the entire clip for VO and Graphics
|
| 313 |
+
if is_vo or is_graphic:
|
| 314 |
+
lines.append(f'\t\t\t\t\t\t<out>{src_out_frames}</out>')
|
| 315 |
+
else:
|
| 316 |
+
lines.append(f'\t\t\t\t\t\t<out>{src_in_frames}</out>')
|
| 317 |
+
|
| 318 |
lines.append('\t\t\t\t\t</marker>')
|
| 319 |
|
| 320 |
lines.append('\t\t\t\t</clipitem>')
|
|
|
|
| 331 |
|
| 332 |
timeline_head_frames = 0
|
| 333 |
for i, seg in enumerate(segments, 1):
|
| 334 |
+
seg_type = seg.get('type', 'clip')
|
| 335 |
+
is_vo = seg_type == 'vo' or ('text' in seg and seg_type != 'graphic')
|
| 336 |
+
is_graphic = seg_type == 'graphic'
|
| 337 |
|
| 338 |
+
if is_graphic:
|
| 339 |
+
# Graphics have no audio. We just advance the playhead so the timeline stays perfectly in sync!
|
| 340 |
+
duration_sec = float(seg.get('duration', 4.0))
|
| 341 |
+
duration_frames = seconds_to_frames(duration_sec, fps)
|
| 342 |
+
gap_frames = seconds_to_frames(float(seg.get('gap', 0.0)), fps)
|
| 343 |
+
timeline_head_frames += duration_frames + gap_frames
|
| 344 |
+
continue
|
| 345 |
+
|
| 346 |
if is_vo:
|
| 347 |
vo_text = seg.get('text', 'VO')
|
| 348 |
duration_sec = float(seg.get('duration', max(1.0, len(vo_text.split()) / 2.5)))
|
|
|
|
| 390 |
lines = [f"Sequence Transcript: {sequence_name}", "=" * 50, ""]
|
| 391 |
|
| 392 |
for i, seg in enumerate(segments, 1):
|
| 393 |
+
seg_type = seg.get('type', 'clip')
|
| 394 |
+
is_vo = seg_type == 'vo' or ('text' in seg and seg_type != 'graphic')
|
| 395 |
+
is_graphic = seg_type == 'graphic'
|
| 396 |
|
| 397 |
if is_vo:
|
| 398 |
vo_text = seg.get('text', 'VO')
|
| 399 |
duration = float(seg.get('duration', max(1.0, len(vo_text.split()) / 2.5)))
|
| 400 |
lines.append(f"Clip {i:02} | [GENERATED VOICE-OVER] | Est. Duration: {duration:.1f}s")
|
| 401 |
lines.append(f"Script: \"{vo_text}\"")
|
| 402 |
+
elif is_graphic:
|
| 403 |
+
gfx_text = seg.get('text', 'Graphic Text')
|
| 404 |
+
duration = float(seg.get('duration', 4.0))
|
| 405 |
+
lines.append(f"Clip {i:02} | [GRAPHIC CARD] | Est. Duration: {duration:.1f}s")
|
| 406 |
+
lines.append(f"Text: \"{gfx_text}\"")
|
| 407 |
else:
|
| 408 |
src_start_val = float(seg.get('src_start', seg.get('start', 0.0)))
|
| 409 |
src_end_val = float(seg.get('src_end', seg.get('end', 0.0)))
|
|
|
|
| 440 |
system_prompt = (
|
| 441 |
"You are an expert Documentary Senior Editor. Use the provided transcript JSON "
|
| 442 |
"(which includes Speaker IDs and word-level timestamps) to create a condensed story.\n\n"
|
| 443 |
+
"NEW CAPABILITIES:\n"
|
| 444 |
+
"1. You can generate Voice-Over (VO) to bridge gaps, introduce topics, or summarize.\n"
|
| 445 |
+
"2. You can generate Graphic Cards (Title Cards) to display text on screen (e.g., location, date, or chapter title).\n"
|
| 446 |
+
"CRITICAL: ONLY generate 'vo' or 'graphic' segments if the user explicitly requests voice over, narration, or graphic/title cards in their creative brief. Otherwise, ONLY use 'clip' segments.\n\n"
|
| 447 |
+
"Output ONLY a valid JSON array of segments. Every segment MUST be a 'clip', a 'vo', or a 'graphic'.\n\n"
|
| 448 |
"For 'clip' segments (extracting from the subject):\n"
|
| 449 |
"{\"type\": \"clip\", \"src_start\": 12.5, \"src_end\": 25.0, \"note\": \"Subject talks about X\", \"gap\": 0.0}\n"
|
| 450 |
"- IGNORE ALL INTERVIEWER COMMENTS.\n"
|
|
|
|
| 454 |
"{\"type\": \"vo\", \"text\": \"The journey began years ago...\", \"duration\": 3.5, \"gap\": 0.0}\n"
|
| 455 |
"- DURATION: Estimate duration accurately based on reading speed (approx 2.5 words per second).\n"
|
| 456 |
"- Keep VO concise and in the style of a documentary narrator.\n\n"
|
| 457 |
+
"For 'graphic' segments (generating text on screen):\n"
|
| 458 |
+
"{\"type\": \"graphic\", \"text\": \"Chapter 1: The Beginning\", \"duration\": 4.0, \"gap\": 0.0}\n\n"
|
| 459 |
"PACING: Group related clips. Add a 'gap' (in seconds) between distinct ideas."
|
| 460 |
)
|
| 461 |
|
|
|
|
| 555 |
* Upload your file here (video or audio).
|
| 556 |
* Set your timeline FPS, Source Timecode, and transcription quality in the sidebar.
|
| 557 |
* Junior Editor will transcribe and separate speakers. You can then instruct it to find engaging bits or construct a narrative.
|
| 558 |
+
* **Note: If you want me to write voice over suggestions or title cards, please request it.**
|
| 559 |
* It will create an EDL or XML to import back into your editing software (Resolve, Premiere).
|
| 560 |
* **For Multicam Workflows:** Use the **XML** option in the sidebar and enter the **exact name** of your Multicam Sequence.
|
| 561 |
* **Resolve Users:** Uncheck "Automatically import source clips into media pool" during import.
|
|
|
|
| 697 |
st.markdown(f"**{seg['speaker']}:** {seg['text']}")
|
| 698 |
|
| 699 |
st.subheader("Your Instruction")
|
| 700 |
+
brief = st.text_area("What should the Junior Editor do?", placeholder="e.g. Find the most engaging bits. If you want me to write voice over suggestions or title cards, please request it.")
|
| 701 |
|
| 702 |
if st.button("Generate Edit"):
|
| 703 |
if not ACTIVE_GEMINI_KEY:
|
|
|
|
| 728 |
|
| 729 |
# Save to session state so buttons don't disappear on click
|
| 730 |
st.session_state.edit_generated = True
|
| 731 |
+
st.session_state.revision_count = 1
|
| 732 |
+
st.session_state.base_filename = safe_filename
|
| 733 |
st.session_state.final_output = final_output
|
| 734 |
st.session_state.ext = ext
|
| 735 |
st.session_state.transcript_txt = transcript_txt
|
|
|
|
| 752 |
|
| 753 |
st.divider()
|
| 754 |
st.subheader("Revise Edit")
|
| 755 |
+
revision_brief = st.text_area("Want changes? Tell Junior Editor:", placeholder="e.g. Make it twice as long, add a graphic card for location, or write a VO intro.")
|
| 756 |
|
| 757 |
if st.button("Apply Revisions"):
|
| 758 |
with st.spinner("Junior Editor is revising the edit..."):
|
|
|
|
| 767 |
)
|
| 768 |
|
| 769 |
if new_edl_segments:
|
| 770 |
+
# Increment version count
|
| 771 |
+
st.session_state.revision_count = st.session_state.get("revision_count", 1) + 1
|
| 772 |
+
rev_seq_name = f"{final_source_name} V{st.session_state.revision_count}"
|
| 773 |
+
|
| 774 |
if export_format == "EDL":
|
| 775 |
+
final_output = generate_cmx_edl(rev_seq_name, new_edl_segments, final_source_name, fps, offset_frames)
|
| 776 |
ext = "edl"
|
| 777 |
else:
|
| 778 |
+
final_output = generate_xml(rev_seq_name, new_edl_segments, final_source_name, fps, offset_frames)
|
| 779 |
ext = "xml"
|
| 780 |
|
| 781 |
+
transcript_txt = generate_transcript_txt(rev_seq_name, new_edl_segments, fps, offset_frames, st.session_state.transcript)
|
| 782 |
|
| 783 |
st.session_state.final_output = final_output
|
| 784 |
st.session_state.ext = ext
|
|
|
|
| 786 |
st.session_state.used_model = used_model
|
| 787 |
st.session_state.used_endpoint = used_endpoint
|
| 788 |
st.session_state.edl_segments = new_edl_segments
|
| 789 |
+
|
| 790 |
+
# Update safe filename for the download buttons to show _V2, _V3, etc.
|
| 791 |
+
st.session_state.safe_filename = f"{st.session_state.base_filename}_V{st.session_state.revision_count}"
|
| 792 |
+
|
| 793 |
st.rerun()
|
| 794 |
|
| 795 |
|
| 796 |
+
|