Spaces:
Running
Running
import os | |
import re | |
import shutil | |
import requests | |
import gradio as gr | |
from mutagen.mp3 import MP3 | |
from mutagen.flac import FLAC, Picture | |
from mutagen.id3 import TIT2, TPE1, TALB, USLT, APIC | |
API2 = os.getenv("api_qmusic_2") | |
API1 = os.getenv("api_qmusic_1") | |
KEY = os.getenv("apikey_qmusic_1") | |
def insert_metadata(file_path: str, title, artist, album, lyric, cover): | |
print(f"The file was successfully downloaded and saved to {file_path}") | |
if file_path.endswith(".mp3"): | |
audio = MP3(file_path) | |
try: | |
audio.add_tags() | |
except Exception as e: | |
print(e) | |
audio.tags.add(TIT2(encoding=3, text=title)) | |
audio.tags.add(TPE1(encoding=3, text=artist)) | |
audio.tags.add(TALB(encoding=3, text=album)) | |
audio.tags.add(USLT(encoding=3, desc="Lyrics", text=lyric)) | |
audio.tags.add( | |
APIC( | |
encoding=3, # UTF-8 编码 | |
mime="image/jpeg", # 图片的 MIME 类型 | |
type=3, # 封面图片 | |
desc="Cover", | |
data=requests.get(cover).content, | |
) | |
) | |
audio.save() | |
elif file_path.endswith(".flac"): | |
audio = FLAC(file_path) | |
audio.tags["TITLE"] = title | |
audio.tags["ARTIST"] = artist | |
audio.tags["ALBUM"] = album | |
audio.tags["LYRICS"] = lyric | |
picture = Picture() | |
picture.type = 3 # 封面图片 | |
picture.mime = "image/jpeg" | |
picture.data = requests.get(cover).content | |
audio.add_picture(picture) | |
audio.save() | |
def download_file( | |
mid: str, | |
url: str, | |
title, | |
artist, | |
album, | |
lyric, | |
cover, | |
cache="./__pycache__", | |
): | |
if os.path.exists(cache): | |
shutil.rmtree(cache) | |
os.makedirs(cache) | |
local_filename = f"{cache}/{mid}.mp3" | |
response = requests.get(url, stream=True) | |
if response.status_code == 200: | |
with open(local_filename, "wb") as file: | |
for chunk in response.iter_content(chunk_size=8192): | |
file.write(chunk) | |
insert_metadata(local_filename, title, artist, album, lyric, cover) | |
return local_filename | |
else: | |
print(f"Download Failure, status code: {response.status_code}") | |
return url | |
def extract_fst_url(text): | |
url_pattern = r'(https?://[^\s"]+)' | |
match = re.search(url_pattern, text) | |
if match: | |
return match.group(1) | |
else: | |
return None | |
def get_real_url(short_url): | |
return requests.get( | |
short_url, | |
headers={ | |
"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" | |
}, | |
allow_redirects=True, | |
timeout=10, | |
).url | |
def parse_id(url: str): | |
if not url: | |
return None | |
if re.fullmatch(r"^[A-Za-z0-9]{14}$", url): | |
return str(url) | |
url = extract_fst_url(url) | |
if not url: | |
return None | |
if url.startswith("https://c6.y.qq.com/base/fcgi-bin/"): | |
return get_real_url(url).split("songmid=")[1].split("&")[0] | |
match = re.search(r"songDetail/([^?]+)", url) | |
if match: | |
return str(match.group(1)) | |
else: | |
return None | |
def parse_size(size_in_bytes): | |
units = ["B", "KB", "MB", "GB"] | |
unit_index = 0 | |
if size_in_bytes >= 1024**3: | |
unit_index = 3 | |
size = size_in_bytes / (1024**3) | |
elif size_in_bytes >= 1024**2: | |
unit_index = 2 | |
size = size_in_bytes / (1024**2) | |
elif size_in_bytes >= 1024: | |
unit_index = 1 | |
size = size_in_bytes / 1024 | |
else: | |
size = size_in_bytes | |
if unit_index == 0: | |
return f"{size}{units[unit_index]}" | |
else: | |
return f"{size:.2f}{units[unit_index]}" | |
def infer(url: str): | |
song = title = artist = album = lyric = cover = song_id = quality = size = None | |
try: | |
song_mid = parse_id(url) | |
if not song_mid: | |
title = "Please enter a valid URL or mid!" | |
return song, title, artist, album, lyric, cover, song_id, quality, size | |
# title, artist, album, cover | |
response = requests.get( | |
API1, | |
params={ | |
"key": KEY, | |
"type": "song", | |
"mid": song_mid, | |
}, | |
) | |
if response.status_code == 200 and response.json()["code"] == 200: | |
data = response.json()["data"] | |
title = data["title"] | |
artist = data["author"] | |
album = data["album_title"] | |
cover = str(data["pic"]).replace("300x300", "800x800").split("?max_age=")[0] | |
# lyrics | |
response = requests.get( | |
API1, | |
params={ | |
"key": KEY, | |
"type": "lrc", | |
"mid": song_mid, | |
"format": "json", | |
}, | |
) | |
if response.status_code == 200 and response.json()["code"] == 200: | |
lyric = response.json()["data"] | |
# url | |
response = requests.get(API2, params={"mid": song_mid}) | |
if response.status_code == 200 and response.json()["code"] == 200: | |
data = response.json()["data"] | |
song = download_file( | |
song_mid, | |
data["urls"][0]["url"], | |
title, | |
artist, | |
album, | |
lyric, | |
cover, | |
) | |
song_id = data["id"] | |
quality = data["urls"][0]["type"] | |
size = parse_size(int(data["urls"][0]["size"])) | |
else: | |
raise Exception(response.json()["msg"]) | |
except Exception as e: | |
size = f"{e}" | |
return song, title, artist, album, lyric, cover, song_id, quality, size | |
if __name__ == "__main__": | |
gr.Interface( | |
fn=infer, | |
inputs=[ | |
gr.Textbox( | |
label="Please enter the mid or URL of QQ music song", | |
placeholder="https://y.qq.com/n/ryqq/songDetail/*", | |
), | |
], | |
outputs=[ | |
gr.Audio(label="Audio", show_download_button=True, show_share_button=False), | |
gr.Textbox(label="Title", show_copy_button=True), | |
gr.Textbox(label="Artist", show_copy_button=True), | |
gr.Textbox(label="Album", show_copy_button=True), | |
gr.TextArea(label="Lyrics", show_copy_button=True), | |
gr.Image(label="Cover", show_share_button=False), | |
gr.Textbox(label="Song ID", show_copy_button=True), | |
gr.Textbox(label="Quality", show_copy_button=True), | |
gr.Textbox(label="Size", show_copy_button=True), | |
], | |
title="QQ Music Parser", | |
description="This site does not provide any audio storage services, only provide the most basic parsing services", | |
flagging_mode="never", | |
examples=[ | |
"000ZjUoy0DeHRO", | |
"https://y.qq.com/n/ryqq/songDetail/000ZjUoy0DeHRO", | |
"https://c6.y.qq.com/base/fcgi-bin/u?__=fY3GHnkxtiHJ", | |
], | |
cache_examples=False, | |
).launch() | |