Spaces:
Running
Running
admin
commited on
Commit
·
c73c29a
1
Parent(s):
215960c
sync ms
Browse files- app.py +126 -77
- requirements.txt +2 -1
app.py
CHANGED
@@ -1,20 +1,51 @@
|
|
1 |
import os
|
2 |
import re
|
3 |
import json
|
4 |
-
import uuid
|
5 |
import torch
|
6 |
import shutil
|
7 |
import requests
|
|
|
|
|
8 |
import gradio as gr
|
9 |
from piano_transcription_inference import PianoTranscription, load_audio, sample_rate
|
10 |
-
from huggingface_hub import snapshot_download
|
11 |
from urllib.parse import urlparse
|
12 |
from convert import midi2xml, xml2abc, xml2mxl, xml2jpg
|
13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
WEIGHTS_PATH = (
|
15 |
-
snapshot_download(
|
16 |
-
|
17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
|
19 |
|
20 |
def clean_cache(cache_dir):
|
@@ -34,15 +65,18 @@ def download_audio(url: str, save_path: str):
|
|
34 |
|
35 |
def is_url(s: str):
|
36 |
try:
|
|
|
37 |
result = urlparse(s)
|
|
|
38 |
return all([result.scheme, result.netloc])
|
39 |
|
40 |
except:
|
|
|
41 |
return False
|
42 |
|
43 |
|
44 |
def audio2midi(audio_path: str, cache_dir: str):
|
45 |
-
audio, _ = load_audio(audio_path, sr=sample_rate)
|
46 |
transcriptor = PianoTranscription(
|
47 |
device="cuda" if torch.cuda.is_available() else "cpu",
|
48 |
checkpoint_path=WEIGHTS_PATH,
|
@@ -52,25 +86,11 @@ def audio2midi(audio_path: str, cache_dir: str):
|
|
52 |
return midi_path, os.path.basename(audio_path).split(".")[-2].capitalize()
|
53 |
|
54 |
|
55 |
-
def
|
56 |
-
clean_cache(cache_dir)
|
57 |
-
try:
|
58 |
-
print(audio_path)
|
59 |
-
midi, title = audio2midi(audio_path, cache_dir)
|
60 |
-
xml = midi2xml(midi, title)
|
61 |
-
abc = xml2abc(xml)
|
62 |
-
mxl = xml2mxl(xml)
|
63 |
-
pdf, jpg = xml2jpg(xml)
|
64 |
-
return midi, pdf, xml, mxl, abc, jpg
|
65 |
-
|
66 |
-
except Exception as e:
|
67 |
-
return None, None, None, None, f"{e}", None
|
68 |
-
|
69 |
-
|
70 |
-
def get_1st_int(input_string: str):
|
71 |
match = re.search(r"\d+", input_string)
|
72 |
if match:
|
73 |
return str(int(match.group()))
|
|
|
74 |
else:
|
75 |
return ""
|
76 |
|
@@ -79,9 +99,11 @@ def music163_song_info(id: str):
|
|
79 |
detail_api = "https://music.163.com/api/v3/song/detail"
|
80 |
parm_dict = {"id": id, "c": str([{"id": id}]), "csrf_token": ""}
|
81 |
free = False
|
82 |
-
song_name = "
|
83 |
response = requests.get(detail_api, params=parm_dict)
|
|
|
84 |
if response.status_code == 200:
|
|
|
85 |
data = json.loads(response.text)
|
86 |
if data and "songs" in data and data["songs"]:
|
87 |
fee = int(data["songs"][0]["fee"])
|
@@ -89,31 +111,48 @@ def music163_song_info(id: str):
|
|
89 |
song_name = str(data["songs"][0]["name"])
|
90 |
|
91 |
else:
|
92 |
-
song_name = "
|
93 |
|
94 |
else:
|
95 |
-
raise ConnectionError(f"
|
96 |
|
97 |
return song_name, free
|
98 |
|
99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
def url_infer(song: str, cache_dir="./__pycache__/mode2"):
|
101 |
song_name = ""
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
try:
|
|
|
106 |
if (is_url(song) and "163" in song and "?id=" in song) or song.isdigit():
|
107 |
-
song_id =
|
108 |
-
|
109 |
song_name, free = music163_song_info(song_id)
|
110 |
if not free:
|
111 |
-
raise AttributeError("
|
112 |
|
113 |
-
|
114 |
-
download_audio(song_url, audio_path)
|
115 |
|
116 |
-
midi, title = audio2midi(
|
117 |
if song_name:
|
118 |
title = song_name
|
119 |
|
@@ -121,54 +160,64 @@ def url_infer(song: str, cache_dir="./__pycache__/mode2"):
|
|
121 |
abc = xml2abc(xml)
|
122 |
mxl = xml2mxl(xml)
|
123 |
pdf, jpg = xml2jpg(xml)
|
124 |
-
|
|
|
125 |
|
126 |
except Exception as e:
|
127 |
-
|
|
|
|
|
128 |
|
129 |
|
130 |
if __name__ == "__main__":
|
131 |
with gr.Blocks() as iface:
|
132 |
-
gr.Markdown("#
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
173 |
|
174 |
iface.launch()
|
|
|
1 |
import os
|
2 |
import re
|
3 |
import json
|
|
|
4 |
import torch
|
5 |
import shutil
|
6 |
import requests
|
7 |
+
import modelscope
|
8 |
+
import huggingface_hub
|
9 |
import gradio as gr
|
10 |
from piano_transcription_inference import PianoTranscription, load_audio, sample_rate
|
|
|
11 |
from urllib.parse import urlparse
|
12 |
from convert import midi2xml, xml2abc, xml2mxl, xml2jpg
|
13 |
|
14 |
+
EN_US = os.getenv("LANG") != "zh_CN.UTF-8"
|
15 |
+
|
16 |
+
ZH2EN = {
|
17 |
+
"上传模式": "Uploading Mode",
|
18 |
+
"上传音频": "Upload an audio",
|
19 |
+
"下载 MIDI": "Download MIDI",
|
20 |
+
"下载 PDF 乐谱": "Download PDF score",
|
21 |
+
"下载 MusicXML": "Download MusicXML",
|
22 |
+
"下载 MXL": "Download MXL",
|
23 |
+
"ABC 记谱": "ABC notation",
|
24 |
+
"五线谱": "Staff",
|
25 |
+
"状态栏": "Status",
|
26 |
+
"请上传音频 100% 后再点提交": "Please make sure the audio is completely uploaded before clicking Submit",
|
27 |
+
"直链模式": "Direct Link Mode",
|
28 |
+
"输入音频 URL 直链": "Input audio direct link",
|
29 |
+
"下载音频": "Download audio",
|
30 |
+
"网易云音乐可直接输入非 VIP 歌曲页面链接自动解析": "For Netease Cloud music, you can directly input the non-VIP song page link",
|
31 |
+
"# 钢琴转谱工具": "Piano Transcription Tool",
|
32 |
+
}
|
33 |
+
|
34 |
WEIGHTS_PATH = (
|
35 |
+
huggingface_hub.snapshot_download(
|
36 |
+
"Genius-Society/piano_trans",
|
37 |
+
cache_dir="./__pycache__",
|
38 |
+
)
|
39 |
+
if EN_US
|
40 |
+
else modelscope.snapshot_download(
|
41 |
+
"Genius-Society/piano_trans",
|
42 |
+
cache_dir="./__pycache__",
|
43 |
+
)
|
44 |
+
) + "/CRNN_note_F1=0.9677_pedal_F1=0.9186.pth"
|
45 |
+
|
46 |
+
|
47 |
+
def _L(zh_txt: str):
|
48 |
+
return ZH2EN[zh_txt] if EN_US else zh_txt
|
49 |
|
50 |
|
51 |
def clean_cache(cache_dir):
|
|
|
65 |
|
66 |
def is_url(s: str):
|
67 |
try:
|
68 |
+
# 解析字符串
|
69 |
result = urlparse(s)
|
70 |
+
# 检查scheme(如http, https)和netloc(域名)
|
71 |
return all([result.scheme, result.netloc])
|
72 |
|
73 |
except:
|
74 |
+
# 如果解析过程中发生异常,则返回False
|
75 |
return False
|
76 |
|
77 |
|
78 |
def audio2midi(audio_path: str, cache_dir: str):
|
79 |
+
audio, _ = load_audio(audio_path, sr=sample_rate, mono=True)
|
80 |
transcriptor = PianoTranscription(
|
81 |
device="cuda" if torch.cuda.is_available() else "cpu",
|
82 |
checkpoint_path=WEIGHTS_PATH,
|
|
|
86 |
return midi_path, os.path.basename(audio_path).split(".")[-2].capitalize()
|
87 |
|
88 |
|
89 |
+
def get_first_integer(input_string: str):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
match = re.search(r"\d+", input_string)
|
91 |
if match:
|
92 |
return str(int(match.group()))
|
93 |
+
|
94 |
else:
|
95 |
return ""
|
96 |
|
|
|
99 |
detail_api = "https://music.163.com/api/v3/song/detail"
|
100 |
parm_dict = {"id": id, "c": str([{"id": id}]), "csrf_token": ""}
|
101 |
free = False
|
102 |
+
song_name = "获取歌曲失败"
|
103 |
response = requests.get(detail_api, params=parm_dict)
|
104 |
+
# 检查请求是否成功
|
105 |
if response.status_code == 200:
|
106 |
+
# 处理成功响应
|
107 |
data = json.loads(response.text)
|
108 |
if data and "songs" in data and data["songs"]:
|
109 |
fee = int(data["songs"][0]["fee"])
|
|
|
111 |
song_name = str(data["songs"][0]["name"])
|
112 |
|
113 |
else:
|
114 |
+
song_name = "歌曲不存在"
|
115 |
|
116 |
else:
|
117 |
+
raise ConnectionError(f"错误: {response.status_code}, {response.text}")
|
118 |
|
119 |
return song_name, free
|
120 |
|
121 |
|
122 |
+
def upl_infer(audio_path: str, cache_dir="./__pycache__/mode1"):
|
123 |
+
status = "Success"
|
124 |
+
midi = pdf = xml = mxl = abc = jpg = None
|
125 |
+
try:
|
126 |
+
clean_cache(cache_dir)
|
127 |
+
midi, title = audio2midi(audio_path, cache_dir)
|
128 |
+
xml = midi2xml(midi, title)
|
129 |
+
abc = xml2abc(xml)
|
130 |
+
mxl = xml2mxl(xml)
|
131 |
+
pdf, jpg = xml2jpg(xml)
|
132 |
+
|
133 |
+
except Exception as e:
|
134 |
+
status = f"{e}"
|
135 |
+
|
136 |
+
return status, midi, pdf, xml, mxl, abc, jpg
|
137 |
+
|
138 |
+
|
139 |
def url_infer(song: str, cache_dir="./__pycache__/mode2"):
|
140 |
song_name = ""
|
141 |
+
status = "Success"
|
142 |
+
download_path = f"{cache_dir}/output.mp3"
|
143 |
+
midi = pdf = xml = mxl = abc = jpg = None
|
144 |
try:
|
145 |
+
clean_cache(cache_dir)
|
146 |
if (is_url(song) and "163" in song and "?id=" in song) or song.isdigit():
|
147 |
+
song_id = get_first_integer(song.split("?id=")[-1])
|
148 |
+
song = f"https://music.163.com/song/media/outer/url?id={song_id}.mp3"
|
149 |
song_name, free = music163_song_info(song_id)
|
150 |
if not free:
|
151 |
+
raise AttributeError("付费歌曲无法解析")
|
152 |
|
153 |
+
download_audio(song, download_path)
|
|
|
154 |
|
155 |
+
midi, title = audio2midi(download_path, cache_dir)
|
156 |
if song_name:
|
157 |
title = song_name
|
158 |
|
|
|
160 |
abc = xml2abc(xml)
|
161 |
mxl = xml2mxl(xml)
|
162 |
pdf, jpg = xml2jpg(xml)
|
163 |
+
if not os.path.exists(download_path):
|
164 |
+
raise FileExistsError(f"{download_path} not exist")
|
165 |
|
166 |
except Exception as e:
|
167 |
+
status = f"{e}"
|
168 |
+
|
169 |
+
return status, download_path, midi, pdf, xml, mxl, abc, jpg
|
170 |
|
171 |
|
172 |
if __name__ == "__main__":
|
173 |
with gr.Blocks() as iface:
|
174 |
+
gr.Markdown(_L("# 钢琴转谱工具"))
|
175 |
+
with gr.Tab(_L("上传模式")):
|
176 |
+
gr.Interface(
|
177 |
+
fn=upl_infer,
|
178 |
+
inputs=gr.Audio(label=_L("上传音频"), type="filepath"),
|
179 |
+
outputs=[
|
180 |
+
gr.Textbox(label=_L("状态栏"), show_copy_button=True),
|
181 |
+
gr.File(label=_L("下载 MIDI")),
|
182 |
+
gr.File(label=_L("下载 PDF 乐谱")),
|
183 |
+
gr.File(label=_L("下载 MusicXML")),
|
184 |
+
gr.File(label=_L("下载 MXL")),
|
185 |
+
gr.Textbox(label=_L("ABC 记谱"), show_copy_button=True),
|
186 |
+
gr.Image(
|
187 |
+
label=_L("五线谱"),
|
188 |
+
type="filepath",
|
189 |
+
show_share_button=False,
|
190 |
+
),
|
191 |
+
],
|
192 |
+
title=_L("请上传音频 100% 后再点提交"),
|
193 |
+
flagging_mode="never",
|
194 |
+
)
|
195 |
+
|
196 |
+
with gr.Tab(_L("直链模式")):
|
197 |
+
gr.Interface(
|
198 |
+
fn=url_infer,
|
199 |
+
inputs=gr.Textbox(
|
200 |
+
label=_L("输入音频 URL 直链"),
|
201 |
+
placeholder="https://music.163.com/#/song?id=",
|
202 |
+
),
|
203 |
+
outputs=[
|
204 |
+
gr.Textbox(label=_L("状态栏"), show_copy_button=True),
|
205 |
+
gr.Audio(label=_L("下载音频"), type="filepath"),
|
206 |
+
gr.File(label=_L("下载 MIDI")),
|
207 |
+
gr.File(label=_L("下载 PDF 乐谱")),
|
208 |
+
gr.File(label=_L("下载 MusicXML")),
|
209 |
+
gr.File(label=_L("���载 MXL")),
|
210 |
+
gr.Textbox(label=_L("ABC 记谱"), show_copy_button=True),
|
211 |
+
gr.Image(
|
212 |
+
label=_L("五线谱"),
|
213 |
+
type="filepath",
|
214 |
+
show_share_button=False,
|
215 |
+
),
|
216 |
+
],
|
217 |
+
title=_L("网易云音乐可直接输入非 VIP 歌曲页面链接自动解析"),
|
218 |
+
examples=["1945798894", "1945798973", "1946098771"],
|
219 |
+
flagging_mode="never",
|
220 |
+
cache_examples=False,
|
221 |
+
)
|
222 |
|
223 |
iface.launch()
|
requirements.txt
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
-
torch
|
|
|
2 |
pymupdf
|
3 |
music21
|
4 |
piano_transcription_inference
|
|
|
1 |
+
torch==2.6.0+cu118
|
2 |
+
-f https://download.pytorch.org/whl/torch
|
3 |
pymupdf
|
4 |
music21
|
5 |
piano_transcription_inference
|