Spaces:
Sleeping
Sleeping
File size: 9,150 Bytes
70923cd 6567ae9 906ec90 d83b57c 906ec90 b8f0bd6 70923cd d83b57c 906ec90 70923cd 906ec90 70923cd 906ec90 70923cd b8f0bd6 70923cd b8f0bd6 906ec90 70923cd b8f0bd6 906ec90 ded38ea 906ec90 ded38ea 6567ae9 b8f0bd6 906ec90 70923cd 906ec90 70923cd 906ec90 70923cd 906ec90 70923cd 906ec90 b8f0bd6 906ec90 70923cd c841491 b32dded 70923cd de205c3 d7ae8bc 906ec90 9341904 906ec90 b32dded d7ae8bc 906ec90 de205c3 906ec90 de205c3 b8f0bd6 b32dded 906ec90 b32dded 70923cd b8f0bd6 70923cd b8f0bd6 de205c3 b8f0bd6 70923cd b8f0bd6 d7ae8bc 70923cd b8f0bd6 70923cd b8f0bd6 d7ae8bc 70923cd b8f0bd6 70923cd b8f0bd6 70923cd b8f0bd6 d7ae8bc 70923cd b8f0bd6 906ec90 b8f0bd6 d7ae8bc 906ec90 7100b64 b8f0bd6 906ec90 b8f0bd6 70923cd b8f0bd6 906ec90 d7ae8bc 906ec90 b8f0bd6 906ec90 b8f0bd6 906ec90 b8f0bd6 906ec90 d7ae8bc 70923cd 906ec90 b8f0bd6 906ec90 b8f0bd6 906ec90 d7ae8bc 906ec90 b8f0bd6 906ec90 70923cd b8f0bd6 906ec90 b8f0bd6 906ec90 b8f0bd6 906ec90 b8f0bd6 906ec90 b8f0bd6 906ec90 b8f0bd6 70923cd 906ec90 70923cd 906ec90 b8f0bd6 70923cd b8f0bd6 906ec90 fd7d0fc d83b57c 70923cd |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# app.py
import asyncio
import os
import re
import shutil
import uuid
from pathlib import Path
import html
from typing import Optional, Tuple
import gradio as gr
# -------------------------
# Config
# -------------------------
BASE_DIR = Path(".")
UPLOAD_FOLDER = BASE_DIR / "uploads"
CONVERTED_FOLDER = BASE_DIR / "converted"
UPLOAD_FOLDER.mkdir(exist_ok=True)
CONVERTED_FOLDER.mkdir(exist_ok=True)
ALLOWED_EXTENSIONS = {
".3g2", ".3gp", ".3gpp", ".avi", ".cavs", ".dv", ".dvr", ".flv",
".m2ts", ".m4v", ".mkv", ".mod", ".mov", ".mp4", ".mpeg", ".mpg",
".mts", ".mxf", ".ogg", ".rm", ".rmvb", ".swf", ".ts", ".vob",
".webm", ".wmv", ".wtv", ".ogv", ".opus", ".aac", ".ac3", ".aif",
".aifc", ".aiff", ".amr", ".au", ".caf", ".dss", ".flac", ".m4a",
".m4b", ".mp3", ".oga", ".voc", ".wav", ".weba", ".wma"
}
VIDEO_BASE_OPTS = ["-crf", "63", "-c:v", "libx264", "-tune", "zerolatency"]
ACCEL = "auto"
FFMPEG_TIME_RE = re.compile(r"time=(\d+):(\d+):(\d+\.\d+)")
FDKAAC_PATH = Path("./fdkaac")
if FDKAAC_PATH.exists():
FDKAAC_PATH.chmod(FDKAAC_PATH.stat().st_mode | 0o111)
# -------------------------
# Helpers
# -------------------------
def is_audio_file(path: str) -> bool:
return Path(path).suffix.lower() in {".mp3", ".m4a", ".wav", ".aac", ".oga", ".ogg"}
async def run_command_capture(cmd, cwd=None, env=None) -> Tuple[str, str, int]:
"""Run command, log stdout/stderr live."""
print(f"[CMD] {' '.join(cmd)}")
proc = await asyncio.create_subprocess_exec(
*cmd,
cwd=cwd,
env=env or os.environ.copy(),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout_lines = []
stderr_lines = []
async def read_stream(stream, buffer, name):
while True:
line = await stream.readline()
if not line:
break
line_txt = line.decode(errors="ignore").rstrip()
print(f"[{name}] {line_txt}")
buffer.append(line_txt)
await asyncio.gather(
read_stream(proc.stdout, stdout_lines, "STDOUT"),
read_stream(proc.stderr, stderr_lines, "STDERR"),
)
await proc.wait()
return "\n".join(stdout_lines), "\n".join(stderr_lines), proc.returncode
async def get_duration_seconds(path: Path) -> Optional[float]:
cmd = [
"ffprobe", "-v", "error",
"-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1",
str(path)
]
stdout, stderr, rc = await run_command_capture(cmd)
if rc != 0 or not stdout:
return None
try:
return float(stdout.strip())
except Exception:
return None
# -------------------------
# Converter generator
# -------------------------
async def convert_stream(
use_youtube: bool,
youtube_url: str,
video_file, # gr.File
downscale: bool,
faster: bool,
use_mp3: bool,
audio_only: bool,
custom_bitrate: bool,
video_bitrate: float
):
print("Starting conversion...")
temp_files = []
input_path: Optional[Path] = None
try:
# SOURCE
if use_youtube:
yield None, None, "Starting YouTube download..."
out_uuid = uuid.uuid4().hex
out_template = str(UPLOAD_FOLDER / f"{out_uuid}.%(ext)s")
ytdlp_cmd = ["yt-dlp", "--extractor-args", "youtube:player_client=tv_simply", "-f", "b", "-o", out_template, youtube_url]
stdout, stderr, rc = await run_command_capture(ytdlp_cmd)
files = list(UPLOAD_FOLDER.glob(f"{out_uuid}.*"))
if not files:
yield None, None, "Failed to download YouTube video."
return
input_path = files[0]
temp_files.append(input_path)
else:
input_path = UPLOAD_FOLDER / f"{uuid.uuid4().hex}{Path(video_file.name).suffix}"
shutil.copy2(video_file.name, input_path)
temp_files.append(input_path)
total_seconds = await get_duration_seconds(input_path)
print(f"Duration: {total_seconds}s")
# AUDIO ONLY
if audio_only:
out_audio = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.m4a"
wav_tmp = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.wav"
# Step1: generate WAV
yield None, None, "Generating WAV for AAC..."
ffmpeg_wav_cmd = ["ffmpeg", "-y", "-i", str(input_path), "-ac", "1", "-ar", "8000", str(wav_tmp)]
await run_command_capture(ffmpeg_wav_cmd)
# Step2: fdkaac
yield None, None, "Encoding AAC via fdkaac..."
fdkaac_cmd = [
"./fdkaac", "-b", "1k", "-C", "-f", "2", "-G", "1", "-w", "8000",
"-o", str(out_audio), str(wav_tmp)
]
await run_command_capture(fdkaac_cmd)
try: wav_tmp.unlink()
except: pass
yield str(out_audio), str(out_audio), "AAC conversion complete!"
return
# FULL VIDEO
out_audio = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.m4a"
# Step1: encode audio
yield None, None, "Encoding audio track..."
if use_mp3:
out_audio = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.mp3"
ffmpeg_audio_cmd = ["ffmpeg", "-y", "-i", str(input_path), "-ac", "1", "-ar", "24k",
"-b:a", "8k", "-vn", str(out_audio)]
await run_command_capture(ffmpeg_audio_cmd)
else:
# fdkaac branch
wav_tmp = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.wav"
ffmpeg_wav_cmd = ["ffmpeg", "-y", "-i", str(input_path), "-ac", "1", "-ar", "8000", str(wav_tmp)]
await run_command_capture(ffmpeg_wav_cmd)
fdkaac_cmd = ["./fdkaac", "-b", "1k", "-C", "-f", "2", "-G", "1", "-w", "8000",
"-o", str(out_audio), str(wav_tmp)]
await run_command_capture(fdkaac_cmd)
try: wav_tmp.unlink()
except: pass
# Step2: encode video
out_video = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.mp4"
yield None, None, "Encoding video track..."
ffmpeg_video_cmd = ["ffmpeg", "-y", "-hwaccel", ACCEL, "-i", str(input_path)]
if downscale: ffmpeg_video_cmd += ["-vf", "scale=-2:144"]
if custom_bitrate and video_bitrate: ffmpeg_video_cmd += ["-b:v", f"{int(video_bitrate)}k"]
else: ffmpeg_video_cmd += VIDEO_BASE_OPTS
if faster: ffmpeg_video_cmd += ["-preset", "ultrafast"]
ffmpeg_video_cmd += ["-an", str(out_video)]
await run_command_capture(ffmpeg_video_cmd)
# Step3: merge
merged_out = CONVERTED_FOLDER / f"{uuid.uuid4().hex}.mp4"
yield None, None, "Merging audio & video..."
merge_cmd = ["ffmpeg", "-y", "-i", str(out_video), "-i", str(out_audio), "-c", "copy", str(merged_out)]
stdout, stderr, rc = await run_command_capture(merge_cmd)
if rc != 0:
merge_cmd = ["ffmpeg", "-y", "-i", str(out_video), "-i", str(out_audio),
"-c:v", "copy", "-c:a", "aac", str(merged_out)]
await run_command_capture(merge_cmd)
for f in (out_audio, out_video):
try: f.unlink()
except: pass
yield str(merged_out), str(merged_out), "Conversion complete!"
return
finally:
for f in temp_files:
try: f.unlink()
except: pass
# -------------------------
# Gradio UI
# -------------------------
with gr.Blocks(title="Low Quality Video Inator (fdkaac)") as demo:
gr.Markdown("## Low Quality Video Inator\nUpload a file or paste a YouTube URL.")
with gr.Row():
use_youtube = gr.Checkbox(label="Use YouTube URL", value=False)
youtube_url = gr.Textbox(label="YouTube URL", placeholder="https://youtube.com/...")
video_file = gr.File(label="Upload Video", file_types=list(ALLOWED_EXTENSIONS))
with gr.Row():
downscale = gr.Checkbox(label="Downscale to 144p", value=False)
faster = gr.Checkbox(label="Faster and lower quality encoding", value=False)
with gr.Row():
use_mp3 = gr.Checkbox(label="Use MP3 audio (AAC is however lower quality)", value=False)
audio_only = gr.Checkbox(label="Audio only", value=False)
with gr.Row():
custom_bitrate = gr.Checkbox(label="Custom video bitrate", value=False)
video_bitrate = gr.Number(label="Video bitrate (kbps)", value=64, visible=False)
def toggle_bitrate(v):
return gr.update(visible=v)
custom_bitrate.change(toggle_bitrate, inputs=[custom_bitrate], outputs=[video_bitrate])
convert_btn = gr.Button("Convert Now", variant="primary")
video_preview = gr.Video(label="Video Preview")
download_file = gr.File(label="Download Result")
step_text = gr.Textbox(label="Status / Progress", interactive=False)
convert_btn.click(
fn=convert_stream,
inputs=[use_youtube, youtube_url, video_file, downscale, faster, use_mp3, audio_only, custom_bitrate, video_bitrate],
outputs=[video_preview, download_file, step_text]
)
demo.launch(share=False) |