Update app.py
Browse files
app.py
CHANGED
|
@@ -518,9 +518,11 @@ def step1_extract(
|
|
| 518 |
prog_html: str,
|
| 519 |
):
|
| 520 |
if not video or not video.name:
|
| 521 |
-
|
|
|
|
| 522 |
if not FFMPEG or not FFPROBE:
|
| 523 |
-
|
|
|
|
| 524 |
|
| 525 |
work = Path(tempfile.mkdtemp(prefix="vid2img_"))
|
| 526 |
raw_dir = work / "frames_raw"
|
|
@@ -528,9 +530,24 @@ def step1_extract(
|
|
| 528 |
|
| 529 |
prefix = sanitize_prefix(prefix_in) or Path(video.name).stem
|
| 530 |
|
| 531 |
-
|
| 532 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 533 |
|
|
|
|
| 534 |
pattern = str(raw_dir / f"{prefix}_%05d.{out_format}")
|
| 535 |
cmd = build_ffmpeg_extract(
|
| 536 |
input_path=video.name,
|
|
@@ -548,41 +565,67 @@ def step1_extract(
|
|
| 548 |
scene_thresh=scene_thresh,
|
| 549 |
out_pattern=pattern,
|
| 550 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 551 |
cmd_preview = " ".join([s if " " not in s else f'"{s}"' for s in cmd])
|
| 552 |
|
| 553 |
-
proc = subprocess.Popen(
|
|
|
|
|
|
|
| 554 |
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
while True:
|
| 559 |
line = proc.stderr.readline()
|
| 560 |
if not line and proc.poll() is not None:
|
| 561 |
break
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 569 |
ret = proc.wait()
|
| 570 |
|
| 571 |
frames = sorted(raw_dir.glob(f"{prefix}_*.{out_format}"), key=_natural_key)
|
| 572 |
|
| 573 |
-
#
|
| 574 |
if len(frames) <= 100:
|
| 575 |
-
gallery = [str(p) for p in frames]
|
| 576 |
else:
|
| 577 |
-
gallery = sample_paths(frames, 100)
|
| 578 |
|
| 579 |
zip_path = work / "frames.zip"
|
| 580 |
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
| 581 |
for p in frames:
|
| 582 |
zf.write(p, p.name)
|
| 583 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 584 |
details = f"Frames extracted: {len(frames)} | Saved to: {raw_dir}"
|
| 585 |
-
|
| 586 |
|
| 587 |
# ───────────────── Upscale (Step 2) — supports uploaded images OR frames from Step 1
|
| 588 |
|
|
@@ -628,8 +671,10 @@ def step2_process_next_batch(
|
|
| 628 |
up_src_paths, up_out_dir, up_done_idx, up_total,
|
| 629 |
ui_model_name, outscale, tile, precision, denoise_strength, face_enhance, batch_size,
|
| 630 |
):
|
|
|
|
| 631 |
if not up_src_paths or not up_out_dir:
|
| 632 |
-
|
|
|
|
| 633 |
|
| 634 |
model_id = map_ui_model_to_internal(ui_model_name)
|
| 635 |
scale = clamp_scale_for_model(int(outscale or 4), model_id)
|
|
@@ -667,10 +712,16 @@ def step2_process_next_batch(
|
|
| 667 |
zip_file = _save_zip_of_dir(out_dir, zip_path)
|
| 668 |
prog = render_progress(100.0, "All images processed")
|
| 669 |
details = f"Done. Total upscaled: {len(list(out_dir.glob('*.jpg')))+len(list(out_dir.glob('*.png')))}"
|
| 670 |
-
|
|
|
|
| 671 |
|
|
|
|
|
|
|
| 672 |
processed_now = 0
|
| 673 |
-
|
|
|
|
|
|
|
|
|
|
| 674 |
try:
|
| 675 |
with Image.open(fp) as im:
|
| 676 |
img = im.convert("RGB")
|
|
@@ -681,27 +732,41 @@ def step2_process_next_batch(
|
|
| 681 |
cv_img, has_aligned=False, only_center_face=False, paste_back=True
|
| 682 |
)
|
| 683 |
else:
|
|
|
|
| 684 |
output, _ = upsampler.enhance(cv_img, outscale=scale, denoise_strength=float(denoise_strength or 0.5))
|
| 685 |
|
| 686 |
Image.fromarray(output).save(out_dir / (Path(fp).stem + ".jpg"), quality=95)
|
| 687 |
|
| 688 |
except Exception as e:
|
| 689 |
print("Upscale error:", e)
|
| 690 |
-
processed_now += 1
|
| 691 |
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 698 |
|
|
|
|
|
|
|
| 699 |
gallery = _build_gallery_from_dir(out_dir, 30)
|
| 700 |
zip_path = Path(out_dir.parent) / "upscaled.zip"
|
| 701 |
zip_file = _save_zip_of_dir(out_dir, zip_path)
|
| 702 |
-
return gallery, zip_file, label, prog, next_idx, up_out_dir
|
| 703 |
-
|
| 704 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 705 |
|
| 706 |
|
| 707 |
def save_uploaded_images(files: List[gr.File] | None, prefix: str = "upload") -> Tuple[List[Path], Path]:
|
|
@@ -876,9 +941,17 @@ def build_ffmpeg_encode(frames_dir: str, prefix: str, fps: float, fmt: str, incl
|
|
| 876 |
return args
|
| 877 |
|
| 878 |
|
| 879 |
-
def step3_encode(
|
| 880 |
-
|
| 881 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 882 |
# Choose frames source: uploaded takes priority
|
| 883 |
frames_dir = frames_dir_state
|
| 884 |
prefix = prefix_state
|
|
@@ -887,32 +960,68 @@ def step3_encode(frames_dir_state: str | None, prefix_state: str | None, orig_vi
|
|
| 887 |
if detected:
|
| 888 |
prefix = detected
|
| 889 |
if not frames_dir or not prefix:
|
| 890 |
-
|
|
|
|
| 891 |
|
| 892 |
fps = float(fps or 30.0)
|
| 893 |
orig_path = uploaded_audio_video.name if uploaded_audio_video else (orig_video.name if orig_video else None)
|
| 894 |
|
|
|
|
| 895 |
cmd = build_ffmpeg_encode(frames_dir, prefix, fps, fmt, include_audio, orig_path)
|
| 896 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 897 |
|
| 898 |
last_html = prog_html
|
|
|
|
|
|
|
| 899 |
while True:
|
| 900 |
line = proc.stderr.readline()
|
| 901 |
if not line and proc.poll() is not None:
|
| 902 |
break
|
| 903 |
-
if int(time.time()*10) % 5 == 0:
|
| 904 |
-
last_html = render_progress(50.0, "Encoding…")
|
| 905 |
-
ret = proc.wait()
|
| 906 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 907 |
out_file = Path(frames_dir) / ("output.mp4" if fmt in ("h264", "h265") else "output.webm")
|
|
|
|
| 908 |
if ret != 0 or not out_file.exists():
|
| 909 |
try:
|
| 910 |
err = proc.stderr.read() if proc.stderr else ""
|
| 911 |
except Exception:
|
| 912 |
err = ""
|
| 913 |
-
|
| 914 |
-
|
| 915 |
-
|
|
|
|
|
|
|
| 916 |
|
| 917 |
# ───────────────── Quick Mode — one click: All frames → Upscale ×4 → MP4 (audio)
|
| 918 |
|
|
@@ -1061,7 +1170,29 @@ def build_ui():
|
|
| 1061 |
cmd_preview = gr.Textbox(label="ffmpeg command", lines=4, elem_classes=["cmdbox"])
|
| 1062 |
if MISSING_MSG:
|
| 1063 |
gr.Markdown(f"<span style='color:#b45309'>{MISSING_MSG}</span>")
|
| 1064 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1065 |
def update_estimate(vfile, mode_val, evs, nth, exfps, st, et):
|
| 1066 |
if not vfile or not getattr(vfile, 'name', None):
|
| 1067 |
return "Estimated output: —"
|
|
|
|
| 518 |
prog_html: str,
|
| 519 |
):
|
| 520 |
if not video or not video.name:
|
| 521 |
+
yield None, None, "Upload a video.", "", prog_html, None, None, None
|
| 522 |
+
return
|
| 523 |
if not FFMPEG or not FFPROBE:
|
| 524 |
+
yield None, None, "FFmpeg missing. See note below.", MISSING_MSG, prog_html, None, None, None
|
| 525 |
+
return
|
| 526 |
|
| 527 |
work = Path(tempfile.mkdtemp(prefix="vid2img_"))
|
| 528 |
raw_dir = work / "frames_raw"
|
|
|
|
| 530 |
|
| 531 |
prefix = sanitize_prefix(prefix_in) or Path(video.name).stem
|
| 532 |
|
| 533 |
+
# Duration for progress %
|
| 534 |
+
vinfo = parse_video_info(ffprobe_json(video.name))
|
| 535 |
+
full_duration = float(vinfo.get("duration") or 0.0)
|
| 536 |
+
|
| 537 |
+
# If trimming, adjust expected duration window
|
| 538 |
+
def _parse_ts(ts: str) -> float:
|
| 539 |
+
if not ts: return 0.0
|
| 540 |
+
h, m, s = ts.split(":") if ":" in ts else ("0","0",ts)
|
| 541 |
+
return float(h)*3600 + float(m)*60 + float(s)
|
| 542 |
+
|
| 543 |
+
st_s = _parse_ts((start_time or "").strip())
|
| 544 |
+
et_s = _parse_ts((end_time or "").strip())
|
| 545 |
+
if full_duration and st_s > 0:
|
| 546 |
+
full_duration = max(0.0, full_duration - st_s)
|
| 547 |
+
if full_duration and et_s > 0 and et_s < (vinfo.get("duration") or 0):
|
| 548 |
+
full_duration = max(0.0, min(full_duration, et_s))
|
| 549 |
|
| 550 |
+
# Build command
|
| 551 |
pattern = str(raw_dir / f"{prefix}_%05d.{out_format}")
|
| 552 |
cmd = build_ffmpeg_extract(
|
| 553 |
input_path=video.name,
|
|
|
|
| 565 |
scene_thresh=scene_thresh,
|
| 566 |
out_pattern=pattern,
|
| 567 |
)
|
| 568 |
+
|
| 569 |
+
# Inject progress reporting
|
| 570 |
+
# ffmpeg will write key=value lines including out_time to stderr
|
| 571 |
+
cmd = [cmd[0], "-progress", "pipe:2"] + cmd[1:]
|
| 572 |
cmd_preview = " ".join([s if " " not in s else f'"{s}"' for s in cmd])
|
| 573 |
|
| 574 |
+
proc = subprocess.Popen(
|
| 575 |
+
cmd, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL, text=True, bufsize=1
|
| 576 |
+
)
|
| 577 |
|
| 578 |
+
# Stream updates
|
| 579 |
+
last_pct = 0.0
|
| 580 |
+
gallery_preview = []
|
| 581 |
while True:
|
| 582 |
line = proc.stderr.readline()
|
| 583 |
if not line and proc.poll() is not None:
|
| 584 |
break
|
| 585 |
+
|
| 586 |
+
line = (line or "").strip()
|
| 587 |
+
# out_time is in HH:MM:SS.microsec
|
| 588 |
+
if line.startswith("out_time=") and full_duration > 0:
|
| 589 |
+
t = line.split("=", 1)[1]
|
| 590 |
+
# Convert HH:MM:SS.xx to seconds
|
| 591 |
+
try:
|
| 592 |
+
h, m, s = t.split(":")
|
| 593 |
+
secs = float(h) * 3600 + float(m) * 60 + float(s)
|
| 594 |
+
except Exception:
|
| 595 |
+
secs = 0.0
|
| 596 |
+
pct = max(0.0, min(100.0, (secs / full_duration) * 100.0))
|
| 597 |
+
if pct - last_pct >= 1.0 or pct in (0.0, 100.0):
|
| 598 |
+
last_pct = pct
|
| 599 |
+
# Lightweight live gallery sample for feel; not required
|
| 600 |
+
gallery_preview = sample_paths(sorted(raw_dir.glob(f"{prefix}_*.{out_format}"), key=_natural_key), 36)
|
| 601 |
+
yield gallery_preview, None, "Extracting…", cmd_preview, render_progress(pct, f"Extracting {pct:.0f}%"), None, str(raw_dir), prefix
|
| 602 |
+
|
| 603 |
ret = proc.wait()
|
| 604 |
|
| 605 |
frames = sorted(raw_dir.glob(f"{prefix}_*.{out_format}"), key=_natural_key)
|
| 606 |
|
| 607 |
+
# Show all if ≤100, else sample 100
|
| 608 |
if len(frames) <= 100:
|
| 609 |
+
gallery = [str(p) for p in frames]
|
| 610 |
else:
|
| 611 |
+
gallery = sample_paths(frames, 100)
|
| 612 |
|
| 613 |
zip_path = work / "frames.zip"
|
| 614 |
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
| 615 |
for p in frames:
|
| 616 |
zf.write(p, p.name)
|
| 617 |
|
| 618 |
+
if ret != 0 or not frames:
|
| 619 |
+
err = ""
|
| 620 |
+
try:
|
| 621 |
+
err = proc.stderr.read() if proc.stderr else ""
|
| 622 |
+
except Exception:
|
| 623 |
+
pass
|
| 624 |
+
yield gallery, None, f"Extraction failed.\n\n{err}", cmd_preview, render_progress(0.0, "Failed"), None, str(raw_dir), prefix
|
| 625 |
+
return
|
| 626 |
+
|
| 627 |
details = f"Frames extracted: {len(frames)} | Saved to: {raw_dir}"
|
| 628 |
+
yield gallery, str(zip_path), details, cmd_preview, render_progress(100.0, f"Extracted {len(frames)} frames"), [str(p) for p in frames], str(raw_dir), prefix
|
| 629 |
|
| 630 |
# ───────────────── Upscale (Step 2) — supports uploaded images OR frames from Step 1
|
| 631 |
|
|
|
|
| 671 |
up_src_paths, up_out_dir, up_done_idx, up_total,
|
| 672 |
ui_model_name, outscale, tile, precision, denoise_strength, face_enhance, batch_size,
|
| 673 |
):
|
| 674 |
+
# Turn this into a generator that streams progress
|
| 675 |
if not up_src_paths or not up_out_dir:
|
| 676 |
+
yield None, None, "Load sources first.", render_progress(0.0, "Idle"), up_done_idx, up_out_dir
|
| 677 |
+
return
|
| 678 |
|
| 679 |
model_id = map_ui_model_to_internal(ui_model_name)
|
| 680 |
scale = clamp_scale_for_model(int(outscale or 4), model_id)
|
|
|
|
| 712 |
zip_file = _save_zip_of_dir(out_dir, zip_path)
|
| 713 |
prog = render_progress(100.0, "All images processed")
|
| 714 |
details = f"Done. Total upscaled: {len(list(out_dir.glob('*.jpg')))+len(list(out_dir.glob('*.png')))}"
|
| 715 |
+
yield gallery, zip_file, details, prog, start, up_out_dir
|
| 716 |
+
return
|
| 717 |
|
| 718 |
+
batch_paths = up_src_paths[start:end]
|
| 719 |
+
total_in_batch = len(batch_paths)
|
| 720 |
processed_now = 0
|
| 721 |
+
|
| 722 |
+
# For ETA
|
| 723 |
+
t0 = time.time()
|
| 724 |
+
for idx, fp in enumerate(batch_paths, start=1):
|
| 725 |
try:
|
| 726 |
with Image.open(fp) as im:
|
| 727 |
img = im.convert("RGB")
|
|
|
|
| 732 |
cv_img, has_aligned=False, only_center_face=False, paste_back=True
|
| 733 |
)
|
| 734 |
else:
|
| 735 |
+
# denoise_strength only applies to general-x4v3, but harmless otherwise
|
| 736 |
output, _ = upsampler.enhance(cv_img, outscale=scale, denoise_strength=float(denoise_strength or 0.5))
|
| 737 |
|
| 738 |
Image.fromarray(output).save(out_dir / (Path(fp).stem + ".jpg"), quality=95)
|
| 739 |
|
| 740 |
except Exception as e:
|
| 741 |
print("Upscale error:", e)
|
|
|
|
| 742 |
|
| 743 |
+
processed_now = idx
|
| 744 |
+
# Progress & ETA for THIS batch
|
| 745 |
+
pct_batch = (processed_now / total_in_batch) * 100.0
|
| 746 |
+
elapsed = time.time() - t0
|
| 747 |
+
secs_per_img = elapsed / max(1, processed_now)
|
| 748 |
+
remaining_imgs = total_in_batch - processed_now
|
| 749 |
+
eta = remaining_imgs * secs_per_img
|
| 750 |
+
label = (f"Batch: {processed_now}/{total_in_batch} · "
|
| 751 |
+
f"~{eta:.1f}s ETA · global {start+processed_now}/{up_total} "
|
| 752 |
+
f"(x{scale}, model={ui_model_name}, denoise={denoise_strength}, face={face_enhance})")
|
| 753 |
+
|
| 754 |
+
gallery = _build_gallery_from_dir(out_dir, 30)
|
| 755 |
+
zip_path = Path(out_dir.parent) / "upscaled.zip"
|
| 756 |
+
zip_file = _save_zip_of_dir(out_dir, zip_path)
|
| 757 |
+
yield gallery, zip_file, label, render_progress(pct_batch, f"Upscaling… {pct_batch:.0f}% (this batch)"), start+processed_now, up_out_dir
|
| 758 |
|
| 759 |
+
# Batch complete — final emit for this click
|
| 760 |
+
next_idx = end
|
| 761 |
gallery = _build_gallery_from_dir(out_dir, 30)
|
| 762 |
zip_path = Path(out_dir.parent) / "upscaled.zip"
|
| 763 |
zip_file = _save_zip_of_dir(out_dir, zip_path)
|
|
|
|
|
|
|
| 764 |
|
| 765 |
+
# Total (global) percentage across all sources
|
| 766 |
+
pct_global = (next_idx / up_total) * 100.0 if up_total else 100.0
|
| 767 |
+
final_label = (f"Processed batch {total_in_batch} image(s). "
|
| 768 |
+
f"{next_idx}/{up_total} done (global {pct_global:.0f}%).")
|
| 769 |
+
yield gallery, zip_file, final_label, render_progress(pct_global, "Upscaling… (global)"), next_idx, up_out_dir
|
| 770 |
|
| 771 |
|
| 772 |
def save_uploaded_images(files: List[gr.File] | None, prefix: str = "upload") -> Tuple[List[Path], Path]:
|
|
|
|
| 941 |
return args
|
| 942 |
|
| 943 |
|
| 944 |
+
def step3_encode(
|
| 945 |
+
frames_dir_state: str | None,
|
| 946 |
+
prefix_state: str | None,
|
| 947 |
+
orig_video: gr.File | None,
|
| 948 |
+
fps: float | None,
|
| 949 |
+
fmt: str,
|
| 950 |
+
include_audio: bool,
|
| 951 |
+
prog_html: str,
|
| 952 |
+
uploaded_frames: List[gr.File] | None,
|
| 953 |
+
uploaded_audio_video: gr.File | None
|
| 954 |
+
):
|
| 955 |
# Choose frames source: uploaded takes priority
|
| 956 |
frames_dir = frames_dir_state
|
| 957 |
prefix = prefix_state
|
|
|
|
| 960 |
if detected:
|
| 961 |
prefix = detected
|
| 962 |
if not frames_dir or not prefix:
|
| 963 |
+
yield None, "No frames available. Upload frames (ZIP/images) or run Step 1.", prog_html
|
| 964 |
+
return
|
| 965 |
|
| 966 |
fps = float(fps or 30.0)
|
| 967 |
orig_path = uploaded_audio_video.name if uploaded_audio_video else (orig_video.name if orig_video else None)
|
| 968 |
|
| 969 |
+
# Build ffmpeg command
|
| 970 |
cmd = build_ffmpeg_encode(frames_dir, prefix, fps, fmt, include_audio, orig_path)
|
| 971 |
+
|
| 972 |
+
# Inject progress reporting
|
| 973 |
+
cmd.insert(1, "-progress")
|
| 974 |
+
cmd.insert(2, "pipe:2")
|
| 975 |
+
|
| 976 |
+
# Try to estimate total frames for progress %
|
| 977 |
+
total_frames = len(list(Path(frames_dir).glob(f"{prefix}_*.jpg"))) \
|
| 978 |
+
+ len(list(Path(frames_dir).glob(f"{prefix}_*.png")))
|
| 979 |
+
|
| 980 |
+
proc = subprocess.Popen(
|
| 981 |
+
cmd,
|
| 982 |
+
stderr=subprocess.PIPE,
|
| 983 |
+
stdout=subprocess.DEVNULL,
|
| 984 |
+
text=True,
|
| 985 |
+
bufsize=1,
|
| 986 |
+
cwd=frames_dir
|
| 987 |
+
)
|
| 988 |
|
| 989 |
last_html = prog_html
|
| 990 |
+
current_frame = 0
|
| 991 |
+
|
| 992 |
while True:
|
| 993 |
line = proc.stderr.readline()
|
| 994 |
if not line and proc.poll() is not None:
|
| 995 |
break
|
|
|
|
|
|
|
|
|
|
| 996 |
|
| 997 |
+
if "frame=" in line:
|
| 998 |
+
try:
|
| 999 |
+
# parse `frame=123`
|
| 1000 |
+
current_frame = int(line.strip().split("=")[-1])
|
| 1001 |
+
except Exception:
|
| 1002 |
+
pass
|
| 1003 |
+
|
| 1004 |
+
if total_frames > 0:
|
| 1005 |
+
pct = min(100.0, (current_frame / total_frames) * 100.0)
|
| 1006 |
+
last_html = render_progress(pct, f"Encoding… {current_frame}/{total_frames} frames")
|
| 1007 |
+
yield None, f"Encoding in progress… {current_frame}/{total_frames}", last_html
|
| 1008 |
+
else:
|
| 1009 |
+
last_html = render_progress(50.0, "Encoding…")
|
| 1010 |
+
yield None, "Encoding in progress…", last_html
|
| 1011 |
+
|
| 1012 |
+
ret = proc.wait()
|
| 1013 |
out_file = Path(frames_dir) / ("output.mp4" if fmt in ("h264", "h265") else "output.webm")
|
| 1014 |
+
|
| 1015 |
if ret != 0 or not out_file.exists():
|
| 1016 |
try:
|
| 1017 |
err = proc.stderr.read() if proc.stderr else ""
|
| 1018 |
except Exception:
|
| 1019 |
err = ""
|
| 1020 |
+
yield None, f"Encoding failed.\n\n{err}", last_html
|
| 1021 |
+
return
|
| 1022 |
+
|
| 1023 |
+
yield str(out_file), f"Video created: {out_file.name}", render_progress(100.0, "Encoding complete")
|
| 1024 |
+
|
| 1025 |
|
| 1026 |
# ───────────────── Quick Mode — one click: All frames → Upscale ×4 → MP4 (audio)
|
| 1027 |
|
|
|
|
| 1170 |
cmd_preview = gr.Textbox(label="ffmpeg command", lines=4, elem_classes=["cmdbox"])
|
| 1171 |
if MISSING_MSG:
|
| 1172 |
gr.Markdown(f"<span style='color:#b45309'>{MISSING_MSG}</span>")
|
| 1173 |
+
# Wire behavior: enable/disable param groups depending on mode / format
|
| 1174 |
+
def _toggle_params(mode_val, fmt):
|
| 1175 |
+
return (
|
| 1176 |
+
gr.update(visible=(mode_val == "Every N seconds")),
|
| 1177 |
+
gr.update(visible=(mode_val == "Every Nth frame")),
|
| 1178 |
+
gr.update(visible=(mode_val == "Exact FPS")),
|
| 1179 |
+
gr.update(visible=(fmt == "jpg")),
|
| 1180 |
+
gr.update(visible=(fmt == "png")),
|
| 1181 |
+
)
|
| 1182 |
+
|
| 1183 |
+
mode.change(
|
| 1184 |
+
_toggle_params,
|
| 1185 |
+
inputs=[mode, out_format],
|
| 1186 |
+
outputs=[every_seconds, nth_frame, exact_fps, jpg_quality, png_level],
|
| 1187 |
+
)
|
| 1188 |
+
out_format.change(
|
| 1189 |
+
_toggle_params,
|
| 1190 |
+
inputs=[mode, out_format],
|
| 1191 |
+
outputs=[every_seconds, nth_frame, exact_fps, jpg_quality, png_level],
|
| 1192 |
+
)
|
| 1193 |
+
# Initialize visibility
|
| 1194 |
+
demo.load(_toggle_params, inputs=[mode, out_format], outputs=[every_seconds, nth_frame, exact_fps, jpg_quality, png_level])
|
| 1195 |
+
|
| 1196 |
def update_estimate(vfile, mode_val, evs, nth, exfps, st, et):
|
| 1197 |
if not vfile or not getattr(vfile, 'name', None):
|
| 1198 |
return "Estimated output: —"
|