Riolit commited on
Commit
cc42e1e
·
verified ·
1 Parent(s): 03a589c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -12
app.py CHANGED
@@ -1,29 +1,111 @@
1
  import gradio as gr
2
- import os, json, tempfile, shutil, subprocess
3
  from pathlib import Path
4
 
 
5
  def run(cmd):
6
  r = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
7
  if r.returncode != 0:
8
- raise RuntimeError(r.stderr)
9
  return r.stdout
10
 
11
- def ffprobe_duration(path):
12
- info = run(["ffprobe","-v","error","-print_format","json","-show_format","-show_streams", path])
13
- meta = json.loads(info)
 
 
 
 
14
  if "format" in meta and "duration" in meta["format"]:
15
  return float(meta["format"]["duration"])
16
  dur = 0.0
17
  for s in meta.get("streams", []):
18
  if "duration" in s:
19
- dur = max(dur, float(s["duration"]))
 
 
 
20
  if dur <= 0:
21
- raise ValueError("Could not determine duration")
22
  return dur
23
 
24
- def has_audio(path):
25
- info = run(["ffprobe","-v","error","-print_format","json","-show_streams", path])
26
- meta = json.loads(info)
27
- return any(s.get("codec_type")=="audio" for s in meta.get("streams", []))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
- def split_and
 
1
  import gradio as gr
2
+ import os, json, shutil, tempfile, subprocess
3
  from pathlib import Path
4
 
5
+ # ---------- helpers ----------
6
  def run(cmd):
7
  r = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
8
  if r.returncode != 0:
9
+ raise RuntimeError(r.stderr.strip())
10
  return r.stdout
11
 
12
+ def ffprobe_json(path):
13
+ return json.loads(run([
14
+ "ffprobe", "-v", "error", "-print_format", "json",
15
+ "-show_format", "-show_streams", path
16
+ ]))
17
+
18
+ def get_duration_seconds(meta):
19
  if "format" in meta and "duration" in meta["format"]:
20
  return float(meta["format"]["duration"])
21
  dur = 0.0
22
  for s in meta.get("streams", []):
23
  if "duration" in s:
24
+ try:
25
+ dur = max(dur, float(s["duration"]))
26
+ except:
27
+ pass
28
  if dur <= 0:
29
+ raise ValueError("Could not determine video duration.")
30
  return dur
31
 
32
+ def has_audio_stream(meta):
33
+ return any(s.get("codec_type") == "audio" for s in meta.get("streams", []))
34
+
35
+ # ---------- core logic ----------
36
+ def split_and_zip(video_path, n_parts):
37
+ if video_path is None:
38
+ raise gr.Error("Please upload a video.")
39
+ try:
40
+ n_parts = int(n_parts)
41
+ except:
42
+ raise gr.Error("Number of parts must be an integer.")
43
+ if n_parts < 1:
44
+ raise gr.Error("Number of parts must be ≥ 1.")
45
+
46
+ # probe
47
+ meta = ffprobe_json(video_path)
48
+ duration = get_duration_seconds(meta)
49
+ audio_present = has_audio_stream(meta)
50
+
51
+ stem = Path(video_path).stem
52
+
53
+ # temp working dir
54
+ workdir = tempfile.mkdtemp(prefix="split_")
55
+ outdir = Path(workdir) / f"{stem}_parts"
56
+ outdir.mkdir(parents=True, exist_ok=True)
57
+
58
+ seg_len = duration / n_parts
59
+ logs = [f"Input: {Path(video_path).name} ({duration:.3f}s) | Parts: {n_parts} | Segment ~ {seg_len:.3f}s"]
60
+
61
+ # split with ffmpeg (re-encode for clean frame-accurate cuts)
62
+ for i in range(n_parts):
63
+ start = max(0.0, min(i * seg_len, duration))
64
+ length = duration - start if i == n_parts - 1 else seg_len
65
+ length = max(0.0, min(length, duration - start))
66
+
67
+ out_file = outdir / f"{stem}_part{i+1:02d}.mp4"
68
+ cmd = [
69
+ "ffmpeg", "-hide_banner", "-loglevel", "error", "-y",
70
+ "-ss", f"{start:.6f}",
71
+ "-i", video_path,
72
+ "-t", f"{length:.6f}",
73
+ "-c:v", "libx264", "-preset", "veryfast", "-crf", "18",
74
+ "-movflags", "+faststart"
75
+ ]
76
+ if audio_present:
77
+ cmd += ["-c:a", "aac", "-b:a", "128k"]
78
+ else:
79
+ cmd += ["-an"]
80
+ cmd += [str(out_file)]
81
+ try:
82
+ run(cmd)
83
+ logs.append(f"✔️ Saved {out_file.name} ({length:.3f}s)")
84
+ except Exception as e:
85
+ logs.append(f"❌ Failed part {i+1}: {e}")
86
+
87
+ # zip
88
+ zip_path = str(outdir) + ".zip"
89
+ shutil.make_archive(str(outdir), "zip", root_dir=outdir)
90
+
91
+ logs.append(f"ZIP ready: {Path(zip_path).name}")
92
+ return zip_path, "\n".join(logs)
93
+
94
+ # ---------- UI ----------
95
+ with gr.Blocks(title="Video Splitter") as demo:
96
+ gr.Markdown("## Split a video into N equal parts and download a ZIP\n"
97
+ "Upload a video, choose how many parts, and get a zipped folder of clips.")
98
+
99
+ with gr.Row():
100
+ video = gr.File(label="Upload video", file_count="single", type="filepath")
101
+ parts = gr.Number(label="Number of parts", value=10, precision=0)
102
+
103
+ with gr.Row():
104
+ run_btn = gr.Button("Split Video", variant="primary")
105
+
106
+ out_zip = gr.File(label="Download ZIP")
107
+ log_box = gr.Textbox(label="Process Log", lines=10)
108
+
109
+ run_btn.click(split_and_zip, inputs=[video, parts], outputs=[out_zip, log_box])
110
 
111
+ demo.launch()