Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import os, re, tempfile | |
| from typing import List, Tuple | |
| from pydub import AudioSegment | |
| # ---------- Utils ---------- | |
| def ms_to_hhmmss(ms: int) -> str: | |
| sec = int(round(ms / 1000.0)) | |
| h = sec // 3600 | |
| m = (sec % 3600) // 60 | |
| s = sec % 60 | |
| return f"{h:02d}:{m:02d}:{s:02d}" if h > 0 else f"{m:02d}:{s:02d}" | |
| def clean_title(path: str) -> str: | |
| base = os.path.splitext(os.path.basename(path))[0] | |
| base = re.sub(r"^\s*\d{1,3}[\.\-\)_\s]+", "", base) # hapus โ01. โ, โ1 - โ, โ02_โ dll | |
| base = re.sub(r"\s{2,}", " ", base).strip() | |
| return base or "Untitled" | |
| def natural_key(name: str): | |
| b = os.path.basename(name) | |
| return [int(t) if t.isdigit() else t.lower() for t in re.split(r"(\d+)", b)] | |
| # ---------- Core ---------- | |
| def build_timecodes_from_paths(paths: List[str]) -> Tuple[str, str]: | |
| if not paths: | |
| return "Please upload audio files.", None | |
| # paths dari gr.File(file_count='multiple', type='filepath') -> List[str] | |
| # sort natural agar 01 < 2 < 10 | |
| paths = sorted(paths, key=natural_key) | |
| starts, titles, cursor = [], [], 0 | |
| skipped = [] | |
| for p in paths: | |
| try: | |
| seg = AudioSegment.from_file(p) | |
| starts.append(cursor) | |
| titles.append(clean_title(p)) | |
| cursor += len(seg) | |
| except Exception as e: | |
| skipped.append(f"{os.path.basename(p)} ({e})") | |
| if not starts: | |
| return "No readable audio files. Please re-upload.", None | |
| lines = [f"{ms_to_hhmmss(ts)} โ {t}" for ts, t in zip(starts, titles)] | |
| if skipped: | |
| lines.append("\n# Skipped files") | |
| lines += [f"- {msg}" for msg in skipped] | |
| tmpdir = tempfile.mkdtemp() | |
| out_path = os.path.join(tmpdir, "timecodes.txt") | |
| with open(out_path, "w", encoding="utf-8") as f: | |
| f.write("\n".join(lines)) | |
| return "\n".join(lines), out_path | |
| # ---------- UI ---------- | |
| with gr.Blocks(title="Auto Timecode Generator (Batch Tracks)") as demo: | |
| gr.Markdown( | |
| "## ๐ถ Auto Timecode Generator (Batch โ YouTube)\n" | |
| "Upload beberapa file audio (MP3/WAV/FLAC/M4A). Aplikasi akan:\n" | |
| "- Urutkan berdasarkan **nama file** (natural sort)\n" | |
| "- Ambil **judul** dari nama file (tanpa nomor & ekstensi)\n" | |
| "- Hitung **timestamp kumulatif** berdasarkan durasi tiap file\n" | |
| "- Menghasilkan output siap **copyโpaste** + **download `timecodes.txt`**" | |
| ) | |
| files = gr.File( | |
| label="Upload multiple audio files", | |
| file_count="multiple", # <- penting | |
| type="filepath", # <- penting | |
| ) | |
| btn = gr.Button("Generate Timecodes", variant="primary") | |
| out = gr.Textbox(label="Timecodes", lines=22) | |
| dl = gr.File(label="Download timecodes.txt") | |
| # Auto-generate begitu selesai upload | |
| files.upload(fn=build_timecodes_from_paths, inputs=[files], outputs=[out, dl]) | |
| # Tombol manual juga ada | |
| btn.click(fn=build_timecodes_from_paths, inputs=[files], outputs=[out, dl]) | |
| if __name__ == "__main__": | |
| demo.launch() | |