admin commited on
Commit
b02246d
·
1 Parent(s): 262568e
app.py CHANGED
@@ -1,28 +1,41 @@
1
  import gradio as gr
2
- from netease import parser163
3
- from qq import qmusic_parser
4
- from kuwo import kuwo_parser
5
- from lizhi import lizhifm_parser
6
- from meta import music_meta_editor
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
  if __name__ == "__main__":
9
  with gr.Blocks() as demo:
10
- gr.Markdown(
11
- "This site does not provide any audio storage services, only provide the most basic parsing services, please DO NOT abuse"
12
- )
13
- with gr.Tab("Music163"):
14
  parser163()
15
 
16
- with gr.Tab("QQ"):
17
  qmusic_parser()
18
 
19
- with gr.Tab("Kuwo"):
20
  kuwo_parser()
21
 
22
- with gr.Tab("LizhiFM"):
23
  lizhifm_parser()
24
 
25
- with gr.Tab("MetaEditor"):
26
  music_meta_editor()
27
 
28
  demo.launch()
 
1
  import gradio as gr
2
+ from modules.netease import parser163
3
+ from modules.qq import qmusic_parser
4
+ from modules.kuwo import kuwo_parser
5
+ from modules.lizhi import lizhifm_parser
6
+ from modules.meta import music_meta_editor
7
+ from utils import LANG
8
+
9
+ ZH2EN = {
10
+ "本站不提供任何音频存储服务,仅提供最基本的解析服务,请勿滥用": "This site does not provide any audio storage services, only provide the most basic parsing services, please DO NOT abuse",
11
+ "网易云音乐": "Music163",
12
+ "QQ音乐": "QQ",
13
+ "酷我音乐": "Kuwo",
14
+ "荔枝FM": "LizhiFM",
15
+ "元信息编辑器": "MetaEditor",
16
+ }
17
+
18
+
19
+ def _L(zh_txt: str):
20
+ return ZH2EN[zh_txt] if LANG else zh_txt
21
+
22
 
23
  if __name__ == "__main__":
24
  with gr.Blocks() as demo:
25
+ gr.Markdown(_L("本站不提供任何音频存储服务,仅提供最基本的解析服务,请勿滥用"))
26
+ with gr.Tab(_L("网易云音乐")):
 
 
27
  parser163()
28
 
29
+ with gr.Tab(_L("QQ音乐")):
30
  qmusic_parser()
31
 
32
+ with gr.Tab(_L("酷我音乐")):
33
  kuwo_parser()
34
 
35
+ with gr.Tab(_L("荔枝FM")):
36
  lizhifm_parser()
37
 
38
+ with gr.Tab(_L("元信息编辑器")):
39
  music_meta_editor()
40
 
41
  demo.launch()
config.py DELETED
@@ -1,16 +0,0 @@
1
- import os
2
-
3
- API_163 = os.getenv("api_music163")
4
- API_KUWO = os.getenv("api_kuwo")
5
- API_QQ_2 = os.getenv("api_qmusic_2")
6
- API_QQ_1 = os.getenv("api_qmusic_1")
7
- KEY_QQ_1 = os.getenv("apikey_qmusic_1")
8
- if not (API_163 and API_KUWO and API_QQ_2 and API_QQ_1 and KEY_QQ_1):
9
- print("Please check your environment var!")
10
- exit()
11
-
12
- TIMEOUT = None
13
- TMP_DIR = "./__pycache__"
14
- HEADER = {
15
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36"
16
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fail.mp3 DELETED
Binary file (6.48 kB)
 
kuwo.py → modules/kuwo.py RENAMED
@@ -2,38 +2,26 @@ import os
2
  import re
3
  import requests
4
  import gradio as gr
5
- from tqdm import tqdm
6
- from utils import insert_meta, clean_dir, timestamp
7
- from config import API_KUWO, TIMEOUT, TMP_DIR
8
-
9
-
10
- def download_file(
11
- id, url: str, title, artist, album, lyric, cover, cache=f"{TMP_DIR}/kuwo"
12
- ):
13
- clean_dir(cache)
14
- fmt = url.split(".")[-1]
15
- local_file = f"{cache}/{id}.{fmt}"
16
- response = requests.get(url, stream=True)
17
- if response.status_code == 200:
18
- total_size = int(response.headers.get("Content-Length", 0)) + 1
19
- time_stamp = timestamp()
20
- progress_bar = tqdm(
21
- total=total_size,
22
- unit="B",
23
- unit_scale=True,
24
- desc=f"[{time_stamp}] {local_file}",
25
- )
26
- with open(local_file, "wb") as f:
27
- for chunk in response.iter_content(chunk_size=8192):
28
- if chunk:
29
- f.write(chunk)
30
- progress_bar.update(len(chunk))
31
-
32
- insert_meta(local_file, title, artist, album, lyric, cover)
33
- return local_file
34
 
35
- else:
36
- raise ConnectionError(f"Download Failure, status code: {response.status_code}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
 
39
  def parse_id(url: str):
@@ -52,37 +40,38 @@ def parse_id(url: str):
52
 
53
  def get_file_size(file_path):
54
  size_in_bytes = os.path.getsize(file_path)
55
- units = ["B", "K", "M", "G"]
56
- unit_index = 0
57
- if size_in_bytes >= 1024**3:
 
58
  unit_index = 3
59
  size = size_in_bytes / (1024**3)
60
 
61
- elif size_in_bytes >= 1024**2:
62
  unit_index = 2
63
  size = size_in_bytes / (1024**2)
64
 
65
- elif size_in_bytes >= 1024:
66
  unit_index = 1
67
  size = size_in_bytes / 1024
68
 
69
  else:
70
  size = size_in_bytes
71
-
72
  if unit_index == 0:
73
- return f"{size} {units[unit_index]}"
74
  else:
75
- return f"{size:.2f} {units[unit_index]}"
76
 
77
 
78
- # outer func
79
  def infer(url: str):
 
80
  song = title = cover = artist = album = quality = size = lyric = None
81
  try:
82
  song_id = parse_id(url)
83
  if not song_id:
84
- title = "Please enter a valid URL or ID!"
85
- return song, title, artist, album, lyric, cover, song_id, quality, size
86
 
87
  response = requests.get(API_KUWO, params={"id": song_id}, timeout=TIMEOUT)
88
  if response.status_code == 200:
@@ -108,13 +97,17 @@ def infer(url: str):
108
  album,
109
  lyric,
110
  cover,
 
111
  )
112
  size = size.upper() if size else get_file_size(song)
113
 
 
 
 
114
  except Exception as e:
115
- size = f"{e}"
116
 
117
- return song, title, artist, album, lyric, cover, song_id, quality, size
118
 
119
 
120
  def kuwo_parser():
@@ -122,26 +115,27 @@ def kuwo_parser():
122
  fn=infer,
123
  inputs=[
124
  gr.Textbox(
125
- label="Please enter Kuwo music ID or URL Link",
126
  placeholder="https://kuwo.cn/play_detail/*",
127
  )
128
  ],
129
  outputs=[
 
130
  gr.Audio(
131
- label="Audio with metadata",
132
  show_download_button=True,
133
  show_share_button=False,
134
  ),
135
- gr.Textbox(label="Title", show_copy_button=True),
136
- gr.Textbox(label="Artist", show_copy_button=True),
137
- gr.Textbox(label="Album", show_copy_button=True),
138
- gr.TextArea(label="Lyrics", show_copy_button=True),
139
- gr.Image(label="Cover", show_share_button=False),
140
- gr.Textbox(label="Song ID", show_copy_button=True),
141
- gr.Textbox(label="Quality", show_copy_button=True),
142
- gr.Textbox(label="Size", show_copy_button=True),
143
  ],
144
- title="Kuwo Music URL Parser without Loss",
145
  flagging_mode="never",
146
  examples=[
147
  "237668532",
 
2
  import re
3
  import requests
4
  import gradio as gr
5
+ from utils import download_file, TIMEOUT, API_KUWO, TMP_DIR, LANG
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
+ ZH2EN = {
8
+ "请输入酷我音乐 ID URL 链接": "Please enter Kuwo music ID or URL Link",
9
+ "含元信息音频下载": "Audio with metadata",
10
+ "歌名": "Title",
11
+ "作者": "Artist",
12
+ "专辑": "Album",
13
+ "歌词": "Lyrics",
14
+ "歌曲图片": "Cover",
15
+ "歌曲 ID": "Song ID",
16
+ "音质": "Quality",
17
+ "大小": "Size",
18
+ "酷我音乐无损解析": "Kuwo Music URL Parser without Loss",
19
+ "状态栏": "Status",
20
+ }
21
+
22
+
23
+ def _L(zh_txt: str):
24
+ return ZH2EN[zh_txt] if LANG else zh_txt
25
 
26
 
27
  def parse_id(url: str):
 
40
 
41
  def get_file_size(file_path):
42
  size_in_bytes = os.path.getsize(file_path)
43
+ units = ["B", "KB", "MB", "GB"]
44
+ unit_index = 0 # 初始单位为字节
45
+ # 根据文件大小选择合适的单位
46
+ if size_in_bytes >= 1024**3: # 大于等于 1 GB
47
  unit_index = 3
48
  size = size_in_bytes / (1024**3)
49
 
50
+ elif size_in_bytes >= 1024**2: # 大于等于 1 MB
51
  unit_index = 2
52
  size = size_in_bytes / (1024**2)
53
 
54
+ elif size_in_bytes >= 1024: # 大于等于 1 KB
55
  unit_index = 1
56
  size = size_in_bytes / 1024
57
 
58
  else:
59
  size = size_in_bytes
60
+ # 格式化输出,保留两位小数
61
  if unit_index == 0:
62
+ return f"{size}{units[unit_index]}"
63
  else:
64
+ return f"{size:.2f}{units[unit_index]}"
65
 
66
 
67
+ # outer func requires try except
68
  def infer(url: str):
69
+ status = "Success"
70
  song = title = cover = artist = album = quality = size = lyric = None
71
  try:
72
  song_id = parse_id(url)
73
  if not song_id:
74
+ raise ValueError("请输入有效的网址或ID!")
 
75
 
76
  response = requests.get(API_KUWO, params={"id": song_id}, timeout=TIMEOUT)
77
  if response.status_code == 200:
 
97
  album,
98
  lyric,
99
  cover,
100
+ f"{TMP_DIR}/kuwo",
101
  )
102
  size = size.upper() if size else get_file_size(song)
103
 
104
+ else:
105
+ raise ConnectionError(f"HTTP {response.status_code}")
106
+
107
  except Exception as e:
108
+ status = f"{e}"
109
 
110
+ return status, song, title, artist, album, lyric, cover, song_id, quality, size
111
 
112
 
113
  def kuwo_parser():
 
115
  fn=infer,
116
  inputs=[
117
  gr.Textbox(
118
+ label=_L("请输入酷我音乐 ID URL 链接"),
119
  placeholder="https://kuwo.cn/play_detail/*",
120
  )
121
  ],
122
  outputs=[
123
+ gr.Textbox(label=_L("状态栏"), show_copy_button=True),
124
  gr.Audio(
125
+ label=_L("含元信息音频下载"),
126
  show_download_button=True,
127
  show_share_button=False,
128
  ),
129
+ gr.Textbox(label=_L("歌名"), show_copy_button=True),
130
+ gr.Textbox(label=_L("作者"), show_copy_button=True),
131
+ gr.Textbox(label=_L("专辑"), show_copy_button=True),
132
+ gr.TextArea(label=_L("歌词"), show_copy_button=True),
133
+ gr.Image(label=_L("歌曲图片"), show_share_button=False),
134
+ gr.Textbox(label=_L("歌曲 ID"), show_copy_button=True),
135
+ gr.Textbox(label=_L("音质"), show_copy_button=True),
136
+ gr.Textbox(label=_L("大小"), show_copy_button=True),
137
  ],
138
+ title=_L("酷我音乐无损解析"),
139
  flagging_mode="never",
140
  examples=[
141
  "237668532",
lizhi.py → modules/lizhi.py RENAMED
@@ -3,8 +3,20 @@ import gradio as gr
3
  from tqdm import tqdm
4
  from pydub import AudioSegment
5
  from datetime import datetime, timedelta
6
- from utils import rm_dir, mk_dir, extract_fst_int, timestamp
7
- from config import TMP_DIR
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
 
10
  def get_prev_day(date_str):
@@ -14,15 +26,20 @@ def get_prev_day(date_str):
14
  return previous_day.strftime(date_format)
15
 
16
 
17
- def rm_end_seconds(input_file: str, output_file: str, seconds: float):
 
 
 
 
18
  audio = AudioSegment.from_file(input_file)
19
  remove_ms = seconds * 1000
20
  new_audio = audio[:-remove_ms]
21
  new_audio.export(output_file, format="mp3")
 
22
 
23
 
24
  def download_mp3(url: str, local_file: str):
25
- response = requests.get(url)
26
  retcode = response.status_code
27
  if retcode == 200:
28
  total_size = int(response.headers.get("Content-Length", 0)) + 1
@@ -35,45 +52,45 @@ def download_mp3(url: str, local_file: str):
35
  )
36
  with open(local_file, "wb") as f:
37
  for chunk in response.iter_content(chunk_size=8192):
38
- if chunk:
39
- f.write(chunk)
40
  progress_bar.update(len(chunk))
41
 
42
- rm_end_seconds(local_file, local_file, 3.1)
43
 
44
  elif retcode == 404:
45
  bad_date = "/".join(url.split("/audio/")[-1].split("/")[:-1])
46
  fixed_date = get_prev_day(bad_date)
47
  fixed_url = url.replace(bad_date, fixed_date)
48
- download_mp3(fixed_url, local_file)
49
 
50
  else:
51
- raise ConnectionError(f"Error: {response.status_code}, {response.text}")
52
 
53
 
54
- # outer func
55
  def infer(page_url: str, date: str, cache=f"{TMP_DIR}/lizhi"):
56
- fail_voice = "./fail.mp3"
 
57
  try:
58
  rm_dir(cache)
59
  if not page_url:
60
- raise ValueError("Empty song url or id!")
61
 
62
  if ("http" in page_url and ".lizhi" in page_url) or page_url.isdigit():
63
  sound_id = extract_fst_int(page_url.split("/")[-1])
64
  else:
65
- raise ValueError("Invalid song url or id!")
66
 
67
  voice_time = date.strip().replace("-", "/")
68
  mp3_url = f"http://cdn5.lizhi.fm/audio/{voice_time}/{sound_id}_hd.mp3"
69
- outpath = f"{cache}/{sound_id}.mp3"
70
  mk_dir(cache)
71
- download_mp3(mp3_url, outpath)
72
- return outpath
73
 
74
  except Exception as e:
75
- print(e)
76
- return fail_voice
 
77
 
78
 
79
  def lizhifm_parser():
@@ -81,21 +98,20 @@ def lizhifm_parser():
81
  fn=infer,
82
  inputs=[
83
  gr.Textbox(
84
- label="Enter the sound page URL",
85
  placeholder="https://www.lizhi.fm/*/*",
86
  ),
87
- gr.Textbox(
88
- label="Enter sound publication date in format",
89
- placeholder="YYYY-MM-DD",
90
- ),
 
91
  ],
92
- outputs=gr.Audio(
93
- label="Download MP3",
94
- show_download_button=True,
95
- ),
96
- title="Lizhi FM Audio Direct URL Parsing Tool",
97
  flagging_mode="never",
98
- description="The <a href='https://tool.lu/datecalc' target='_blank'>datecalc</a> is highly recommanded.",
 
 
 
99
  examples=[
100
  ["https://www.lizhi.fm/voice/3136401036767886342", "2025-04-05"],
101
  ["https://www.lizhifm.com/voice/3136401036767886342", "2025-04-05"],
 
3
  from tqdm import tqdm
4
  from pydub import AudioSegment
5
  from datetime import datetime, timedelta
6
+ from utils import timestamp, extract_fst_int, rm_dir, mk_dir, TMP_DIR, LANG
7
+
8
+ ZH2EN = {
9
+ "输入声音页 URL": "Enter the sound page URL",
10
+ "按格式输入声音发布日期": "Enter sound publication date in format",
11
+ "下载 MP3": "Download MP3",
12
+ "荔枝FM音频解析下载": "Lizhi FM Audio Direct URL Parsing Tool",
13
+ "推荐辅助工具 <a href='https://tool.lu/datecalc' target='_blank'>日期计算器</a>": "The <a href='https://tool.lu/en_US/datecalc' target='_blank'>datecalc</a> is highly recommanded.",
14
+ "状态栏": "Status",
15
+ }
16
+
17
+
18
+ def _L(zh_txt: str):
19
+ return ZH2EN[zh_txt] if LANG else zh_txt
20
 
21
 
22
  def get_prev_day(date_str):
 
26
  return previous_day.strftime(date_format)
27
 
28
 
29
+ def rm_end_seconds(input_file: str, output_file="", seconds=3.1):
30
+ print("移除音频水印...")
31
+ if not output_file:
32
+ output_file = input_file
33
+
34
  audio = AudioSegment.from_file(input_file)
35
  remove_ms = seconds * 1000
36
  new_audio = audio[:-remove_ms]
37
  new_audio.export(output_file, format="mp3")
38
+ return output_file
39
 
40
 
41
  def download_mp3(url: str, local_file: str):
42
+ response = requests.get(url, stream=True)
43
  retcode = response.status_code
44
  if retcode == 200:
45
  total_size = int(response.headers.get("Content-Length", 0)) + 1
 
52
  )
53
  with open(local_file, "wb") as f:
54
  for chunk in response.iter_content(chunk_size=8192):
55
+ if chunk: # 确保 chunk 不为空
56
+ f.write(chunk) # 更新进度条
57
  progress_bar.update(len(chunk))
58
 
59
+ return rm_end_seconds(local_file)
60
 
61
  elif retcode == 404:
62
  bad_date = "/".join(url.split("/audio/")[-1].split("/")[:-1])
63
  fixed_date = get_prev_day(bad_date)
64
  fixed_url = url.replace(bad_date, fixed_date)
65
+ return download_mp3(fixed_url, local_file)
66
 
67
  else:
68
+ raise ConnectionError(f"错误: {retcode}, {response.text}")
69
 
70
 
71
+ # outer func requires try except
72
  def infer(page_url: str, date: str, cache=f"{TMP_DIR}/lizhi"):
73
+ status = "Success"
74
+ outpath = None
75
  try:
76
  rm_dir(cache)
77
  if not page_url:
78
+ raise ValueError("声音链接或ID为空")
79
 
80
  if ("http" in page_url and ".lizhi" in page_url) or page_url.isdigit():
81
  sound_id = extract_fst_int(page_url.split("/")[-1])
82
  else:
83
+ raise ValueError("无效的声音链接或ID")
84
 
85
  voice_time = date.strip().replace("-", "/")
86
  mp3_url = f"http://cdn5.lizhi.fm/audio/{voice_time}/{sound_id}_hd.mp3"
 
87
  mk_dir(cache)
88
+ outpath = download_mp3(mp3_url, f"{cache}/{sound_id}.mp3")
 
89
 
90
  except Exception as e:
91
+ status = f"{e}"
92
+
93
+ return status, outpath
94
 
95
 
96
  def lizhifm_parser():
 
98
  fn=infer,
99
  inputs=[
100
  gr.Textbox(
101
+ label=_L("输入声音页 URL"),
102
  placeholder="https://www.lizhi.fm/*/*",
103
  ),
104
+ gr.Textbox(label=_L("按格式输入声音发布日期"), placeholder="YYYY-MM-DD"),
105
+ ],
106
+ outputs=[
107
+ gr.Textbox(label=_L("状态栏"), show_copy_button=True),
108
+ gr.Audio(label=_L("下载 MP3"), show_download_button=True),
109
  ],
 
 
 
 
 
110
  flagging_mode="never",
111
+ title=_L("荔枝FM音频解析下载"),
112
+ description=_L(
113
+ "推荐辅助工具 <a href='https://tool.lu/datecalc' target='_blank'>日期计算器</a>"
114
+ ),
115
  examples=[
116
  ["https://www.lizhi.fm/voice/3136401036767886342", "2025-04-05"],
117
  ["https://www.lizhifm.com/voice/3136401036767886342", "2025-04-05"],
meta.py → modules/meta.py RENAMED
@@ -4,18 +4,36 @@ import gradio as gr
4
  from mutagen.mp3 import MP3
5
  from mutagen.flac import FLAC
6
  from mutagen.id3 import USLT, PictureType
7
- from utils import insert_meta, clean_dir
8
- from config import TMP_DIR
9
 
10
  CACHE_DIR = f"{TMP_DIR}/meta"
11
-
12
-
13
- # outer func
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  def extract_metadata(file_path: str):
 
15
  title = fname = artist = album = lyrics = cover = None
16
  try:
17
  if not file_path:
18
- raise ValueError("Empty file path!")
19
 
20
  fname = os.path.splitext(os.path.basename(file_path))[0]
21
  file_ext = os.path.splitext(file_path)[1].lower()
@@ -25,9 +43,9 @@ def extract_metadata(file_path: str):
25
  title, artist, album, lyrics, cover = extract_flac_meta(file_path)
26
 
27
  except Exception as e:
28
- title = f"{e}"
29
 
30
- return title, fname, artist, album, lyrics, cover
31
 
32
 
33
  def extract_mp3_meta(file_path):
@@ -41,9 +59,9 @@ def extract_mp3_meta(file_path):
41
  lyrics = tag.text
42
 
43
  if tag.FrameID == "APIC":
44
- with open(f"{CACHE_DIR}/cover.jpg", "wb") as img_file:
 
45
  img_file.write(tag.data)
46
- cover = f"{CACHE_DIR}/cover.jpg"
47
 
48
  return title, artist, album, lyrics, cover
49
 
@@ -57,82 +75,110 @@ def extract_flac_meta(file_path):
57
  lyrics = audio.get("LYRICS")[0]
58
  for picture in audio.pictures:
59
  if picture.type == PictureType.COVER_FRONT:
60
- with open(f"{CACHE_DIR}/cover.jpg", "wb") as img_file:
 
61
  img_file.write(picture.data)
62
- cover = f"{CACHE_DIR}/cover.jpg"
63
 
64
  break
65
 
66
  return title, artist, album, lyrics, cover
67
 
68
 
69
- # outer func
70
- def upd_meta(file_path: str, fname, title, artist, album, lyric, cover):
 
 
71
  try:
72
- file_ext = os.path.splitext(file_path)[1].lower()
73
- output_file = f"{CACHE_DIR}/{fname}{file_ext}"
74
- shutil.copyfile(file_path, output_file)
75
- insert_meta(output_file, title, artist, album, lyric, cover)
76
- return output_file
 
 
 
 
 
 
 
 
 
 
77
 
 
 
 
 
 
78
  except Exception as e:
79
- print(e)
80
- return None
 
81
 
82
 
83
  def music_meta_editor():
84
  clean_dir(CACHE_DIR)
85
  with gr.Blocks() as meta:
86
- gr.Markdown("# Music Metadata Editor")
87
  with gr.Row():
88
  with gr.Column():
89
- input_audio = gr.Audio(label="Upload music (mp3/flac)", type="filepath")
 
 
 
90
  fname = gr.Textbox(
91
- label="Edit filename (excluding suffix)",
92
- placeholder="Ensure the filename is valid",
93
  interactive=True,
94
  show_copy_button=True,
95
  )
96
  title = gr.Textbox(
97
- label="Edit title",
98
  interactive=True,
99
  show_copy_button=True,
100
  )
101
  artist = gr.Textbox(
102
- label="Edit artist",
103
  interactive=True,
104
  show_copy_button=True,
105
  )
106
  album = gr.Textbox(
107
- label="Edit album",
108
  interactive=True,
109
  show_copy_button=True,
110
  )
111
  lyrics = gr.TextArea(
112
- label="Edit lyrics",
113
  interactive=True,
114
  show_copy_button=True,
115
  )
116
  cover = gr.Image(
117
- label="Edit cover (leaving blank means deletion)",
118
  interactive=True,
119
  type="filepath",
120
  )
121
- submit = gr.Button("Submit")
122
 
123
  with gr.Column():
124
- output_audio = gr.Audio(label="Download output audio")
 
125
 
126
  input_audio.upload(
127
  fn=extract_metadata,
128
  inputs=input_audio,
129
- outputs=[title, fname, artist, album, lyrics, cover],
 
 
 
 
 
 
130
  )
131
 
132
  submit.click(
133
  fn=upd_meta,
134
  inputs=[input_audio, fname, title, artist, album, lyrics, cover],
135
- outputs=output_audio,
136
  )
137
 
138
  return meta
 
4
  from mutagen.mp3 import MP3
5
  from mutagen.flac import FLAC
6
  from mutagen.id3 import USLT, PictureType
7
+ from utils import insert_meta, clean_dir, TMP_DIR, LANG
 
8
 
9
  CACHE_DIR = f"{TMP_DIR}/meta"
10
+ ZH2EN = {
11
+ "# 音频 Meta 信息编辑器": "# Music Metadata Editor",
12
+ "上传待编辑音乐 (mp3/flac)": "Upload music (mp3/flac)",
13
+ "编辑文件名 (不含后缀)": "Edit filename (excluding suffix)",
14
+ "请确保文件名有效": "Ensure the filename is valid",
15
+ "编辑歌名": "Edit title",
16
+ "编辑作者": "Edit artist",
17
+ "编辑专辑": "Edit album",
18
+ "编辑歌词": "Edit lyrics",
19
+ "编辑封面 (留空意味着清空)": "Edit cover (leaving blank means deletion)",
20
+ "提交": "Submit",
21
+ "下载输出音频": "Download output audio",
22
+ "状态栏": "Status",
23
+ }
24
+
25
+
26
+ def _L(zh_txt: str):
27
+ return ZH2EN[zh_txt] if LANG else zh_txt
28
+
29
+
30
+ # outer func requires try except
31
  def extract_metadata(file_path: str):
32
+ status = "Success"
33
  title = fname = artist = album = lyrics = cover = None
34
  try:
35
  if not file_path:
36
+ raise ValueError("文件路径为空!")
37
 
38
  fname = os.path.splitext(os.path.basename(file_path))[0]
39
  file_ext = os.path.splitext(file_path)[1].lower()
 
43
  title, artist, album, lyrics, cover = extract_flac_meta(file_path)
44
 
45
  except Exception as e:
46
+ status = f"{e}"
47
 
48
+ return status, title, fname, artist, album, lyrics, cover
49
 
50
 
51
  def extract_mp3_meta(file_path):
 
59
  lyrics = tag.text
60
 
61
  if tag.FrameID == "APIC":
62
+ cover = f"{CACHE_DIR}/cover.jpg"
63
+ with open(cover, "wb") as img_file:
64
  img_file.write(tag.data)
 
65
 
66
  return title, artist, album, lyrics, cover
67
 
 
75
  lyrics = audio.get("LYRICS")[0]
76
  for picture in audio.pictures:
77
  if picture.type == PictureType.COVER_FRONT:
78
+ cover = f"{CACHE_DIR}/cover.jpg"
79
+ with open(cover, "wb") as img_file:
80
  img_file.write(picture.data)
 
81
 
82
  break
83
 
84
  return title, artist, album, lyrics, cover
85
 
86
 
87
+ # outer func requires try except
88
+ def upd_meta(audio_in: str, fname, title, artist, album, lyric, cover):
89
+ status = "Success"
90
+ audio_out = None
91
  try:
92
+ file_ext = os.path.splitext(audio_in)[1].lower()
93
+ audio_out = insert_meta(
94
+ audio_in,
95
+ title,
96
+ artist,
97
+ album,
98
+ lyric,
99
+ cover,
100
+ f"{CACHE_DIR}/{fname}{file_ext}",
101
+ )
102
+
103
+ except Exception as e:
104
+ status = f"{e}"
105
+
106
+ return status, audio_out
107
 
108
+
109
+ def clear_inputs():
110
+ status = None
111
+ try:
112
+ clean_dir(CACHE_DIR)
113
  except Exception as e:
114
+ status = f"{e}"
115
+
116
+ return status, None, None, None, None, None, None
117
 
118
 
119
  def music_meta_editor():
120
  clean_dir(CACHE_DIR)
121
  with gr.Blocks() as meta:
122
+ gr.Markdown(_L("# 音频 Meta 信息编辑器"))
123
  with gr.Row():
124
  with gr.Column():
125
+ input_audio = gr.Audio(
126
+ label=_L("上传待编辑音乐 (mp3/flac)"),
127
+ type="filepath",
128
+ )
129
  fname = gr.Textbox(
130
+ label=_L("编辑文件名 (不含后缀)"),
131
+ placeholder=_L("请确保文件名有效"),
132
  interactive=True,
133
  show_copy_button=True,
134
  )
135
  title = gr.Textbox(
136
+ label=_L("编辑歌名"),
137
  interactive=True,
138
  show_copy_button=True,
139
  )
140
  artist = gr.Textbox(
141
+ label=_L("编辑作者"),
142
  interactive=True,
143
  show_copy_button=True,
144
  )
145
  album = gr.Textbox(
146
+ label=_L("编辑专辑"),
147
  interactive=True,
148
  show_copy_button=True,
149
  )
150
  lyrics = gr.TextArea(
151
+ label=_L("编辑歌词"),
152
  interactive=True,
153
  show_copy_button=True,
154
  )
155
  cover = gr.Image(
156
+ label=_L("编辑封面 (留空意味着清空)"),
157
  interactive=True,
158
  type="filepath",
159
  )
160
+ submit = gr.Button(_L("提交"))
161
 
162
  with gr.Column():
163
+ status_bar = gr.Textbox(label=_L("状态栏"), show_copy_button=True)
164
+ output_audio = gr.Audio(label=_L("下载输出音频"))
165
 
166
  input_audio.upload(
167
  fn=extract_metadata,
168
  inputs=input_audio,
169
+ outputs=[status_bar, title, fname, artist, album, lyrics, cover],
170
+ )
171
+
172
+ input_audio.clear(
173
+ fn=clear_inputs,
174
+ inputs=None,
175
+ outputs=[status_bar, title, fname, artist, album, lyrics, cover],
176
  )
177
 
178
  submit.click(
179
  fn=upd_meta,
180
  inputs=[input_audio, fname, title, artist, album, lyrics, cover],
181
+ outputs=[status_bar, output_audio],
182
  )
183
 
184
  return meta
netease.py → modules/netease.py RENAMED
@@ -2,12 +2,57 @@ import re
2
  import requests
3
  import gradio as gr
4
  from tqdm import tqdm
5
- from config import API_163, TIMEOUT, TMP_DIR
6
- from utils import insert_meta, get_real_url, extract_fst_url, clean_dir, timestamp
7
-
8
-
9
- def download_file(
10
- id, quality, url, title, artist, album, lyric, cover, cache=f"{TMP_DIR}/163"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  ):
12
  clean_dir(cache)
13
  fmt = "mp3" if " MP3" in quality else "flac"
@@ -24,15 +69,14 @@ def download_file(
24
  )
25
  with open(local_file, "wb") as f:
26
  for chunk in response.iter_content(chunk_size=8192):
27
- if chunk:
28
- f.write(chunk)
29
  progress_bar.update(len(chunk))
30
 
31
- insert_meta(local_file, title, artist, album, lyric, cover)
32
- return local_file
33
 
34
  else:
35
- raise ConnectionError(f"Download Failure, status code: {response.status_code}")
36
 
37
 
38
  def parse_id(url: str):
@@ -56,24 +100,28 @@ def parse_id(url: str):
56
  return None
57
 
58
 
59
- # outer func
60
  def infer(url: str, level: str):
 
61
  if not level:
62
- level = "lossless"
63
 
64
  song = title = cover = artist = album = quality = size = lyric = None
65
  try:
66
  song_id = parse_id(url)
67
  if "playlist?" in url:
68
- raise ValueError("Please enter a song link instead of a playlist link!")
69
 
70
  if not song_id or not level:
71
- title = "Please input valid url or id and select a sound quality level!"
72
- return song, title, artist, album, lyric, cover, song_id, quality, size
73
 
74
  response = requests.get(
75
  API_163,
76
- params={"id": song_id, "type": "json", "level": level},
 
 
 
 
77
  timeout=TIMEOUT,
78
  )
79
 
@@ -82,23 +130,33 @@ def infer(url: str, level: str):
82
  url = data["url"]
83
  if extract_fst_url(url):
84
  title = data["name"]
85
- cover = data["pic"]
86
  artist = data["artist"]
87
  album = data["album"]
 
 
88
  quality = data["format"]
89
  size = data["size"]
90
- lyric = data["lyric"]
91
- song = download_file(
92
- data["id"], quality, url, title, artist, album, lyric, cover
 
 
 
 
 
 
93
  )
94
 
95
  else:
96
- raise ValueError(f"{url} or song does not exist!")
 
 
 
97
 
98
  except Exception as e:
99
- size = f"{e}"
100
 
101
- return song, title, artist, album, lyric, cover, song_id, quality, size
102
 
103
 
104
  def parser163():
@@ -106,45 +164,50 @@ def parser163():
106
  fn=infer,
107
  inputs=[
108
  gr.Textbox(
109
- label="Please input music163 song ID or URL",
110
  placeholder="https://music.163.com/#/song?id=",
111
  ),
112
  gr.Dropdown(
113
  choices=[
114
- "standard",
115
- "exhigh",
116
- "lossless",
117
- "hires",
118
- "sky",
119
- "jyeffect",
120
- "jymaster",
121
  ],
122
- value="lossless",
123
- label="Sound Quality",
124
  ),
125
  ],
126
  outputs=[
 
127
  gr.Audio(
128
- label="Audio with metadata",
129
  show_download_button=True,
130
  show_share_button=False,
131
  ),
132
- gr.Textbox(label="Title", show_copy_button=True),
133
- gr.Textbox(label="Artist", show_copy_button=True),
134
- gr.Textbox(label="Album", show_copy_button=True),
135
- gr.TextArea(label="Lyrics", show_copy_button=True),
136
- gr.Image(label="Cover", show_share_button=False),
137
- gr.Textbox(label="Song ID", show_copy_button=True),
138
- gr.Textbox(label="Quality", show_copy_button=True),
139
- gr.Textbox(label="Size", show_copy_button=True),
140
  ],
141
- title="Parse Music163 Songs without Loss",
142
  flagging_mode="never",
143
  examples=[
144
- ["36990266", "standard"],
145
- ["https://y.music.163.com/m/song?id=36990266", "standard"],
146
- ["https://music.163.com/#/song?id=36990266", "exhigh"],
147
- ["http://163cn.tv/CmHfO44", "lossless"],
 
 
 
 
148
  ],
149
  cache_examples=False,
150
  )
 
2
  import requests
3
  import gradio as gr
4
  from tqdm import tqdm
5
+ from utils import (
6
+ extract_fst_url,
7
+ get_real_url,
8
+ insert_meta,
9
+ clean_dir,
10
+ timestamp,
11
+ API_163,
12
+ TIMEOUT,
13
+ TMP_DIR,
14
+ LANG,
15
+ )
16
+
17
+
18
+ ZH2EN = {
19
+ "请输入网易云音乐 ID 或 URL 链接": "Please input music163 song ID or URL",
20
+ "选择音质": "Sound quality",
21
+ "标准音质": "standard",
22
+ "极高音质": "exhigh",
23
+ "无损音质": "lossless",
24
+ "Hires音质": "hires",
25
+ "沉浸环绕声": "sky",
26
+ "高清环绕声": "jyeffect",
27
+ "超清母带": "jymaster",
28
+ "含元信息音频下载": "Audio with metadata",
29
+ "歌名": "Title",
30
+ "作者": "Artist",
31
+ "专辑": "Album",
32
+ "歌词": "Lyrics",
33
+ "歌曲图片": "Cover",
34
+ "歌曲 ID": "Song ID",
35
+ "音质": "Quality",
36
+ "大小": "Size",
37
+ "网易云音乐无损解析": "Parse Music163 Songs without Loss",
38
+ "状态栏": "Status",
39
+ }
40
+
41
+
42
+ def _L(zh_txt: str):
43
+ return ZH2EN[zh_txt] if LANG else zh_txt
44
+
45
+
46
+ def download_audio(
47
+ id: int,
48
+ quality: str,
49
+ url: str,
50
+ title: str,
51
+ artist: str,
52
+ album: str,
53
+ lyric: str,
54
+ cover: str,
55
+ cache=f"{TMP_DIR}/163",
56
  ):
57
  clean_dir(cache)
58
  fmt = "mp3" if " MP3" in quality else "flac"
 
69
  )
70
  with open(local_file, "wb") as f:
71
  for chunk in response.iter_content(chunk_size=8192):
72
+ if chunk: # 确保 chunk 不为空
73
+ f.write(chunk) # 更新进度条
74
  progress_bar.update(len(chunk))
75
 
76
+ return insert_meta(local_file, title, artist, album, lyric, cover)
 
77
 
78
  else:
79
+ raise ConnectionError(f"下载失败,状态码:{response.status_code}")
80
 
81
 
82
  def parse_id(url: str):
 
100
  return None
101
 
102
 
103
+ # outer func requires try except
104
  def infer(url: str, level: str):
105
+ status = "Success"
106
  if not level:
107
+ level = _L("无损音质")
108
 
109
  song = title = cover = artist = album = quality = size = lyric = None
110
  try:
111
  song_id = parse_id(url)
112
  if "playlist?" in url:
113
+ raise ValueError("请输入歌曲链接而非歌单链接!")
114
 
115
  if not song_id or not level:
116
+ raise ValueError("请输入有效的网址或 ID, 并选择一个声音质量水平!")
 
117
 
118
  response = requests.get(
119
  API_163,
120
+ params={
121
+ "id": song_id,
122
+ "type": "json",
123
+ "level": level if LANG else ZH2EN[level],
124
+ },
125
  timeout=TIMEOUT,
126
  )
127
 
 
130
  url = data["url"]
131
  if extract_fst_url(url):
132
  title = data["name"]
 
133
  artist = data["artist"]
134
  album = data["album"]
135
+ lyric = data["lyric"]
136
+ cover = data["pic"]
137
  quality = data["format"]
138
  size = data["size"]
139
+ song = download_audio(
140
+ data["id"],
141
+ quality,
142
+ url,
143
+ title,
144
+ artist,
145
+ album,
146
+ lyric,
147
+ cover,
148
  )
149
 
150
  else:
151
+ raise ValueError(f"{url}或歌曲不存在")
152
+
153
+ else:
154
+ raise ConnectionError(f"HTTP {response.status_code}")
155
 
156
  except Exception as e:
157
+ status = f"{e}"
158
 
159
+ return status, song, title, artist, album, lyric, cover, song_id, quality, size
160
 
161
 
162
  def parser163():
 
164
  fn=infer,
165
  inputs=[
166
  gr.Textbox(
167
+ label=_L("请输入网易云音乐 ID URL 链接"),
168
  placeholder="https://music.163.com/#/song?id=",
169
  ),
170
  gr.Dropdown(
171
  choices=[
172
+ _L("标准音质"),
173
+ _L("极高音质"),
174
+ _L("无损音质"),
175
+ _L("Hires音质"),
176
+ _L("沉浸环绕声"),
177
+ _L("高清环绕声"),
178
+ _L("超清母带"),
179
  ],
180
+ value=_L("无损音质"),
181
+ label=_L("选择音质"),
182
  ),
183
  ],
184
  outputs=[
185
+ gr.Textbox(label=_L("状态栏"), show_copy_button=True),
186
  gr.Audio(
187
+ label=_L("含元信息音频下载"),
188
  show_download_button=True,
189
  show_share_button=False,
190
  ),
191
+ gr.Textbox(label=_L("歌名"), show_copy_button=True),
192
+ gr.Textbox(label=_L("作者"), show_copy_button=True),
193
+ gr.Textbox(label=_L("专辑"), show_copy_button=True),
194
+ gr.TextArea(label=_L("歌词"), show_copy_button=True),
195
+ gr.Image(label=_L("歌曲图片"), show_share_button=False),
196
+ gr.Textbox(label=_L("歌曲 ID"), show_copy_button=True),
197
+ gr.Textbox(label=_L("音质"), show_copy_button=True),
198
+ gr.Textbox(label=_L("大小"), show_copy_button=True),
199
  ],
200
+ title=_L("网易云音乐无损解析"),
201
  flagging_mode="never",
202
  examples=[
203
+ ["36990266", _L("标准音质")],
204
+ ["http://163cn.tv/CmHfO44", _L("标准音质")],
205
+ ["https://music.163.com/#/song?id=36990266", _L("标准音质")],
206
+ ["https://y.music.163.com/m/song?id=36990266", _L("极高音质")],
207
+ [
208
+ "分享Alan Walker的单曲《Faded》http://163cn.tv/CmHfO44 (@网易云音乐)",
209
+ _L("无损音质"),
210
+ ],
211
  ],
212
  cache_examples=False,
213
  )
qq.py → modules/qq.py RENAMED
@@ -1,35 +1,36 @@
1
  import re
2
  import requests
3
  import gradio as gr
4
- from tqdm import tqdm
5
- from utils import insert_meta, extract_fst_url, get_real_url, clean_dir, timestamp
6
- from config import API_QQ_2, API_QQ_1, KEY_QQ_1, TIMEOUT, TMP_DIR
7
-
8
-
9
- def download_file(mid, url, title, artist, album, lyric, cover, cache=f"{TMP_DIR}/qq"):
10
- clean_dir(cache)
11
- local_file = f"{cache}/{mid}.mp3"
12
- response = requests.get(url, stream=True)
13
- if response.status_code == 200:
14
- total_size = int(response.headers.get("Content-Length", 0)) + 1
15
- time_stamp = timestamp()
16
- progress_bar = tqdm(
17
- total=total_size,
18
- unit="B",
19
- unit_scale=True,
20
- desc=f"[{time_stamp}] {local_file}",
21
- )
22
- with open(local_file, "wb") as f:
23
- for chunk in response.iter_content(chunk_size=8192):
24
- if chunk:
25
- f.write(chunk)
26
- progress_bar.update(len(chunk))
27
-
28
- insert_meta(local_file, title, artist, album, lyric, cover)
29
- return local_file
30
-
31
- else:
32
- raise ConnectionError(f"Download Failure, status code: {response.status_code}")
 
33
 
34
 
35
  def parse_id(url: str):
@@ -55,36 +56,37 @@ def parse_id(url: str):
55
 
56
  def parse_size(size_in_bytes):
57
  units = ["B", "KB", "MB", "GB"]
58
- unit_index = 0
59
- if size_in_bytes >= 1024**3:
 
60
  unit_index = 3
61
  size = size_in_bytes / (1024**3)
62
 
63
- elif size_in_bytes >= 1024**2:
64
  unit_index = 2
65
  size = size_in_bytes / (1024**2)
66
 
67
- elif size_in_bytes >= 1024:
68
  unit_index = 1
69
  size = size_in_bytes / 1024
70
 
71
  else:
72
  size = size_in_bytes
73
-
74
  if unit_index == 0:
75
  return f"{size}{units[unit_index]}"
76
  else:
77
  return f"{size:.2f}{units[unit_index]}"
78
 
79
 
80
- # outer func
81
  def infer(url: str):
 
82
  song = title = artist = album = lyric = cover = song_id = quality = size = None
83
  try:
84
  song_mid = parse_id(url)
85
  if not song_mid:
86
- title = "Please enter a valid URL or mid!"
87
- return song, title, artist, album, lyric, cover, song_id, quality, size
88
 
89
  # title, artist, album, cover
90
  response = requests.get(
@@ -112,21 +114,27 @@ def infer(url: str):
112
  response = requests.get(API_QQ_2, params={"mid": song_mid}, timeout=TIMEOUT)
113
  if response.status_code == 200 and response.json()["code"] == 200:
114
  data = response.json()["data"]
115
- song = download_file(
116
- song_mid, data["urls"][0]["url"], title, artist, album, lyric, cover
117
- )
118
  song_id = data["id"]
119
  quality = data["urls"][0]["type"]
120
  size = parse_size(int(data["urls"][0]["size"]))
 
 
 
 
 
 
 
 
 
 
121
 
122
  else:
123
- cover = None
124
- raise ConnectionError(response.json()["msg"] + ", mid or URL may be wrong!")
125
 
126
  except Exception as e:
127
- size = f"{e}"
128
 
129
- return song, title, artist, album, lyric, cover, song_id, quality, size
130
 
131
 
132
  def qmusic_parser():
@@ -134,31 +142,32 @@ def qmusic_parser():
134
  fn=infer,
135
  inputs=[
136
  gr.Textbox(
137
- label="Please enter the mid or URL of QQ music song",
138
  placeholder="https://y.qq.com/n/ryqq/songDetail/*",
139
- ),
140
  ],
141
  outputs=[
 
142
  gr.Audio(
143
- label="Audio with metadata",
144
  show_download_button=True,
145
  show_share_button=False,
146
  ),
147
- gr.Textbox(label="Title", show_copy_button=True),
148
- gr.Textbox(label="Artist", show_copy_button=True),
149
- gr.Textbox(label="Album", show_copy_button=True),
150
- gr.TextArea(label="Lyrics", show_copy_button=True),
151
- gr.Image(label="Cover", show_share_button=False),
152
- gr.Textbox(label="Song ID", show_copy_button=True),
153
- gr.Textbox(label="Quality", show_copy_button=True),
154
- gr.Textbox(label="Size", show_copy_button=True),
155
  ],
156
- title="QQ Music Parser",
157
  flagging_mode="never",
158
  examples=[
159
  "000ZjUoy0DeHRO",
160
  "https://y.qq.com/n/ryqq/songDetail/000ZjUoy0DeHRO",
161
- "https://c6.y.qq.com/base/fcgi-bin/u?__=fY3GHnkxtiHJ",
162
  ],
163
  cache_examples=False,
164
  )
 
1
  import re
2
  import requests
3
  import gradio as gr
4
+ from utils import (
5
+ extract_fst_url,
6
+ get_real_url,
7
+ download_file,
8
+ API_QQ_2,
9
+ API_QQ_1,
10
+ KEY_QQ_1,
11
+ TIMEOUT,
12
+ TMP_DIR,
13
+ LANG,
14
+ )
15
+
16
+ ZH2EN = {
17
+ "请输入QQ音乐 mid 或 URL 链接": "Please enter the mid or URL of QQ music song",
18
+ "含元信息音频下载": "Audio with metadata",
19
+ "歌名": "Title",
20
+ "作者": "Artist",
21
+ "专辑": "Album",
22
+ "歌词": "Lyrics",
23
+ "歌曲图片": "Cover",
24
+ "歌曲 ID": "Song ID",
25
+ "音质": "Quality",
26
+ "大小": "Size",
27
+ "QQ音乐直链解析": "QQ Music Parser",
28
+ "状态栏": "Status",
29
+ }
30
+
31
+
32
+ def _L(zh_txt: str):
33
+ return ZH2EN[zh_txt] if LANG else zh_txt
34
 
35
 
36
  def parse_id(url: str):
 
56
 
57
  def parse_size(size_in_bytes):
58
  units = ["B", "KB", "MB", "GB"]
59
+ unit_index = 0 # 初始单位为字节
60
+ # 根据文件大小选择合适的单位
61
+ if size_in_bytes >= 1024**3: # 大于等于 1 GB
62
  unit_index = 3
63
  size = size_in_bytes / (1024**3)
64
 
65
+ elif size_in_bytes >= 1024**2: # 大于等于 1 MB
66
  unit_index = 2
67
  size = size_in_bytes / (1024**2)
68
 
69
+ elif size_in_bytes >= 1024: # 大于等于 1 KB
70
  unit_index = 1
71
  size = size_in_bytes / 1024
72
 
73
  else:
74
  size = size_in_bytes
75
+ # 格式化输出,保留两位小数
76
  if unit_index == 0:
77
  return f"{size}{units[unit_index]}"
78
  else:
79
  return f"{size:.2f}{units[unit_index]}"
80
 
81
 
82
+ # outer func requires try except
83
  def infer(url: str):
84
+ status = "Success"
85
  song = title = artist = album = lyric = cover = song_id = quality = size = None
86
  try:
87
  song_mid = parse_id(url)
88
  if not song_mid:
89
+ raise ValueError("请输入有效的网址或mid!")
 
90
 
91
  # title, artist, album, cover
92
  response = requests.get(
 
114
  response = requests.get(API_QQ_2, params={"mid": song_mid}, timeout=TIMEOUT)
115
  if response.status_code == 200 and response.json()["code"] == 200:
116
  data = response.json()["data"]
 
 
 
117
  song_id = data["id"]
118
  quality = data["urls"][0]["type"]
119
  size = parse_size(int(data["urls"][0]["size"]))
120
+ song = download_file(
121
+ song_mid,
122
+ data["urls"][0]["url"],
123
+ title,
124
+ artist,
125
+ album,
126
+ lyric,
127
+ cover,
128
+ f"{TMP_DIR}/qq",
129
+ )
130
 
131
  else:
132
+ raise ConnectionError(response.json()["msg"] + ", 可能是歌曲mid或URL有误")
 
133
 
134
  except Exception as e:
135
+ status = f"{e}"
136
 
137
+ return status, song, title, artist, album, lyric, cover, song_id, quality, size
138
 
139
 
140
  def qmusic_parser():
 
142
  fn=infer,
143
  inputs=[
144
  gr.Textbox(
145
+ label=_L("请输入QQ音乐 mid URL 链接"),
146
  placeholder="https://y.qq.com/n/ryqq/songDetail/*",
147
+ )
148
  ],
149
  outputs=[
150
+ gr.Textbox(label=_L("状态栏"), show_copy_button=True),
151
  gr.Audio(
152
+ label=_L("含元信息音频下载"),
153
  show_download_button=True,
154
  show_share_button=False,
155
  ),
156
+ gr.Textbox(label=_L("歌名"), show_copy_button=True),
157
+ gr.Textbox(label=_L("作者"), show_copy_button=True),
158
+ gr.Textbox(label=_L("专辑"), show_copy_button=True),
159
+ gr.TextArea(label=_L("歌词"), show_copy_button=True),
160
+ gr.Image(label=_L("歌曲图片"), show_share_button=False),
161
+ gr.Textbox(label=_L("歌曲 ID"), show_copy_button=True),
162
+ gr.Textbox(label=_L("音质"), show_copy_button=True),
163
+ gr.Textbox(label=_L("大小"), show_copy_button=True),
164
  ],
165
+ title=_L("QQ音乐直链解析"),
166
  flagging_mode="never",
167
  examples=[
168
  "000ZjUoy0DeHRO",
169
  "https://y.qq.com/n/ryqq/songDetail/000ZjUoy0DeHRO",
170
+ "DJMOUN《一氧化碳 (监狱兔概念神) (DJ版)》 https://c6.y.qq.com/base/fcgi-bin/u?__=fY3GHnkxtiHJ @QQ音乐",
171
  ],
172
  cache_examples=False,
173
  )
requirements.txt CHANGED
@@ -1,4 +1,2 @@
1
- gradio
2
  mutagen
3
- tzlocal
4
- requests
 
 
1
  mutagen
2
+ tzlocal
 
utils.py CHANGED
@@ -2,13 +2,29 @@ import os
2
  import re
3
  import shutil
4
  import requests
 
5
  from datetime import datetime
6
  from zoneinfo import ZoneInfo
7
  from tzlocal import get_localzone
8
  from mutagen.mp3 import MP3
9
  from mutagen.flac import FLAC, Picture
10
  from mutagen.id3 import TIT2, TPE1, TALB, USLT, APIC
11
- from config import HEADER, TIMEOUT
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
 
14
  def timestamp(naive_time: datetime = None, target_tz=ZoneInfo("Asia/Shanghai")):
@@ -20,7 +36,12 @@ def timestamp(naive_time: datetime = None, target_tz=ZoneInfo("Asia/Shanghai")):
20
  return aware_local.astimezone(target_tz).strftime("%Y-%m-%d %H:%M:%S")
21
 
22
 
23
- def insert_meta(file_path: str, title, artist, album, lyric, cover: str):
 
 
 
 
 
24
  if cover:
25
  if cover.startswith("http"):
26
  cover = requests.get(cover).content
@@ -28,26 +49,8 @@ def insert_meta(file_path: str, title, artist, album, lyric, cover: str):
28
  with open(cover, "rb") as file:
29
  cover = file.read()
30
 
31
- if file_path.endswith(".flac"):
32
- audio = FLAC(file_path)
33
- audio.tags["TITLE"] = title
34
- audio.tags["ARTIST"] = artist
35
- audio.tags["ALBUM"] = album
36
- audio.tags["LYRICS"] = lyric
37
- if cover:
38
- picture = Picture()
39
- picture.type = 3 # cover
40
- picture.mime = "image/jpeg"
41
- picture.data = cover
42
- audio.add_picture(picture)
43
-
44
- else:
45
- audio.clear_pictures()
46
-
47
- audio.save()
48
-
49
- elif file_path.endswith(".mp3"):
50
- audio = MP3(file_path)
51
  if audio.tags:
52
  for tag_id in list(audio.tags.keys()):
53
  del audio.tags[tag_id]
@@ -62,9 +65,9 @@ def insert_meta(file_path: str, title, artist, album, lyric, cover: str):
62
  if cover:
63
  audio.tags.add(
64
  APIC(
65
- encoding=3, # UTF-8 encoding
66
- mime="image/jpeg", # Picture MIME type
67
- type=3, # cover
68
  desc="Cover",
69
  data=cover,
70
  )
@@ -72,8 +75,26 @@ def insert_meta(file_path: str, title, artist, album, lyric, cover: str):
72
 
73
  audio.save()
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- def extract_fst_url(text: str):
 
 
 
 
 
77
  match = re.search(r'(https?://[^\s"]+)', text)
78
  if match:
79
  return match.group(1)
@@ -111,3 +132,38 @@ def rm_dir(dirpath: str):
111
  def clean_dir(dirpath: str):
112
  rm_dir(dirpath)
113
  os.makedirs(dirpath)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import re
3
  import shutil
4
  import requests
5
+ from tqdm import tqdm
6
  from datetime import datetime
7
  from zoneinfo import ZoneInfo
8
  from tzlocal import get_localzone
9
  from mutagen.mp3 import MP3
10
  from mutagen.flac import FLAC, Picture
11
  from mutagen.id3 import TIT2, TPE1, TALB, USLT, APIC
12
+
13
+ LANG = os.getenv("language")
14
+ API_163 = os.getenv("api_music163")
15
+ API_KUWO = os.getenv("api_kuwo")
16
+ API_QQ_2 = os.getenv("api_qmusic_2")
17
+ API_QQ_1 = os.getenv("api_qmusic_1")
18
+ KEY_QQ_1 = os.getenv("apikey_qmusic_1")
19
+ if not (API_163 and API_KUWO and API_QQ_2 and API_QQ_1 and KEY_QQ_1):
20
+ print("请检查环境变量!")
21
+ exit()
22
+
23
+ TIMEOUT = None
24
+ TMP_DIR = "./__pycache__"
25
+ HEADER = {
26
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36"
27
+ }
28
 
29
 
30
  def timestamp(naive_time: datetime = None, target_tz=ZoneInfo("Asia/Shanghai")):
 
36
  return aware_local.astimezone(target_tz).strftime("%Y-%m-%d %H:%M:%S")
37
 
38
 
39
+ def insert_meta(audio_in: str, title, artist, album, lyric, cover: str, audio_out=""):
40
+ if audio_out:
41
+ shutil.copyfile(audio_in, audio_out)
42
+ else:
43
+ audio_out = audio_in
44
+
45
  if cover:
46
  if cover.startswith("http"):
47
  cover = requests.get(cover).content
 
49
  with open(cover, "rb") as file:
50
  cover = file.read()
51
 
52
+ if audio_out.endswith(".mp3"):
53
+ audio = MP3(audio_out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  if audio.tags:
55
  for tag_id in list(audio.tags.keys()):
56
  del audio.tags[tag_id]
 
65
  if cover:
66
  audio.tags.add(
67
  APIC(
68
+ encoding=3, # UTF-8 编码
69
+ mime="image/jpeg", # 图片的 MIME 类型
70
+ type=3, # 封面图片
71
  desc="Cover",
72
  data=cover,
73
  )
 
75
 
76
  audio.save()
77
 
78
+ elif audio_out.endswith(".flac"):
79
+ audio = FLAC(audio_out)
80
+ audio.tags["TITLE"] = title
81
+ audio.tags["ARTIST"] = artist
82
+ audio.tags["ALBUM"] = album
83
+ audio.tags["LYRICS"] = lyric
84
+ audio.clear_pictures()
85
+ if cover:
86
+ picture = Picture()
87
+ picture.type = 3 # 封面图片
88
+ picture.mime = "image/jpeg"
89
+ picture.data = cover
90
+ audio.add_picture(picture)
91
 
92
+ audio.save()
93
+
94
+ return audio_out
95
+
96
+
97
+ def extract_fst_url(text):
98
  match = re.search(r'(https?://[^\s"]+)', text)
99
  if match:
100
  return match.group(1)
 
132
  def clean_dir(dirpath: str):
133
  rm_dir(dirpath)
134
  os.makedirs(dirpath)
135
+
136
+
137
+ def download_file(
138
+ id: int,
139
+ url: str,
140
+ title: str,
141
+ artist: str,
142
+ album: str,
143
+ lyric: str,
144
+ cover: str,
145
+ cache: str,
146
+ ):
147
+ clean_dir(cache)
148
+ fmt = url.split(".")[-1]
149
+ local_file = f"{cache}/{id}.{fmt}"
150
+ response = requests.get(url, stream=True)
151
+ if response.status_code == 200:
152
+ total_size = int(response.headers.get("Content-Length", 0)) + 1
153
+ time_stamp = timestamp()
154
+ progress_bar = tqdm(
155
+ total=total_size,
156
+ unit="B",
157
+ unit_scale=True,
158
+ desc=f"[{time_stamp}] {local_file}",
159
+ )
160
+ with open(local_file, "wb") as f:
161
+ for chunk in response.iter_content(chunk_size=8192):
162
+ if chunk: # 确保 chunk 不为空
163
+ f.write(chunk) # 更新进度条
164
+ progress_bar.update(len(chunk))
165
+
166
+ return insert_meta(local_file, title, artist, album, lyric, cover)
167
+
168
+ else:
169
+ raise ConnectionError(f"下载失败,状态码:{response.status_code}")