dodd869 commited on
Commit
c5dc168
·
verified ·
1 Parent(s): 285b466

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +84 -97
main.py CHANGED
@@ -2,120 +2,107 @@ import os, json, glob, shutil, subprocess, tempfile, time, re, requests
2
  from pathlib import Path
3
  import gradio as gr
4
  from ytmusicapi import YTMusic
5
- import spotipy
6
- from spotipy.oauth2 import SpotifyClientCredentials
7
  from mutagen.id3 import ID3, TIT2, TPE1, TALB, TCON, TYER, TRCK, TPE2, USLT, APIC, COMM
8
  from mutagen.mp3 import MP3
9
 
10
- # ------------- CONFIG -------------
11
- # Secrets must be set in HF Space → Settings → Secrets
12
- SPOTIFY_CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
13
- SPOTIFY_CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET")
14
- # ----------------------------------
15
 
16
- TMP = Path("/tmp") # HF allows full r/w here
17
- STATIC = Path(__file__).parent # read-only, keep code / assets only
18
-
19
- ytm = YTMusic()
20
- sp = None
21
- if SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET:
22
- sp = spotipy.Spotify(
23
- auth_manager=SpotifyClientCredentials(
24
- client_id=SPOTIFY_CLIENT_ID,
25
- client_secret=SPOTIFY_CLIENT_SECRET,
26
- )
27
- )
28
-
29
- # ------------- HELPERS -------------
30
  def safe(s: str) -> str:
31
  return re.sub(r'[\\/:"*?<>|]+', "", s).strip()
32
 
33
  def ytdlp_fast(url: str, out: str) -> None:
34
- """Download best-audio → 320 k MP3 using aria2c."""
35
- subprocess.run(
36
- [
37
- "yt-dlp",
38
- "-f", "bestaudio/best",
39
- "--extract-audio",
40
- "--audio-format", "mp3",
41
- "--audio-quality", "320K",
42
- "--concurrent-fragments", "16",
43
- "--external-downloader", "aria2c",
44
- "--external-downloader-args",
45
- "aria2c:-x16 -s16 -j16 -k1M --file-allocation=none",
46
- "--fragment-retries", "infinite",
47
- "--skip-unavailable-fragments",
48
- "--no-part",
49
- "--no-cache-dir",
50
- "--force-ipv4",
51
- "--sponsorblock-remove", "all",
52
- "-o", out,
53
- url,
54
- ],
55
- check=True,
56
- )
57
 
58
- def write_tags(
59
- fn: str,
60
- title: str,
61
- artist: str,
62
- album: str,
63
- year: str,
64
- cover: str,
65
- lyrics: str,
66
- genre: str = "",
67
- track: str = "",
68
- album_artist: str = "",
69
- description: str = "",
70
- ):
71
  audio = MP3(fn, ID3=ID3)
72
- if audio.tags is None:
73
- audio.add_tags()
74
  tags = audio.tags
75
  tags.add(TIT2(encoding=3, text=title))
76
  tags.add(TPE1(encoding=3, text=artist))
77
  tags.add(TALB(encoding=3, text=album))
78
- if genre:
79
- tags.add(TCON(encoding=3, text=genre))
80
- if year:
81
- tags.add(TYER(encoding=3, text=year))
82
- if track:
83
- tags.add(TRCK(encoding=3, text=track))
84
- if album_artist:
85
- tags.add(TPE2(encoding=3, text=album_artist))
86
- if lyrics:
87
- tags.add(USLT(encoding=3, lang="eng", desc="", text=lyrics))
88
- if description:
89
- tags.add(COMM(encoding=3, lang="eng", desc="desc", text=description))
90
  if cover:
91
  try:
92
  img = requests.get(cover, timeout=15).content
93
- tags.add(
94
- APIC(
95
- encoding=3,
96
- mime="image/jpeg",
97
- type=3,
98
- desc="Cover",
99
- data=img,
100
- )
101
- )
102
- except Exception:
103
- pass
104
  audio.save(v2_version=3)
105
 
106
- # ------------- CORE -------------
107
- def download_spotify(url: str) -> Path | None:
108
- if not sp:
109
- raise RuntimeError("Spotipy credentials not set")
110
- track = sp.track(url)
111
- title = track["name"]
112
- artist = ", ".join(a["name"] for a in track["artists"])
113
- album = track["album"]["name"]
114
- date = track["album"].get("release_date", "")
115
- year = date.split("-")[0] if date else ""
116
- cover = track["album"]["images"][0]["url"]
117
- query = f"{title} {artist}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
- # search YTM
120
- res = ytm.search(query, filter="songs", limit
121
-
 
2
  from pathlib import Path
3
  import gradio as gr
4
  from ytmusicapi import YTMusic
 
 
5
  from mutagen.id3 import ID3, TIT2, TPE1, TALB, TCON, TYER, TRCK, TPE2, USLT, APIC, COMM
6
  from mutagen.mp3 import MP3
7
 
8
+ TMP = Path("/tmp")
9
+ STATIC = Path(__file__).parent
10
+ ytm = YTMusic()
 
 
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  def safe(s: str) -> str:
13
  return re.sub(r'[\\/:"*?<>|]+', "", s).strip()
14
 
15
  def ytdlp_fast(url: str, out: str) -> None:
16
+ subprocess.run([
17
+ "yt-dlp", "-f", "bestaudio/best", "--extract-audio",
18
+ "--audio-format", "mp3", "--audio-quality", "320K",
19
+ "--concurrent-fragments", "16",
20
+ "--external-downloader", "aria2c",
21
+ "--external-downloader-args", "aria2c:-x16 -s16 -j16 -k1M",
22
+ "--fragment-retries", "infinite", "--skip-unavailable-fragments",
23
+ "--no-part", "--no-cache-dir", "--force-ipv4",
24
+ "--sponsorblock-remove", "all", "-o", out, url
25
+ ], check=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ def write_tags(fn: str, title: str, artist: str, album: str, year: str, cover: str,
28
+ lyrics: str, genre: str = "", track: str = "", album_artist: str = "",
29
+ description: str = ""):
 
 
 
 
 
 
 
 
 
 
30
  audio = MP3(fn, ID3=ID3)
31
+ if audio.tags is None: audio.add_tags()
 
32
  tags = audio.tags
33
  tags.add(TIT2(encoding=3, text=title))
34
  tags.add(TPE1(encoding=3, text=artist))
35
  tags.add(TALB(encoding=3, text=album))
36
+ for tag, cls, val in [(genre, TCON, genre), (year, TYER, year), (track, TRCK, track),
37
+ (album_artist, TPE2, album_artist)]:
38
+ if val: tags.add(cls(encoding=3, text=val))
39
+ if lyrics: tags.add(USLT(encoding=3, lang="eng", desc="", text=lyrics))
40
+ if description: tags.add(COMM(encoding=3, lang="eng", desc="desc", text=description))
 
 
 
 
 
 
 
41
  if cover:
42
  try:
43
  img = requests.get(cover, timeout=15).content
44
+ tags.add(APIC(encoding=3, mime="image/jpeg", type=3, desc="Cover", data=img))
45
+ except Exception: pass
 
 
 
 
 
 
 
 
 
46
  audio.save(v2_version=3)
47
 
48
+ def download_yt(url: str) -> Path:
49
+ info_json = TMP / "info.json"
50
+ subprocess.run([
51
+ "yt-dlp", "--skip-download", "--write-info-json", "--write-thumbnail",
52
+ "-o", str(TMP / "tmp"), url
53
+ ], check=True)
54
+ files = list(TMP.glob("tmp.*"))
55
+ info_file = next((f for f in files if f.suffix == ".json"), None)
56
+ if not info_file: raise RuntimeError("info json missing")
57
+ info = json.loads(info_file.read_text(encoding="utf8"))
58
+ vid = info["id"]
59
+ title = info.get("title") or info.get("fulltitle") or "Unknown"
60
+ author = info.get("artist") or info.get("uploader") or "Unknown"
61
+ upload_date = info.get("upload_date", "")
62
+ extractor = info.get("extractor", "")
63
+ description = info.get("description", "")
64
+ album, album_artist, genre, track, year, lyrics = "Single", "", "", "0", "", ""
65
+ if "music.youtube" in url or extractor.startswith("youtube:music"):
66
+ album = info.get("album") or "Single"
67
+ album_artist = info.get("album_artist", "")
68
+ track = str(info.get("track_number", 0))
69
+ year = str(info.get("release_date") or upload_date)
70
+ genre = info.get("genre", "") or info.get("category", "")
71
+ try:
72
+ pl = ytm.get_watch_playlist(videoId=vid)
73
+ if pl.get("lyrics"):
74
+ lyr = ytm.get_lyrics(pl["lyrics"])
75
+ if lyr and lyr.get("lyrics"): lyrics = lyr["lyrics"]
76
+ except Exception: pass
77
+ else:
78
+ year = upload_date
79
+ base_name = safe(f"{title} [{vid}]")
80
+ out_mp3 = TMP / f"{base_name}.mp3"
81
+ ytdlp_fast(url, str(TMP / f"{base_name}.%(ext)s"))
82
+ candidates = [f for f in TMP.glob(f"{base_name}*") if f.suffix.lower() == ".mp3"]
83
+ if not candidates: raise RuntimeError("mp3 not created")
84
+ shutil.move(str(candidates[0]), str(out_mp3))
85
+ cover = None
86
+ for ext in ("jpg", "jpeg", "webp", "png"):
87
+ thumb = TMP / f"tmp.{ext}"
88
+ if thumb.exists(): cover = str(thumb); break
89
+ write_tags(str(out_mp3), title, author, album, year, cover, lyrics,
90
+ genre, track, album_artist, description)
91
+ return out_mp3
92
+
93
+ def gradio_fn(url):
94
+ try:
95
+ path = download_yt(url)
96
+ return str(path), path.name
97
+ except Exception as e:
98
+ return None, f"Error: {e}"
99
+
100
+ with gr.Blocks(title="YT/YT-Music Downloader") as demo:
101
+ gr.Markdown("### Paste YouTube / YouTube-Music link → 320 k MP3 with tags")
102
+ with gr.Row():
103
+ inp = gr.Textbox(label="URL", placeholder="https://www.youtube.com/watch?v=...")
104
+ btn = gr.Button("Download", variant="primary")
105
+ out = gr.File(label="MP3")
106
+ btn.click(gradio_fn, inputs=inp, outputs=[out, out])
107
 
108
+ demo.launch(server_name="0.0.0.0", server_port=7860)