salman555 commited on
Commit
e7c787e
·
verified ·
1 Parent(s): 7d15954

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +309 -0
  2. apt.txt +2 -0
  3. requirements.txt +2 -0
app.py ADDED
@@ -0,0 +1,309 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # videoconverter.py
2
+ import os, shutil, subprocess, tempfile, mimetypes
3
+ from pathlib import Path
4
+ import gradio as gr
5
+
6
+ VIDEO_OUTS = ["mp4", "avi", "flv", "mov", "wmv", "mkv", "webm"]
7
+ AUDIO_OUTS = ["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma"]
8
+
9
+ L = {
10
+ "id": {
11
+ "title": "Free Video Converter",
12
+ "subtitle": "Pilih/drag file. Format yang sama dengan input disembunyikan otomatis.",
13
+ "choose_target": "Pilih target",
14
+ "to_video": "ke video",
15
+ "to_audio": "ke audio",
16
+ "upload_label": "Unggah file video/audio",
17
+ "status_ready": "Siap. Unggah file untuk mulai.",
18
+ "crf": "CRF H.264 (lebih kecil = lebih bagus)",
19
+ "preset": "Preset sangat cepat (ultrafast)",
20
+ "convert": "Convert",
21
+ "downlabel": "Unduh hasil",
22
+ "detected_video": "Terdeteksi video. Format input disembunyikan otomatis.",
23
+ "detected_audio": "Terdeteksi audio. Grup video dinonaktifkan.",
24
+ "need_file": "Pilih file dulu.",
25
+ "need_target": "Pilih format tujuan (klik salah satu chip).",
26
+ "no_ffmpeg": "FFmpeg tidak ditemukan. Install ffmpeg terlebih dahulu.",
27
+ "fmt_unsupported": "Format {fmt} belum didukung.",
28
+ "fail_convert": "Gagal konversi: {err}",
29
+ "error": "Error: {err}",
30
+ "done_toast": "Selesai: {name}",
31
+ "processing_status": "⏳ Sedang mengonversi… harap tunggu.",
32
+ "processing_toast": "Sedang mengonversi…",
33
+ "lang_label": "Bahasa",
34
+ "lang_id": "🇮🇩 Indonesia",
35
+ "lang_en": "🇺🇸 English",
36
+ "please_wait": "Sedang mengonversi…",
37
+ },
38
+ "en": {
39
+ "title": "Free Video Converter",
40
+ "subtitle": "Pick/drag files. Input’s identical format is hidden automatically.",
41
+ "choose_target": "Choose a target",
42
+ "to_video": "to video",
43
+ "to_audio": "to audio",
44
+ "upload_label": "Upload video/audio file",
45
+ "status_ready": "Ready. Upload a file to start.",
46
+ "crf": "CRF H.264 (lower = better quality)",
47
+ "preset": "Ultra fast preset (ultrafast)",
48
+ "convert": "Convert",
49
+ "downlabel": "Download result",
50
+ "detected_video": "Video detected. Input format hidden automatically.",
51
+ "detected_audio": "Audio detected. Video group disabled.",
52
+ "need_file": "Please choose a file first.",
53
+ "need_target": "Pick a target format (click one of the chips).",
54
+ "no_ffmpeg": "FFmpeg not found. Please install ffmpeg.",
55
+ "fmt_unsupported": "Format {fmt} is not supported.",
56
+ "fail_convert": "Convert failed: {err}",
57
+ "error": "Error: {err}",
58
+ "done_toast": "Done: {name}",
59
+ "processing_status": "⏳ Converting… please wait.",
60
+ "processing_toast": "Converting…",
61
+ "lang_label": "Language",
62
+ "lang_id": "🇮🇩 Indonesian",
63
+ "lang_en": "🇺🇸 English",
64
+ "please_wait": "Converting…",
65
+ },
66
+ }
67
+
68
+ def t(k, lang): return L.get(lang, L["id"]).get(k, k)
69
+ def _ext(n): return (Path(n).suffix or "").lower().lstrip(".")
70
+ def _is_video(n):
71
+ mt,_ = mimetypes.guess_type(n); return (mt or "").startswith("video")
72
+ def _is_audio(n):
73
+ mt,_ = mimetypes.guess_type(n); return (mt or "").startswith("audio")
74
+ def _ffmpeg_exists(): return shutil.which("ffmpeg") is not None
75
+
76
+ # ---------- Suggestions ----------
77
+ def suggest_targets(file, lang):
78
+ reset_dl = gr.update(value=None, visible=False)
79
+ if not file:
80
+ return (gr.update(choices=[], value=None, interactive=True),
81
+ gr.update(choices=[], value=None, interactive=True),
82
+ None, t("status_ready", lang), reset_dl)
83
+ name = Path(file.name).name
84
+ same = _ext(name)
85
+ if _is_video(name):
86
+ v = [f for f in VIDEO_OUTS if f != same]
87
+ a = [f for f in AUDIO_OUTS if f != same]
88
+ default = "mp4" if "mp4" in v else (v[0] if v else (a[0] if a else None))
89
+ return (gr.update(choices=v, value=(default if default in v else None), interactive=True),
90
+ gr.update(choices=a, value=None, interactive=True),
91
+ default, t("detected_video", lang), reset_dl)
92
+ # audio
93
+ v = [f for f in VIDEO_OUTS if f != same]
94
+ a = [f for f in AUDIO_OUTS if f != same]
95
+ default = "mp3" if "mp3" in a else (a[0] if a else None)
96
+ return (gr.update(choices=v, value=None, interactive=False),
97
+ gr.update(choices=a, value=default, interactive=True),
98
+ default, t("detected_audio", lang), reset_dl)
99
+
100
+ def pick_video(fmt): return fmt, gr.update(value=None), gr.update(value=None, visible=False)
101
+ def pick_audio(fmt): return fmt, gr.update(value=None), gr.update(value=None, visible=False)
102
+
103
+ # ---------- Convert ----------
104
+ def convert(file, target_fmt, crf, super_fast, lang):
105
+ if not file: return gr.update(value=None, visible=False), t("need_file", lang)
106
+ if not target_fmt: return gr.update(value=None, visible=False), t("need_target", lang)
107
+ if not _ffmpeg_exists(): return gr.update(value=None, visible=False), t("no_ffmpeg", lang)
108
+
109
+ in_path = Path(file.name)
110
+ work = Path(tempfile.mkdtemp(prefix="vc_"))
111
+ src = work / in_path.name
112
+ shutil.copyfile(in_path, src)
113
+
114
+ out_name = f"{in_path.stem}.{target_fmt}"
115
+ dst = work / out_name
116
+ args = ["ffmpeg","-y","-hide_banner","-loglevel","error","-i",str(src)]
117
+
118
+ if target_fmt in ("mp4","mov"):
119
+ args += ["-c:v","libx264","-preset",("ultrafast" if super_fast else "veryfast"),
120
+ "-pix_fmt","yuv420p","-crf",str(crf),"-c:a","aac","-b:a","192k"]
121
+ elif target_fmt in ("webm","mkv"):
122
+ args += ["-c:v","libvpx-vp9","-b:v","1200k","-c:a","libopus","-b:a","128k"]
123
+ elif target_fmt=="avi":
124
+ args += ["-c:v","mpeg4","-qscale:v","6","-c:a","mp2","-b:a","192k"]
125
+ elif target_fmt=="flv":
126
+ args += ["-c:v","flv","-b:v","1M","-c:a","libmp3lame","-b:a","160k"]
127
+ elif target_fmt=="wmv":
128
+ args += ["-c:v","wmv2","-b:v","1M","-c:a","wmav2","-b:a","160k"]
129
+ elif target_fmt=="mp3":
130
+ args += ["-vn","-c:a","libmp3lame","-b:a","192k"]
131
+ elif target_fmt=="wav":
132
+ args += ["-vn","-c:a","pcm_s16le","-ar","44100","-ac","2"]
133
+ elif target_fmt=="flac":
134
+ args += ["-vn","-c:a","flac"]
135
+ elif target_fmt=="ogg":
136
+ args += ["-vn","-c:a","libopus","-b:a","128k","-f","ogg"]
137
+ elif target_fmt=="m4a":
138
+ args += ["-vn","-c:a","aac","-b:a","192k"]
139
+ elif target_fmt=="aac":
140
+ args += ["-vn","-c:a","aac","-b:a","192k","-f","adts"]
141
+ elif target_fmt=="wma":
142
+ args += ["-vn","-c:a","wmav2","-b:a","160k"]
143
+ else:
144
+ shutil.rmtree(work, ignore_errors=True)
145
+ return gr.update(value=None, visible=False), t("fmt_unsupported", lang).format(fmt=target_fmt)
146
+
147
+ args += [str(dst)]
148
+ try:
149
+ subprocess.run(args, check=True)
150
+ except subprocess.CalledProcessError as e:
151
+ shutil.rmtree(work, ignore_errors=True)
152
+ return gr.update(value=None, visible=False), t("fail_convert", lang).format(err=e)
153
+ except Exception as e:
154
+ shutil.rmtree(work, ignore_errors=True)
155
+ return gr.update(value=None, visible=False), t("error", lang).format(err=e)
156
+
157
+ gr.Info(t("done_toast", lang).format(name=out_name))
158
+ return gr.update(value=str(dst), visible=True, label=t("downlabel", lang)), t("done_toast", lang).format(name=out_name)
159
+
160
+ # ---------- Lock / Unlock + MODAL “please wait” ----------
161
+ def begin_processing(lang):
162
+ gr.Info(t("processing_toast", lang))
163
+ return (
164
+ gr.update(interactive=False), # file_in
165
+ gr.update(interactive=False), # video_radio
166
+ gr.update(interactive=False), # audio_radio
167
+ gr.update(interactive=False), # crf
168
+ gr.update(interactive=False), # super_fast
169
+ gr.update(interactive=False), # btn convert
170
+ gr.update(value=None, visible=False), # download button
171
+ t("processing_status", lang), # status text
172
+ gr.update(visible=True), # SHOW modal
173
+ )
174
+
175
+ def end_processing():
176
+ return (
177
+ gr.update(interactive=True),
178
+ gr.update(interactive=True),
179
+ gr.update(interactive=True),
180
+ gr.update(interactive=True),
181
+ gr.update(interactive=True),
182
+ gr.update(interactive=True),
183
+ gr.update(visible=False), # HIDE modal
184
+ )
185
+
186
+ def switch_lang(choice, lang_state):
187
+ code = "id" if "🇮🇩" in (choice or "") else "en"
188
+ return (
189
+ code,
190
+ gr.update(value=f"<h2>{t('title', code)}</h2><p style='color:#64748b'>{t('subtitle', code)}</p>"),
191
+ gr.update(label=t("upload_label", code)),
192
+ gr.update(value="### " + t("choose_target", code)),
193
+ gr.update(label=t("to_video", code)),
194
+ gr.update(label=t("to_audio", code)),
195
+ gr.update(label=t("crf", code)),
196
+ gr.update(label=t("preset", code)),
197
+ gr.update(value=t("convert", code)),
198
+ gr.update(label=t("downlabel", code)),
199
+ t("status_ready", code),
200
+ # modal content will change language too
201
+ gr.update(value=wait_html(code)),
202
+ )
203
+
204
+ # ---------- UI ----------
205
+ def wait_html(lang):
206
+ return f"""
207
+ <div class="wait-overlay">
208
+ <div class="wait-card">
209
+ <div class="spinner"></div>
210
+ <div class="msg">{t('please_wait', lang)}</div>
211
+ </div>
212
+ </div>
213
+ """
214
+
215
+ CSS = """
216
+ .wrap {max-width:1200px;margin:24px auto;}
217
+ .card {background:#fff;border:1px solid #e5e7eb;border-radius:18px;box-shadow:0 12px 40px rgba(2,6,23,.08);padding:18px}
218
+ .g-row {display:grid;grid-template-columns:1fr 420px;gap:24px}
219
+ @media (max-width:980px){.g-row{grid-template-columns:1fr}}
220
+ .targets {background:#eef2f7;border:1px solid #cdd5df;border-radius:14px;padding:14px}
221
+ .lang {display:flex;gap:10px;align-items:center;justify-content:flex-end;margin:0 0 8px}
222
+ .dl .gr-button {background:linear-gradient(90deg,#10b981,#22c55e)!important;color:#fff!important;border:none!important;
223
+ box-shadow:0 10px 22px rgba(16,185,129,.25)}
224
+ .dl .gr-button:disabled{opacity:.5}
225
+
226
+ /* ===== Modal “please wait” ===== */
227
+ .wait-overlay{position:fixed;inset:0;background:rgba(9,9,11,.55);
228
+ display:flex;align-items:center;justify-content:center;z-index:9999}
229
+ .wait-card{background:#111827;color:#e5e7eb;border:1px solid #334155;border-radius:14px;
230
+ box-shadow:0 20px 60px rgba(0,0,0,.35);padding:20px 24px;min-width:260px;display:flex;gap:12px;align-items:center}
231
+ .spinner{width:22px;height:22px;border:3px solid #475569;border-top-color:#22c55e;border-radius:50%;animation:spin .9s linear infinite}
232
+ .msg{font-weight:700}
233
+ @keyframes spin{to{transform:rotate(360deg)}}
234
+ """
235
+
236
+ with gr.Blocks(css=CSS, title="Free Video Converter") as demo:
237
+ lang = gr.State(value="id")
238
+
239
+ with gr.Row(elem_classes="wrap lang"):
240
+ lang_dd = gr.Dropdown(
241
+ choices=[L["id"]["lang_id"], L["en"]["lang_en"]],
242
+ value=L["id"]["lang_id"],
243
+ label=L["id"]["lang_label"],
244
+ allow_custom_value=False,
245
+ )
246
+
247
+ header_md = gr.Markdown(
248
+ f"<div class='wrap'><div class='card'>"
249
+ f"<h2>{t('title','id')}</h2>"
250
+ f"<p style='color:#64748b'>{t('subtitle','id')}</p></div></div>"
251
+ )
252
+
253
+ with gr.Row(elem_classes="wrap g-row"):
254
+ with gr.Column():
255
+ file_in = gr.File(label=t("upload_label","id"), file_count="single", type="filepath")
256
+ status = gr.Markdown(t("status_ready","id"))
257
+ with gr.Column(elem_classes="targets"):
258
+ target_title = gr.Markdown("### " + t("choose_target","id"))
259
+ video_radio = gr.Radio(choices=[], label=t("to_video","id"), interactive=True)
260
+ audio_radio = gr.Radio(choices=[], label=t("to_audio","id"), interactive=True)
261
+ target = gr.State(value=None)
262
+
263
+ with gr.Row(elem_classes="wrap"):
264
+ crf = gr.Slider(1, 35, value=24, step=1, label=t("crf","id"))
265
+ super_fast = gr.Checkbox(value=True, label=t("preset","id"))
266
+
267
+ with gr.Column(elem_classes="wrap"):
268
+ btn = gr.Button(value=t("convert","id"), variant="primary")
269
+ download_btn = gr.DownloadButton(label=t("downlabel","id"), value=None, visible=False, elem_classes="dl")
270
+
271
+ # MODAL (HTML) — awalnya hidden; isinya ikut bahasa
272
+ wait_modal = gr.HTML(wait_html("id"), visible=False)
273
+
274
+ # language change (update teks + modal content)
275
+ lang_dd.change(
276
+ switch_lang,
277
+ inputs=[lang_dd, lang],
278
+ outputs=[lang, header_md, file_in, target_title, video_radio, audio_radio, crf, super_fast, btn, download_btn, status, wait_modal],
279
+ )
280
+
281
+ file_in.change(
282
+ suggest_targets,
283
+ inputs=[file_in, lang],
284
+ outputs=[video_radio, audio_radio, target, status, download_btn],
285
+ )
286
+ video_radio.change(pick_video, inputs=[video_radio], outputs=[target, audio_radio, download_btn])
287
+ audio_radio.change(pick_audio, inputs=[audio_radio], outputs=[target, video_radio, download_btn])
288
+
289
+ # lock → show modal → convert → hide modal + unlock
290
+ (btn.click(
291
+ begin_processing,
292
+ inputs=[lang],
293
+ outputs=[file_in, video_radio, audio_radio, crf, super_fast, btn, download_btn, status, wait_modal],
294
+ queue=False
295
+ ).then(
296
+ convert,
297
+ inputs=[file_in, target, crf, super_fast, lang],
298
+ outputs=[download_btn, status],
299
+ show_progress="full"
300
+ ).then(
301
+ end_processing,
302
+ inputs=None,
303
+ outputs=[file_in, video_radio, audio_radio, crf, super_fast, btn, wait_modal],
304
+ queue=False
305
+ ))
306
+
307
+ demo.queue(max_size=12, default_concurrency_limit=2)
308
+ if __name__ == "__main__":
309
+ demo.launch(share=False)
apt.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ ffmpeg
2
+
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio==5.30.0
2
+