saltudio commited on
Commit
e107f19
·
verified ·
1 Parent(s): c01f21e
Files changed (1) hide show
  1. app.py +57 -43
app.py CHANGED
@@ -1,71 +1,85 @@
1
  import gradio as gr
2
- import os
3
- import re
4
- import tempfile
5
  from typing import List, Tuple
6
  from pydub import AudioSegment
7
 
8
  # ---------- Utils ----------
9
  def ms_to_hhmmss(ms: int) -> str:
10
- seconds = int(round(ms / 1000.0))
11
- h = seconds // 3600
12
- m = (seconds % 3600) // 60
13
- s = seconds % 60
14
  return f"{h:02d}:{m:02d}:{s:02d}" if h > 0 else f"{m:02d}:{s:02d}"
15
 
16
- def clean_title_from_filename(name: str) -> str:
17
- base = os.path.splitext(os.path.basename(name))[0]
18
- base = re.sub(r"^\s*\d{1,3}[\.\-\)_\s]+", "", base) # hapus angka depan
19
  base = re.sub(r"\s{2,}", " ", base).strip()
20
- return base
 
 
 
 
21
 
22
  # ---------- Core ----------
23
- def generate_timecodes_multi(files: List) -> Tuple[str, str]:
24
- if not files:
25
- return "Please upload multiple audio files.", None
 
 
 
 
26
 
27
- # Sort by filename to keep album order
28
- file_objs = sorted(files, key=lambda f: f.name)
 
 
 
 
 
 
 
 
29
 
30
- starts_ms = []
31
- titles = []
32
- cursor = 0
33
- for f in file_objs:
34
- path = f.name if hasattr(f, "name") else f # support dict or file
35
- seg = AudioSegment.from_file(path)
36
- starts_ms.append(cursor)
37
- titles.append(clean_title_from_filename(path))
38
- cursor += len(seg)
39
 
40
- # Build timecodes
41
- lines = [f"{ms_to_hhmmss(ts)} – {title}" for ts, title in zip(starts_ms, titles)]
 
 
42
 
43
  tmpdir = tempfile.mkdtemp()
44
  out_path = os.path.join(tmpdir, "timecodes.txt")
45
  with open(out_path, "w", encoding="utf-8") as f:
46
  f.write("\n".join(lines))
47
-
48
  return "\n".join(lines), out_path
49
 
50
  # ---------- UI ----------
51
- with gr.Blocks(title="Auto Timecode Generator") as demo:
52
- gr.Markdown("## 🎶 Auto Timecode Generator (Batch MP3 → YouTube ready)")
53
- gr.Markdown("Upload audio tracks (MP3/WAV/FLAC), the app will generate **YouTube timecodes** using filenames as titles.")
54
-
55
- with gr.Row():
56
- files = gr.Files(
57
- label="Upload multiple tracks",
58
- file_types=[".mp3", ".wav", ".flac", ".m4a"],
59
- type="file",
60
- )
61
 
62
- gen_btn = gr.Button("Generate Timecodes", variant="primary")
63
- output_box = gr.Textbox(label="Timecodes", lines=18)
64
- dl_file = gr.File(label="Download timecodes.txt")
 
 
65
 
66
- gen_btn.click(fn=generate_timecodes_multi, inputs=[files], outputs=[output_box, dl_file])
 
 
67
 
68
- gr.Markdown("💡 Copy–paste the output directly into your YouTube description!")
 
 
 
69
 
70
  if __name__ == "__main__":
71
  demo.launch()
 
1
  import gradio as gr
2
+ import os, re, tempfile
 
 
3
  from typing import List, Tuple
4
  from pydub import AudioSegment
5
 
6
  # ---------- Utils ----------
7
  def ms_to_hhmmss(ms: int) -> str:
8
+ sec = int(round(ms / 1000.0))
9
+ h = sec // 3600
10
+ m = (sec % 3600) // 60
11
+ s = sec % 60
12
  return f"{h:02d}:{m:02d}:{s:02d}" if h > 0 else f"{m:02d}:{s:02d}"
13
 
14
+ def clean_title(path: str) -> str:
15
+ base = os.path.splitext(os.path.basename(path))[0]
16
+ base = re.sub(r"^\s*\d{1,3}[\.\-\)_\s]+", "", base) # hapus “01. ”, “1 - ”, “02_” dll
17
  base = re.sub(r"\s{2,}", " ", base).strip()
18
+ return base or "Untitled"
19
+
20
+ def natural_key(name: str):
21
+ b = os.path.basename(name)
22
+ return [int(t) if t.isdigit() else t.lower() for t in re.split(r"(\d+)", b)]
23
 
24
  # ---------- Core ----------
25
+ def build_timecodes_from_paths(paths: List[str]) -> Tuple[str, str]:
26
+ if not paths:
27
+ return "Please upload audio files.", None
28
+
29
+ # paths dari gr.File(file_count='multiple', type='filepath') -> List[str]
30
+ # sort natural agar 01 < 2 < 10
31
+ paths = sorted(paths, key=natural_key)
32
 
33
+ starts, titles, cursor = [], [], 0
34
+ skipped = []
35
+ for p in paths:
36
+ try:
37
+ seg = AudioSegment.from_file(p)
38
+ starts.append(cursor)
39
+ titles.append(clean_title(p))
40
+ cursor += len(seg)
41
+ except Exception as e:
42
+ skipped.append(f"{os.path.basename(p)} ({e})")
43
 
44
+ if not starts:
45
+ return "No readable audio files. Please re-upload.", None
 
 
 
 
 
 
 
46
 
47
+ lines = [f"{ms_to_hhmmss(ts)} – {t}" for ts, t in zip(starts, titles)]
48
+ if skipped:
49
+ lines.append("\n# Skipped files")
50
+ lines += [f"- {msg}" for msg in skipped]
51
 
52
  tmpdir = tempfile.mkdtemp()
53
  out_path = os.path.join(tmpdir, "timecodes.txt")
54
  with open(out_path, "w", encoding="utf-8") as f:
55
  f.write("\n".join(lines))
 
56
  return "\n".join(lines), out_path
57
 
58
  # ---------- UI ----------
59
+ with gr.Blocks(title="Auto Timecode Generator (Batch Tracks)") as demo:
60
+ gr.Markdown(
61
+ "## 🎶 Auto Timecode Generator (Batch YouTube)\n"
62
+ "Upload beberapa file audio (MP3/WAV/FLAC/M4A). Aplikasi akan:\n"
63
+ "- Urutkan berdasarkan **nama file** (natural sort)\n"
64
+ "- Ambil **judul** dari nama file (tanpa nomor & ekstensi)\n"
65
+ "- Hitung **timestamp kumulatif** berdasarkan durasi tiap file\n"
66
+ "- Menghasilkan output siap **copy–paste** + **download `timecodes.txt`**"
67
+ )
 
68
 
69
+ files = gr.File(
70
+ label="Upload multiple audio files",
71
+ file_count="multiple", # <- penting
72
+ type="filepath", # <- penting
73
+ )
74
 
75
+ btn = gr.Button("Generate Timecodes", variant="primary")
76
+ out = gr.Textbox(label="Timecodes", lines=22)
77
+ dl = gr.File(label="Download timecodes.txt")
78
 
79
+ # Auto-generate begitu selesai upload
80
+ files.upload(fn=build_timecodes_from_paths, inputs=[files], outputs=[out, dl])
81
+ # Tombol manual juga ada
82
+ btn.click(fn=build_timecodes_from_paths, inputs=[files], outputs=[out, dl])
83
 
84
  if __name__ == "__main__":
85
  demo.launch()