RandomPersonRR commited on
Commit
b8f0bd6
·
verified ·
1 Parent(s): 2a111b0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +88 -289
app.py CHANGED
@@ -4,8 +4,8 @@ import os
4
  import re
5
  import shutil
6
  import uuid
7
- import html
8
  from pathlib import Path
 
9
  from typing import Optional, Tuple
10
 
11
  import gradio as gr
@@ -33,55 +33,19 @@ ACCEL = "auto"
33
 
34
  FFMPEG_TIME_RE = re.compile(r"time=(\d+):(\d+):(\d+\.\d+)")
35
 
36
- # Ensure fdkaac executable bit if it exists in cwd
37
  FDKAAC_PATH = Path("./fdkaac")
38
- try:
39
- if FDKAAC_PATH.exists():
40
- FDKAAC_PATH.chmod(FDKAAC_PATH.stat().st_mode | 0o111)
41
- except Exception:
42
- pass
43
 
44
  # -------------------------
45
  # Helpers
46
  # -------------------------
47
  def is_audio_file(path: str) -> bool:
48
- ext = Path(path).suffix.lower()
49
- return ext in {".mp3", ".m4a", ".wav", ".aac", ".oga", ".ogg"}
50
-
51
- def preview_html(percent: float, step: str, media_src: Optional[str] = None) -> str:
52
- """Return HTML containing media preview (video or audio) with overlayed progress/step."""
53
- pct = max(0.0, min(100.0, percent))
54
- esc_step = html.escape(str(step))
55
- media_tag = ""
56
- if media_src:
57
- esc_src = html.escape(str(media_src))
58
- if is_audio_file(media_src):
59
- media_tag = f'<audio src="{esc_src}" controls style="width:100%;display:block;"></audio>'
60
- else:
61
- media_tag = f'<video src="{esc_src}" controls style="width:100%;height:auto;display:block;"></video>'
62
- else:
63
- media_tag = (
64
- '<div style="width:100%;height:320px;display:flex;align-items:center;justify-content:center;'
65
- 'background:#0b0b0b;color:#fff;font-size:16px;">Preview will appear here once available</div>'
66
- )
67
-
68
- return f'''
69
- <div style="position:relative;border-radius:8px;overflow:hidden;border:1px solid #ddd;">
70
- {media_tag}
71
- <div style="position:absolute;left:8px;right:8px;bottom:12px;pointer-events:none;">
72
- <div style="background:rgba(0,0,0,0.52);padding:10px;border-radius:8px;color:#fff;font-family:system-ui,Segoe UI,Roboto,Arial;">
73
- <div style="font-size:13px;margin-bottom:8px;"><strong>{esc_step}</strong></div>
74
- <div style="height:12px;background:#222;border-radius:6px;overflow:hidden;">
75
- <div style="width:{pct:.2f}%;height:100%;background:linear-gradient(90deg,#21d4fd,#b721ff);"></div>
76
- </div>
77
- <div style="font-size:12px;margin-top:6px;color:#ddd;">{pct:.1f}%</div>
78
- </div>
79
- </div>
80
- </div>
81
- '''
82
 
83
  async def run_command_capture(cmd, cwd=None, env=None) -> Tuple[str, str, int]:
84
- """Run command to completion, capture stdout/stderr."""
 
85
  proc = await asyncio.create_subprocess_exec(
86
  *cmd,
87
  cwd=cwd,
@@ -89,11 +53,27 @@ async def run_command_capture(cmd, cwd=None, env=None) -> Tuple[str, str, int]:
89
  stdout=asyncio.subprocess.PIPE,
90
  stderr=asyncio.subprocess.PIPE,
91
  )
92
- stdout, stderr = await proc.communicate()
93
- return stdout.decode(errors="ignore"), stderr.decode(errors="ignore"), proc.returncode
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
  async def get_duration_seconds(path: Path) -> Optional[float]:
96
- """Get duration using ffprobe (seconds) or None if unknown."""
97
  cmd = [
98
  "ffprobe", "-v", "error",
99
  "-show_entries", "format=duration",
@@ -108,12 +88,8 @@ async def get_duration_seconds(path: Path) -> Optional[float]:
108
  except Exception:
109
  return None
110
 
111
- def which_cmd(name: str) -> Optional[str]:
112
- return shutil.which(name)
113
-
114
  # -------------------------
115
  # Converter generator
116
- # streams tuples: (preview_html_str, download_path_or_None)
117
  # -------------------------
118
  async def convert_stream(
119
  use_youtube: bool,
@@ -126,306 +102,128 @@ async def convert_stream(
126
  custom_bitrate: bool,
127
  video_bitrate: float
128
  ):
129
- # initial
130
- yield preview_html(0.0, "Starting..."), None
131
-
132
  temp_files = []
133
  input_path: Optional[Path] = None
134
 
135
  try:
136
  # SOURCE
137
  if use_youtube:
138
- if not youtube_url:
139
- yield preview_html(0.0, "Error: YouTube URL required."), None
140
- return
141
- if not which_cmd("yt-dlp"):
142
- yield preview_html(0.0, "yt-dlp not found on server. Please upload the file manually."), None
143
- return
144
-
145
- yield preview_html(1.0, "Attempting YouTube download..."), None
146
  out_uuid = uuid.uuid4().hex
147
  out_template = str(UPLOAD_FOLDER / f"{out_uuid}.%(ext)s")
148
  ytdlp_cmd = ["yt-dlp", "-f", "b", "-o", out_template, youtube_url]
149
  stdout, stderr, rc = await run_command_capture(ytdlp_cmd)
150
- combined = (stdout or "") + "\n" + (stderr or "")
151
- if rc != 0:
152
- if "Video unavailable" in combined or "This video is unavailable" in combined:
153
- yield preview_html(0.0, "Video unavailable (removed/private/age-restricted)."), None
154
- return
155
- yield preview_html(0.0, "Could not download from YouTube from this server (cloud host blocked). Please upload manually."), None
156
- return
157
  files = list(UPLOAD_FOLDER.glob(f"{out_uuid}.*"))
158
  if not files:
159
- yield preview_html(0.0, "Download completed but file not found."), None
160
  return
161
  input_path = files[0]
162
  temp_files.append(input_path)
163
  else:
164
- if not video_file:
165
- yield preview_html(0.0, "No video provided. Upload or use YouTube URL."), None
166
- return
167
- try:
168
- ext = Path(video_file.name).suffix.lower()
169
- except Exception:
170
- ext = None
171
- if ext not in ALLOWED_EXTENSIONS:
172
- yield preview_html(0.0, f"Unsupported file type: {ext}"), None
173
- return
174
- input_path = UPLOAD_FOLDER / f"{uuid.uuid4().hex}{ext}"
175
  shutil.copy2(video_file.name, input_path)
176
  temp_files.append(input_path)
177
 
178
- yield preview_html(1.0, "Probing duration..."), None
179
  total_seconds = await get_duration_seconds(input_path)
180
- if not total_seconds:
181
- yield preview_html(0.0, "Warning: duration unknown; progress will be step-based."), None
182
 
183
- # AUDIO-ONLY PATH
184
  if audio_only:
185
- # MP3 branch
186
- if use_mp3:
187
- step = "Converting to MP3..."
188
- out_audio = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.mp3"
189
- ffmpeg_cmd = [
190
- "ffmpeg", "-y", "-i", str(input_path),
191
- "-ac", "1", "-ar", "24000", "-b:a", "8k", str(out_audio)
192
- ]
193
- yield preview_html(2.0, step), None
194
- if total_seconds:
195
- proc = await asyncio.create_subprocess_exec(*ffmpeg_cmd, stderr=asyncio.subprocess.PIPE)
196
- last = 0.0
197
- while True:
198
- line = await proc.stderr.readline()
199
- if not line:
200
- break
201
- txt = line.decode(errors="ignore")
202
- m = FFMPEG_TIME_RE.search(txt)
203
- if m:
204
- hh, mm, ss = m.groups()
205
- current = int(hh) * 3600 + int(mm) * 60 + float(ss)
206
- pct = (current / total_seconds) * 100.0
207
- if pct - last >= 0.5:
208
- last = pct
209
- yield preview_html(pct, step), None
210
- await proc.wait()
211
- if proc.returncode != 0:
212
- yield preview_html(0.0, "ffmpeg failed while encoding MP3."), None
213
- return
214
- else:
215
- stdout, stderr, rc = await run_command_capture(ffmpeg_cmd)
216
- if rc != 0:
217
- yield preview_html(0.0, "ffmpeg failed while encoding MP3."), None
218
- return
219
- yield preview_html(100.0, "MP3 conversion finished.", media_src=str(out_audio)), str(out_audio)
220
- return
221
-
222
- # AAC via fdkaac branch (audio-only)
223
- # Ensure fdkaac exists
224
- if not FDKAAC_PATH.exists():
225
- yield preview_html(0.0, "fdkaac not found at ./fdkaac — please add it to the app folder and make executable."), None
226
- return
227
-
228
- step = "Preparing WAV for fdkaac..."
229
- yield preview_html(2.0, step), None
230
  wav_tmp = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.wav"
231
- aac_out = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.m4a"
232
 
233
- # Generate WAV (low sample rate)
 
234
  ffmpeg_wav_cmd = ["ffmpeg", "-y", "-i", str(input_path), "-ac", "1", "-ar", "8000", str(wav_tmp)]
235
- stdout, stderr, rc = await run_command_capture(ffmpeg_wav_cmd)
236
- if rc != 0:
237
- yield preview_html(0.0, "ffmpeg failed to produce WAV for fdkaac."), None
238
- try:
239
- wav_tmp.unlink()
240
- except Exception:
241
- pass
242
- return
243
 
244
- # Run fdkaac to produce m4a
 
245
  fdkaac_cmd = [
246
  "./fdkaac", "-b", "1k", "-C", "-f", "2", "-G", "1", "-w", "8000",
247
- "-o", str(aac_out), str(wav_tmp)
248
  ]
249
- yield preview_html(5.0, "Encoding AAC with fdkaac..."), None
250
- stdout, stderr, rc = await run_command_capture(fdkaac_cmd)
251
- try:
252
- wav_tmp.unlink()
253
- except Exception:
254
- pass
255
- if rc != 0:
256
- yield preview_html(0.0, f"fdkaac failed: {stderr[:300] if stderr else 'no output'}"), None
257
- return
258
 
259
- yield preview_html(100.0, "AAC (fdkaac) audio ready.", media_src=str(aac_out)), str(aac_out)
 
 
 
260
  return
261
 
262
- # ----------------------
263
- # FULL VIDEO FLOW
264
- # ----------------------
265
- # 1) Audio encode (via fdkaac or mp3)
266
  out_audio = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.m4a"
 
 
267
  if use_mp3:
268
- ffmpeg_audio_cmd = ["ffmpeg", "-y", "-i", str(input_path), "-ac", "1", "-ar", "8000", "-c:a", "libmp3lame", "-b:a", "8k", str(out_audio)]
269
- yield preview_html(2.0, "Encoding audio (MP3)..."), None
270
- if total_seconds:
271
- proc = await asyncio.create_subprocess_exec(*ffmpeg_audio_cmd, stderr=asyncio.subprocess.PIPE)
272
- last = 0.0
273
- while True:
274
- line = await proc.stderr.readline()
275
- if not line:
276
- break
277
- txt = line.decode(errors="ignore")
278
- m = FFMPEG_TIME_RE.search(txt)
279
- if m:
280
- hh, mm, ss = m.groups()
281
- current = int(hh) * 3600 + int(mm) * 60 + float(ss)
282
- pct = (current / total_seconds) * 100.0 * 0.20
283
- if pct - last >= 0.5:
284
- last = pct
285
- yield preview_html(pct, "Encoding audio (MP3)..."), None
286
- await proc.wait()
287
- if proc.returncode != 0:
288
- yield preview_html(0.0, "ffmpeg audio encoding failed."), None
289
- return
290
- else:
291
- stdout, stderr, rc = await run_command_capture(ffmpeg_audio_cmd)
292
- if rc != 0:
293
- yield preview_html(0.0, "ffmpeg audio encoding failed."), None
294
- return
295
  else:
296
- # Use fdkaac path for AAC
297
- if not FDKAAC_PATH.exists():
298
- yield preview_html(0.0, "fdkaac not found at ./fdkaac — please add it to the app folder and make executable."), None
299
- return
300
-
301
- # 1a: Create WAV
302
- yield preview_html(2.0, "Generating WAV for fdkaac..."), None
303
  wav_tmp = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.wav"
304
  ffmpeg_wav_cmd = ["ffmpeg", "-y", "-i", str(input_path), "-ac", "1", "-ar", "8000", str(wav_tmp)]
305
- stdout, stderr, rc = await run_command_capture(ffmpeg_wav_cmd)
306
- if rc != 0:
307
- yield preview_html(0.0, "ffmpeg failed generating WAV for fdkaac."), None
308
- try:
309
- wav_tmp.unlink()
310
- except Exception:
311
- pass
312
- return
313
-
314
- # 1b: Run fdkaac
315
- aac_tmp = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.m4a"
316
- fdkaac_cmd = [
317
- "./fdkaac", "-b", "1k", "-C", "-f", "2", "-G", "1", "-w", "8000",
318
- "-o", str(aac_tmp), str(wav_tmp)
319
- ]
320
- yield preview_html(6.0, "Encoding AAC with fdkaac..."), None
321
- stdout, stderr, rc = await run_command_capture(fdkaac_cmd)
322
- try:
323
- wav_tmp.unlink()
324
- except Exception:
325
- pass
326
- if rc != 0:
327
- yield preview_html(0.0, f"fdkaac failed: {stderr[:300] if stderr else 'no output'}"), None
328
- return
329
- out_audio = aac_tmp
330
-
331
- # 2) Video encode
332
- step_video = "Encoding video track..."
333
- yield preview_html(20.0, step_video), None
334
  out_video = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.mp4"
 
335
  ffmpeg_video_cmd = ["ffmpeg", "-y", "-hwaccel", ACCEL, "-i", str(input_path)]
336
- if downscale:
337
- ffmpeg_video_cmd += ["-vf", "scale=-2:144"]
338
- if custom_bitrate and video_bitrate:
339
- ffmpeg_video_cmd += ["-b:v", f"{int(video_bitrate)}k"]
340
- else:
341
- ffmpeg_video_cmd += VIDEO_BASE_OPTS
342
- if faster:
343
- ffmpeg_video_cmd += ["-preset", "ultrafast"]
344
  ffmpeg_video_cmd += ["-an", str(out_video)]
 
345
 
346
- if total_seconds:
347
- proc = await asyncio.create_subprocess_exec(*ffmpeg_video_cmd, stderr=asyncio.subprocess.PIPE)
348
- last = 20.0
349
- while True:
350
- line = await proc.stderr.readline()
351
- if not line:
352
- break
353
- txt = line.decode(errors="ignore")
354
- m = FFMPEG_TIME_RE.search(txt)
355
- if m:
356
- hh, mm, ss = m.groups()
357
- current = int(hh) * 3600 + int(mm) * 60 + float(ss)
358
- pct_video = (current / total_seconds) * 100.0
359
- combined = 20.0 + (pct_video * 0.70)
360
- if combined - last >= 0.5:
361
- last = combined
362
- yield preview_html(combined, step_video), None
363
- await proc.wait()
364
- if proc.returncode != 0:
365
- yield preview_html(0.0, "ffmpeg video encoding failed."), None
366
- return
367
- else:
368
- stdout, stderr, rc = await run_command_capture(ffmpeg_video_cmd)
369
- if rc != 0:
370
- yield preview_html(0.0, "ffmpeg video encoding failed."), None
371
- return
372
-
373
- # 3) Merge audio + video
374
- yield preview_html(90.0, "Merging audio & video..."), None
375
  merged_out = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.mp4"
 
376
  merge_cmd = ["ffmpeg", "-y", "-i", str(out_video), "-i", str(out_audio), "-c", "copy", str(merged_out)]
377
  stdout, stderr, rc = await run_command_capture(merge_cmd)
378
  if rc != 0:
379
- # fallback re-encode audio into AAC during merge
380
- merge_cmd = ["ffmpeg", "-y", "-i", str(out_video), "-i", str(out_audio), "-c:v", "copy", "-c:a", "aac", str(merged_out)]
381
- stdout, stderr, rc = await run_command_capture(merge_cmd)
382
- if rc != 0:
383
- yield preview_html(0.0, "Merging audio and video failed."), None
384
- return
385
 
386
- # cleanup intermediate audio/video
387
- for p in (out_audio, out_video):
388
- try:
389
- p.unlink()
390
- except Exception:
391
- pass
392
 
393
- # final
394
- yield preview_html(100.0, "Conversion complete!", media_src=str(merged_out)), str(merged_out)
395
  return
396
 
397
- except Exception as e:
398
- yield preview_html(0.0, f"Error: {e}"), None
399
- return
400
  finally:
401
- # remove any temp files downloaded
402
- for p in temp_files:
403
- try:
404
- p.unlink()
405
- except Exception:
406
- pass
407
 
408
  # -------------------------
409
  # Gradio UI
410
  # -------------------------
411
  with gr.Blocks(title="Low Quality Video Inator (fdkaac)") as demo:
412
- gr.Markdown("# Low Quality Video Inator\nUpload a file or paste a YouTube URL. The app streams a step-aware overlayed progress bar while encoding.")
413
 
414
  with gr.Row():
415
- use_youtube = gr.Checkbox(label="Use YouTube URL (server will try to download first)", value=False)
416
- youtube_url = gr.Textbox(label="YouTube URL", placeholder="https://youtube.com/...", lines=1)
417
 
418
- video_file = gr.File(label="Upload Video File", file_types=list(ALLOWED_EXTENSIONS), file_count="single")
419
 
420
- gr.Markdown("### Conversion Settings")
421
  with gr.Row():
422
  downscale = gr.Checkbox(label="Downscale to 144p", value=False)
423
- faster = gr.Checkbox(label="Faster encoding (lower quality)", value=False)
424
  with gr.Row():
425
- use_mp3 = gr.Checkbox(label="Use MP3 audio", value=False)
426
- audio_only = gr.Checkbox(label="Audio Only", value=False)
427
  with gr.Row():
428
- custom_bitrate = gr.Checkbox(label="Use custom video bitrate (kbps)", value=False)
429
  video_bitrate = gr.Number(label="Video bitrate (kbps)", value=64, visible=False)
430
 
431
  def toggle_bitrate(v):
@@ -433,14 +231,15 @@ with gr.Blocks(title="Low Quality Video Inator (fdkaac)") as demo:
433
  custom_bitrate.change(toggle_bitrate, inputs=[custom_bitrate], outputs=[video_bitrate])
434
 
435
  convert_btn = gr.Button("Convert Now", variant="primary")
436
-
437
- preview_html_el = gr.HTML("<div>Ready. Preview will appear here.</div>", label="Preview")
438
  download_file = gr.File(label="Download Result")
 
439
 
440
  convert_btn.click(
441
  fn=convert_stream,
442
  inputs=[use_youtube, youtube_url, video_file, downscale, faster, use_mp3, audio_only, custom_bitrate, video_bitrate],
443
- outputs=[preview_html_el, download_file]
444
  )
445
 
446
  demo.launch(share=False)
 
4
  import re
5
  import shutil
6
  import uuid
 
7
  from pathlib import Path
8
+ import html
9
  from typing import Optional, Tuple
10
 
11
  import gradio as gr
 
33
 
34
  FFMPEG_TIME_RE = re.compile(r"time=(\d+):(\d+):(\d+\.\d+)")
35
 
 
36
  FDKAAC_PATH = Path("./fdkaac")
37
+ if FDKAAC_PATH.exists():
38
+ FDKAAC_PATH.chmod(FDKAAC_PATH.stat().st_mode | 0o111)
 
 
 
39
 
40
  # -------------------------
41
  # Helpers
42
  # -------------------------
43
  def is_audio_file(path: str) -> bool:
44
+ return Path(path).suffix.lower() in {".mp3", ".m4a", ".wav", ".aac", ".oga", ".ogg"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  async def run_command_capture(cmd, cwd=None, env=None) -> Tuple[str, str, int]:
47
+ """Run command, log stdout/stderr live."""
48
+ print(f"[CMD] {' '.join(cmd)}")
49
  proc = await asyncio.create_subprocess_exec(
50
  *cmd,
51
  cwd=cwd,
 
53
  stdout=asyncio.subprocess.PIPE,
54
  stderr=asyncio.subprocess.PIPE,
55
  )
56
+ stdout_lines = []
57
+ stderr_lines = []
58
+
59
+ async def read_stream(stream, buffer, name):
60
+ while True:
61
+ line = await stream.readline()
62
+ if not line:
63
+ break
64
+ line_txt = line.decode(errors="ignore").rstrip()
65
+ print(f"[{name}] {line_txt}")
66
+ buffer.append(line_txt)
67
+
68
+ await asyncio.gather(
69
+ read_stream(proc.stdout, stdout_lines, "STDOUT"),
70
+ read_stream(proc.stderr, stderr_lines, "STDERR"),
71
+ )
72
+
73
+ await proc.wait()
74
+ return "\n".join(stdout_lines), "\n".join(stderr_lines), proc.returncode
75
 
76
  async def get_duration_seconds(path: Path) -> Optional[float]:
 
77
  cmd = [
78
  "ffprobe", "-v", "error",
79
  "-show_entries", "format=duration",
 
88
  except Exception:
89
  return None
90
 
 
 
 
91
  # -------------------------
92
  # Converter generator
 
93
  # -------------------------
94
  async def convert_stream(
95
  use_youtube: bool,
 
102
  custom_bitrate: bool,
103
  video_bitrate: float
104
  ):
105
+ print("Starting conversion...")
 
 
106
  temp_files = []
107
  input_path: Optional[Path] = None
108
 
109
  try:
110
  # SOURCE
111
  if use_youtube:
112
+ yield None, None, None, "Starting YouTube download..."
 
 
 
 
 
 
 
113
  out_uuid = uuid.uuid4().hex
114
  out_template = str(UPLOAD_FOLDER / f"{out_uuid}.%(ext)s")
115
  ytdlp_cmd = ["yt-dlp", "-f", "b", "-o", out_template, youtube_url]
116
  stdout, stderr, rc = await run_command_capture(ytdlp_cmd)
 
 
 
 
 
 
 
117
  files = list(UPLOAD_FOLDER.glob(f"{out_uuid}.*"))
118
  if not files:
119
+ yield None, None, None, "Failed to download YouTube video."
120
  return
121
  input_path = files[0]
122
  temp_files.append(input_path)
123
  else:
124
+ input_path = UPLOAD_FOLDER / f"{uuid.uuid4().hex}{Path(video_file.name).suffix}"
 
 
 
 
 
 
 
 
 
 
125
  shutil.copy2(video_file.name, input_path)
126
  temp_files.append(input_path)
127
 
 
128
  total_seconds = await get_duration_seconds(input_path)
129
+ print(f"Duration: {total_seconds}s")
 
130
 
131
+ # AUDIO ONLY
132
  if audio_only:
133
+ out_audio = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.m4a"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  wav_tmp = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.wav"
 
135
 
136
+ # Step1: generate WAV
137
+ yield None, None, None, "Generating WAV for AAC..."
138
  ffmpeg_wav_cmd = ["ffmpeg", "-y", "-i", str(input_path), "-ac", "1", "-ar", "8000", str(wav_tmp)]
139
+ await run_command_capture(ffmpeg_wav_cmd)
 
 
 
 
 
 
 
140
 
141
+ # Step2: fdkaac
142
+ yield None, None, None, "Encoding AAC via fdkaac..."
143
  fdkaac_cmd = [
144
  "./fdkaac", "-b", "1k", "-C", "-f", "2", "-G", "1", "-w", "8000",
145
+ "-o", str(out_audio), str(wav_tmp)
146
  ]
147
+ await run_command_capture(fdkaac_cmd)
 
 
 
 
 
 
 
 
148
 
149
+ try: wav_tmp.unlink()
150
+ except: pass
151
+
152
+ yield None, str(out_audio), str(out_audio), "AAC conversion complete!"
153
  return
154
 
155
+ # FULL VIDEO
 
 
 
156
  out_audio = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.m4a"
157
+ # Step1: encode audio
158
+ yield None, None, None, "Encoding audio track..."
159
  if use_mp3:
160
+ ffmpeg_audio_cmd = ["ffmpeg", "-y", "-i", str(input_path), "-ac", "1", "-ar", "24000",
161
+ "-b:a", "8k", str(out_audio)]
162
+ await run_command_capture(ffmpeg_audio_cmd)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  else:
164
+ # fdkaac branch
 
 
 
 
 
 
165
  wav_tmp = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.wav"
166
  ffmpeg_wav_cmd = ["ffmpeg", "-y", "-i", str(input_path), "-ac", "1", "-ar", "8000", str(wav_tmp)]
167
+ await run_command_capture(ffmpeg_wav_cmd)
168
+ fdkaac_cmd = ["./fdkaac", "-b", "1k", "-C", "-f", "2", "-G", "1", "-w", "8000",
169
+ "-o", str(out_audio), str(wav_tmp)]
170
+ await run_command_capture(fdkaac_cmd)
171
+ try: wav_tmp.unlink()
172
+ except: pass
173
+
174
+ # Step2: encode video
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  out_video = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.mp4"
176
+ yield None, None, None, "Encoding video track..."
177
  ffmpeg_video_cmd = ["ffmpeg", "-y", "-hwaccel", ACCEL, "-i", str(input_path)]
178
+ if downscale: ffmpeg_video_cmd += ["-vf", "scale=-2:144"]
179
+ if custom_bitrate and video_bitrate: ffmpeg_video_cmd += ["-b:v", f"{int(video_bitrate)}k"]
180
+ else: ffmpeg_video_cmd += VIDEO_BASE_OPTS
181
+ if faster: ffmpeg_video_cmd += ["-preset", "ultrafast"]
 
 
 
 
182
  ffmpeg_video_cmd += ["-an", str(out_video)]
183
+ await run_command_capture(ffmpeg_video_cmd)
184
 
185
+ # Step3: merge
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  merged_out = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.mp4"
187
+ yield None, None, None, "Merging audio & video..."
188
  merge_cmd = ["ffmpeg", "-y", "-i", str(out_video), "-i", str(out_audio), "-c", "copy", str(merged_out)]
189
  stdout, stderr, rc = await run_command_capture(merge_cmd)
190
  if rc != 0:
191
+ merge_cmd = ["ffmpeg", "-y", "-i", str(out_video), "-i", str(out_audio),
192
+ "-c:v", "copy", "-c:a", "aac", str(merged_out)]
193
+ await run_command_capture(merge_cmd)
 
 
 
194
 
195
+ for f in (out_audio, out_video):
196
+ try: f.unlink()
197
+ except: pass
 
 
 
198
 
199
+ yield str(merged_out), str(merged_out), str(merged_out), "Conversion complete!"
 
200
  return
201
 
 
 
 
202
  finally:
203
+ for f in temp_files:
204
+ try: f.unlink()
205
+ except: pass
 
 
 
206
 
207
  # -------------------------
208
  # Gradio UI
209
  # -------------------------
210
  with gr.Blocks(title="Low Quality Video Inator (fdkaac)") as demo:
211
+ gr.Markdown("## Low Quality Video Inator\nUpload a file or paste a YouTube URL.")
212
 
213
  with gr.Row():
214
+ use_youtube = gr.Checkbox(label="Use YouTube URL", value=False)
215
+ youtube_url = gr.Textbox(label="YouTube URL", placeholder="https://youtube.com/...")
216
 
217
+ video_file = gr.File(label="Upload Video", file_types=list(ALLOWED_EXTENSIONS))
218
 
 
219
  with gr.Row():
220
  downscale = gr.Checkbox(label="Downscale to 144p", value=False)
221
+ faster = gr.Checkbox(label="Faster and lower quality encoding", value=False)
222
  with gr.Row():
223
+ use_mp3 = gr.Checkbox(label="Use MP3 audio (AAC is however lower quality)", value=False)
224
+ audio_only = gr.Checkbox(label="Audio only", value=False)
225
  with gr.Row():
226
+ custom_bitrate = gr.Checkbox(label="Custom video bitrate", value=False)
227
  video_bitrate = gr.Number(label="Video bitrate (kbps)", value=64, visible=False)
228
 
229
  def toggle_bitrate(v):
 
231
  custom_bitrate.change(toggle_bitrate, inputs=[custom_bitrate], outputs=[video_bitrate])
232
 
233
  convert_btn = gr.Button("Convert Now", variant="primary")
234
+ video_preview = gr.Video(label="Video Preview")
235
+ audio_preview = gr.Audio(label="Audio Preview")
236
  download_file = gr.File(label="Download Result")
237
+ step_text = gr.Textbox(label="Status / Progress", interactive=False)
238
 
239
  convert_btn.click(
240
  fn=convert_stream,
241
  inputs=[use_youtube, youtube_url, video_file, downscale, faster, use_mp3, audio_only, custom_bitrate, video_bitrate],
242
+ outputs=[video_preview, audio_preview, download_file, step_text]
243
  )
244
 
245
  demo.launch(share=False)