admin commited on
Commit
8b13692
1 Parent(s): 825b8fd

upl piano trans

Browse files
Files changed (7) hide show
  1. .gitattributes +35 -37
  2. .gitignore +5 -2
  3. README.md +6 -8
  4. app.py +178 -256
  5. convert.py +132 -0
  6. requirements.txt +7 -4
  7. xml2abc.py +0 -0
.gitattributes CHANGED
@@ -1,37 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bin.* filter=lfs diff=lfs merge=lfs -text
5
- *.bz2 filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.model filter=lfs diff=lfs merge=lfs -text
12
- *.msgpack filter=lfs diff=lfs merge=lfs -text
13
- *.onnx filter=lfs diff=lfs merge=lfs -text
14
- *.ot filter=lfs diff=lfs merge=lfs -text
15
- *.parquet filter=lfs diff=lfs merge=lfs -text
16
- *.pb filter=lfs diff=lfs merge=lfs -text
17
- *.pt filter=lfs diff=lfs merge=lfs -text
18
- *.pth filter=lfs diff=lfs merge=lfs -text
19
- *.rar filter=lfs diff=lfs merge=lfs -text
20
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
21
- *.tar.* filter=lfs diff=lfs merge=lfs -text
22
- *.tflite filter=lfs diff=lfs merge=lfs -text
23
- *.tgz filter=lfs diff=lfs merge=lfs -text
24
- *.xz filter=lfs diff=lfs merge=lfs -text
25
- *.zip filter=lfs diff=lfs merge=lfs -text
26
- *.zstandard filter=lfs diff=lfs merge=lfs -text
27
- *.tfevents* filter=lfs diff=lfs merge=lfs -text
28
- *.db* filter=lfs diff=lfs merge=lfs -text
29
- *.ark* filter=lfs diff=lfs merge=lfs -text
30
- **/*ckpt*data* filter=lfs diff=lfs merge=lfs -text
31
- **/*ckpt*.meta filter=lfs diff=lfs merge=lfs -text
32
- **/*ckpt*.index filter=lfs diff=lfs merge=lfs -text
33
- *.safetensors filter=lfs diff=lfs merge=lfs -text
34
- *.ckpt filter=lfs diff=lfs merge=lfs -text
35
- *.gguf* filter=lfs diff=lfs merge=lfs -text
36
- *.ggml filter=lfs diff=lfs merge=lfs -text
37
- *.llamafile* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bin.* filter=lfs diff=lfs merge=lfs -text
5
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.model filter=lfs diff=lfs merge=lfs -text
12
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
13
+ *.onnx filter=lfs diff=lfs merge=lfs -text
14
+ *.ot filter=lfs diff=lfs merge=lfs -text
15
+ *.parquet filter=lfs diff=lfs merge=lfs -text
16
+ *.pb filter=lfs diff=lfs merge=lfs -text
17
+ *.pt filter=lfs diff=lfs merge=lfs -text
18
+ *.pth filter=lfs diff=lfs merge=lfs -text
19
+ *.rar filter=lfs diff=lfs merge=lfs -text
20
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
21
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
22
+ *.tflite filter=lfs diff=lfs merge=lfs -text
23
+ *.tgz filter=lfs diff=lfs merge=lfs -text
24
+ *.xz filter=lfs diff=lfs merge=lfs -text
25
+ *.zip filter=lfs diff=lfs merge=lfs -text
26
+ *.zstandard filter=lfs diff=lfs merge=lfs -text
27
+ *.tfevents* filter=lfs diff=lfs merge=lfs -text
28
+ *.db* filter=lfs diff=lfs merge=lfs -text
29
+ *.ark* filter=lfs diff=lfs merge=lfs -text
30
+ **/*ckpt*data* filter=lfs diff=lfs merge=lfs -text
31
+ **/*ckpt*.meta filter=lfs diff=lfs merge=lfs -text
32
+ **/*ckpt*.index filter=lfs diff=lfs merge=lfs -text
33
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
34
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
35
+ *.AppImage filter=lfs diff=lfs merge=lfs -text
 
 
.gitignore CHANGED
@@ -1,2 +1,5 @@
1
- test.py
2
- rename.sh
 
 
 
 
1
+ example/*
2
+ *__pycache__*
3
+ test.py
4
+ rename.sh
5
+ flagged/*
README.md CHANGED
@@ -1,14 +1,12 @@
1
  ---
2
- title: Keep Spaces Active
3
- emoji: 📊
4
- colorFrom: gray
5
  colorTo: gray
6
  sdk: gradio
7
  sdk_version: 5.9.1
8
  app_file: app.py
9
  pinned: false
10
- license: apache-2.0
11
- short_description: Keep your spaces active without payment
12
- ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Piano transcription
3
+ emoji: 🎹🎵
4
+ colorFrom: purple
5
  colorTo: gray
6
  sdk: gradio
7
  sdk_version: 5.9.1
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
+ short_description: Piano Transcription Tool
12
+ ---
 
 
app.py CHANGED
@@ -1,293 +1,215 @@
1
  import os
2
  import re
3
  import json
4
- import time
5
- import random
6
- import string
7
  import requests
8
- import schedule
9
  import gradio as gr
10
- import pandas as pd
11
- from tqdm import tqdm
12
- from functools import partial
13
- from datetime import datetime, timedelta
14
-
15
- TIMEOUT = 15
16
- DELAY = 1
17
-
18
-
19
- def start_monitor(url: str):
20
- payload = {
21
- "data": ["", ""],
22
- "event_data": None, # 使用None来表示null
23
- "fn_index": 0,
24
- "trigger_id": 11,
25
- "session_hash": "".join(
26
- random.choice(string.ascii_lowercase) for _ in range(11)
27
- ),
28
- }
29
- response = requests.post(f"{url}/queue/join?", json=payload)
30
- # 检查请求是否成功
31
- if response.status_code == 200:
32
- return "monitoring"
33
 
34
- return "running"
 
 
35
 
 
36
 
37
- def add_six_hours(match):
38
- datetime_str = match.group(0)
39
- dt = datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S")
40
- dt_plus_six = dt + timedelta(hours=6)
41
- return dt_plus_six.strftime("%Y-%m-%d %H:%M:%S")
42
 
 
 
 
 
 
 
43
 
44
- def fix_datetime(text: str):
45
- datetime_pattern = r"\b\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\b"
46
- return re.sub(datetime_pattern, add_six_hours, text)
47
 
48
 
49
- def get_studios(username: str):
50
- # 请求负载
51
- payload = {
52
- "PageNumber": 1,
53
- "PageSize": 1000,
54
- "Name": "",
55
- "SortBy": "gmt_modified",
56
- "Order": "desc",
57
- }
58
- try:
59
- # 发送PUT请求
60
- response = requests.put(
61
- f"https://www.modelscope.cn/api/v1/studios/{username}/list",
62
- data=json.dumps(payload),
63
- headers={
64
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
65
- },
66
- timeout=TIMEOUT,
67
- )
68
  # 检查请求是否成功
69
- response.raise_for_status()
70
- # 解析JSON响应
71
- spaces: list = response.json()["Data"]["Studios"]
72
- if spaces:
73
- studios = []
74
- for space in spaces:
75
- studios.append(
76
- f"https://www.modelscope.cn/api/v1/studio/{username}/{space['Name']}/start_expired"
77
- )
78
 
79
- return studios
 
 
80
 
81
- except requests.exceptions.Timeout as errt:
82
- print(f"请求超时: {errt}, retrying...")
83
- time.sleep(DELAY)
84
- return get_studios(username)
 
85
 
86
- except Exception as err:
87
- print(f"请求发生错误: {err}")
88
 
89
- return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
 
92
- def get_spaces(username: str):
 
93
  try:
94
- # 发送GET请求
95
- response = requests.get(
96
- "https://huggingface.co/spaces-json",
97
- params={"sort": "trending", "search": username},
98
- headers={
99
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537"
100
- },
101
- timeout=TIMEOUT,
102
- )
103
- # 检查请求是否成功
104
- response.raise_for_status()
105
- # 解析JSON响应
106
- spaces: list = response.json()["spaces"]
107
- studios = []
108
- for space in spaces:
109
- if space["author"] == username:
110
- studios.append(
111
- f"https://{space['id'].replace('/', '-').replace('_', '-')}.hf.space"
112
- )
113
 
114
- return studios
 
115
 
116
- except requests.exceptions.Timeout as errt:
117
- print(f"请求超时: {errt}, retrying...")
118
- time.sleep(DELAY)
119
- return get_spaces(username)
120
 
121
- except Exception as err:
122
- print(f"请求发生错误: {err}")
 
 
123
 
124
- return []
 
125
 
126
 
127
- def activate_space(url: str):
128
- status = "running"
129
- try:
130
- if ".hf.space" in url:
131
- response = requests.get(url, timeout=TIMEOUT)
132
- response.raise_for_status()
133
- if "-keep-spaces-active.hf.space" in url:
134
- status = start_monitor(url)
 
 
 
 
 
 
135
 
136
  else:
137
- response = requests.put(url, timeout=TIMEOUT)
138
- response.raise_for_status()
139
- print("Expired studio found, restarting...")
140
- while (
141
- requests.get(
142
- url.replace("/start_expired", "/status"),
143
- timeout=TIMEOUT,
144
- ).json()["Data"]["Status"]
145
- == "ExpiredCreating"
146
- ):
147
- requests.get(
148
- url.replace("/api/v1/studio/", "/studios/").replace(
149
- "/start_expired", ""
150
- ),
151
- timeout=TIMEOUT,
152
- )
153
- time.sleep(5)
154
 
155
- except requests.exceptions.Timeout as e:
156
- if ".hf.space" in url:
157
- status = "restarting"
158
- else:
159
- print(f"Failed to activate {url} : {e}, retrying...")
160
- return activate_space(url)
161
-
162
- except requests.RequestException as e:
163
- if (
164
- "500 Server Error:" in f"{e}"
165
- and response.json()["Message"] == "studio is not expired"
166
- ):
167
- status = "running"
168
- else:
169
- status = f"{e}"
170
 
171
- except Exception as e:
172
- status = f"{e}"
173
 
174
- return status
175
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
- def activate(hf_users: str, ms_users: str):
178
- if not hf_users:
179
- hf_users = os.getenv("hf_users")
180
 
181
- if not ms_users:
182
- ms_users = hf_users
 
183
 
184
- hf_usernames = hf_users.split(";")
185
- ms_usernames = ms_users.split(";")
186
- spaces = []
187
- for user in tqdm(hf_usernames, desc="Collecting spaces..."):
188
- username = user.strip()
189
- if username:
190
- spaces += get_spaces(username)
191
- time.sleep(DELAY)
192
 
193
- monitors, studios = [], []
194
- for space in spaces:
195
- if "keep-spaces-active" in space:
196
- monitors.append(space)
197
- else:
198
- studios.append(space)
199
- spaces = monitors + studios
200
-
201
- for user in tqdm(ms_usernames, desc="Collecting studios..."):
202
- username = user.strip()
203
- if username:
204
- spaces += get_studios(username)
205
- time.sleep(DELAY)
206
-
207
- output = []
208
- for space in tqdm(spaces, desc="Activating spaces..."):
209
- output.append(
210
- {
211
- "space": space.split("//")[-1].replace(
212
- "www.modelscope.cn/api/v1/studio/", ""
213
- ),
214
- "status": activate_space(space),
215
- }
216
- )
217
- time.sleep(DELAY)
218
-
219
- print("Activation complete!")
220
- return pd.DataFrame(output)
221
-
222
-
223
- def monitor(hf_users: str, ms_users: str, period=4):
224
- if schedule.get_jobs():
225
- return
226
-
227
- if not hf_users:
228
- hf_users = os.getenv("hf_users")
229
-
230
- if not ms_users:
231
- ms_users = hf_users
232
-
233
- print(f"监控开启中...每 {period} 小时触发")
234
- fixed_activate = partial(activate, hf_users=hf_users, ms_users=ms_users)
235
- schedule.every(period).hours.do(fixed_activate)
236
- while True:
237
- schedule.run_pending()
238
- time.sleep(DELAY)
239
-
240
-
241
- def listasks():
242
- jobs = schedule.get_jobs()
243
- if jobs:
244
- details = f"{jobs}".replace("[", "").replace("]", "")
245
- return fix_datetime(
246
- details.split("functools.")[0] + "(" + details.split(") (")[-1]
247
- )
248
-
249
- return "None"
250
-
251
-
252
- with gr.Blocks() as demo:
253
- gr.Interface(
254
- title="Start keeping all spaces active periodically",
255
- fn=monitor,
256
- inputs=[
257
- gr.Textbox(
258
- label="HuggingFace",
259
- placeholder="Usernames joint by ;",
260
- ),
261
- gr.Textbox(
262
- label="ModelScope",
263
- placeholder="Usernames joint by ;",
264
- ),
265
- ],
266
- outputs=None,
267
- allow_flagging="never",
268
- )
269
- gr.Interface(
270
- title="See current task status",
271
- fn=listasks,
272
- inputs=None,
273
- outputs=gr.Textbox(label="Current task details"),
274
- allow_flagging="never",
275
- )
276
- gr.Interface(
277
- title="Test activation for all spaces once",
278
- fn=activate,
279
- inputs=[
280
- gr.Textbox(
281
- label="HuggingFace",
282
- placeholder="Usernames joint by ;",
283
- ),
284
- gr.Textbox(
285
- label="ModelScope",
286
- placeholder="Usernames joint by ;",
287
- ),
288
- ],
289
- outputs=gr.Dataframe(label="Activated spaces"),
290
- allow_flagging="never",
291
- )
292
 
293
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import re
3
  import json
4
+ import torch
5
+ import shutil
 
6
  import requests
 
7
  import gradio as gr
8
+ from piano_transcription_inference import PianoTranscription, load_audio, sample_rate
9
+ from modelscope import snapshot_download
10
+ from tempfile import NamedTemporaryFile
11
+ from pydub.utils import mediainfo
12
+ from urllib.parse import urlparse
13
+ from convert import midi2xml, xml2abc, xml2mxl, xml2jpg
14
+
15
+ CACHE_DIR = "./flagged"
16
+ WEIGHTS_PATH = (
17
+ snapshot_download(
18
+ "Genius-Society/piano_transcription",
19
+ cache_dir="./__pycache__",
20
+ )
21
+ + "/CRNN_note_F1=0.9677_pedal_F1=0.9186.pth"
22
+ )
23
+
 
 
 
 
 
 
 
24
 
25
+ def clean_cache(cache_dir=CACHE_DIR):
26
+ if os.path.exists(cache_dir):
27
+ shutil.rmtree(cache_dir)
28
 
29
+ os.mkdir(cache_dir)
30
 
 
 
 
 
 
31
 
32
+ def get_audio_file_type(file_path: str):
33
+ try:
34
+ # 获取媒体信息
35
+ info = mediainfo(file_path)
36
+ # 返回文件格式
37
+ return "." + info["format_name"]
38
 
39
+ except Exception as e:
40
+ print(f"Error occurred: {e}")
41
+ return None
42
 
43
 
44
+ def download_audio(url: str, save_path: str):
45
+ with NamedTemporaryFile(delete=False, suffix="_temp") as tmp_file:
46
+ temp_file_path = tmp_file.name
47
+ # 发送HTTP GET请求并下载内容
48
+ response = requests.get(url, stream=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  # 检查请求是否成功
50
+ if response.status_code == 200:
51
+ # 将音频内容写入临时文件
52
+ for chunk in response.iter_content(chunk_size=8192):
53
+ tmp_file.write(chunk)
 
 
 
 
 
54
 
55
+ else:
56
+ print(f"Failed to download file: HTTP {response.status_code}")
57
+ return ""
58
 
59
+ ext = get_audio_file_type(temp_file_path)
60
+ full_path = f"{save_path}{ext}"
61
+ # 重命名临时文件以包含正确的扩展名
62
+ shutil.move(temp_file_path, full_path)
63
+ return full_path
64
 
 
 
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):
79
+ # Load audio
80
+ audio, _ = load_audio(audio_path, sr=sample_rate, mono=True)
81
+ # Transcriptor
82
+ transcriptor = PianoTranscription(
83
+ device="cuda" if torch.cuda.is_available() else "cpu",
84
+ checkpoint_path=WEIGHTS_PATH,
85
+ )
86
+ # device: 'cuda' | 'cpu' Transcribe and write out to MIDI file
87
+ midi_path = f"{CACHE_DIR}/output.mid"
88
+ # midi_path = audio_path.replace(audio_path.split(".")[-1], "mid")
89
+ transcriptor.transcribe(audio, midi_path)
90
+ return midi_path, os.path.basename(audio_path).split(".")[-2].capitalize()
91
 
92
 
93
+ def upl_infer(audio_path: str):
94
+ clean_cache()
95
  try:
96
+ midi, title = audio2midi(audio_path)
97
+ xml = midi2xml(midi, title)
98
+ abc = xml2abc(xml)
99
+ mxl = xml2mxl(xml)
100
+ pdf, jpg = xml2jpg(xml)
101
+ return midi, pdf, xml, mxl, abc, jpg
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
+ except Exception as e:
104
+ return None, None, None, None, f"{e}", None
105
 
 
 
 
 
106
 
107
+ def get_first_integer(input_string: str):
108
+ match = re.search(r"\d+", input_string)
109
+ if match:
110
+ return str(int(match.group()))
111
 
112
+ else:
113
+ return ""
114
 
115
 
116
+ def music163_song_info(id: str):
117
+ detail_api = "https://music.163.com/api/v3/song/detail"
118
+ parm_dict = {"id": id, "c": str([{"id": id}]), "csrf_token": ""}
119
+ free = False
120
+ song_name = "获取歌曲失败 Failed to get the song"
121
+ response = requests.get(detail_api, params=parm_dict)
122
+ # 检查请求是否成功
123
+ if response.status_code == 200:
124
+ # 处理成功响应
125
+ data = json.loads(response.text)
126
+ if data and "songs" in data and data["songs"]:
127
+ fee = int(data["songs"][0]["fee"])
128
+ free = fee == 0 or fee == 8
129
+ song_name = str(data["songs"][0]["name"])
130
 
131
  else:
132
+ song_name = "歌曲不存在 Song not exist"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
+ else:
135
+ raise ConnectionError(f"Error: {response.status_code}, {response.text}")
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
+ return song_name, free
 
138
 
 
139
 
140
+ def url_infer(audio_url: str):
141
+ clean_cache()
142
+ song_name = ""
143
+ download_path = f"{CACHE_DIR}/output"
144
+ try:
145
+ if is_url(audio_url):
146
+ if "163" in audio_url and not audio_url.endswith(".mp3"):
147
+ song_id = get_first_integer(audio_url.split("?id=")[1])
148
+ audio_url = (
149
+ f"https://music.163.com/song/media/outer/url?id={song_id}.mp3"
150
+ )
151
+ song_name, free = music163_song_info(song_id)
152
+ if not free:
153
+ raise AttributeError("付费歌曲无法解析 Unable to parse VIP songs")
154
 
155
+ download_path = download_audio(audio_url, download_path)
 
 
156
 
157
+ midi, title = audio2midi(download_path)
158
+ if song_name:
159
+ title = song_name
160
 
161
+ xml = midi2xml(midi, title)
162
+ abc = xml2abc(xml)
163
+ mxl = xml2mxl(xml)
164
+ pdf, jpg = xml2jpg(xml)
165
+ return download_path, midi, pdf, xml, mxl, abc, jpg
166
+
167
+ except Exception as e:
168
+ return None, None, None, None, None, f"{e}", None
169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
+ if __name__ == "__main__":
172
+ with gr.Blocks() as iface:
173
+ with gr.Tab("上传模式 (Upload Mode)"):
174
+ gr.Interface(
175
+ fn=upl_infer,
176
+ inputs=gr.Audio(
177
+ label="上传音频 (Upload an audio)",
178
+ type="filepath",
179
+ ),
180
+ outputs=[
181
+ gr.File(label="下载 MIDI (Download MIDI)"),
182
+ gr.File(label="下载 PDF 乐谱 (Download PDF score)"),
183
+ gr.File(label="下载 MusicXML (Download MusicXML)"),
184
+ gr.File(label="下载 MXL (Download MXL)"),
185
+ gr.Textbox(label="abc 乐谱 (abc notation)", show_copy_button=True),
186
+ gr.Image(label="五线谱 (Staff)", type="filepath"),
187
+ ],
188
+ title="请上传音频 100% 后再点提交<br>Please make sure the audio is completely uploaded before clicking Submit",
189
+ allow_flagging="never",
190
+ )
191
+
192
+ with gr.Tab("直链模式 (Direct Link Mode)"):
193
+ gr.Interface(
194
+ fn=url_infer,
195
+ inputs=gr.Textbox(label="输入音频直链 URL (Input audio direct link)"),
196
+ outputs=[
197
+ gr.Audio(label="下载音频 (Download audio)", type="filepath"),
198
+ gr.File(label="下载 MIDI (Download MIDI)"),
199
+ gr.File(label="下载 PDF 乐谱 (Download PDF score)"),
200
+ gr.File(label="下载 MusicXML (Download MusicXML)"),
201
+ gr.File(label="下载 MXL (Download MXL)"),
202
+ gr.Textbox(label="abc 乐谱 (abc notation)", show_copy_button=True),
203
+ gr.Image(label="五线谱 (Staff)", type="filepath"),
204
+ ],
205
+ title="网易云音乐可直��输入非 VIP 歌曲页面链接自动解析<br>For Netease Cloud music, you can directly input the non-VIP song page link",
206
+ examples=[
207
+ "https://music.163.com/#/song?id=1945798894",
208
+ "https://music.163.com/#/song?id=1945798973",
209
+ "https://music.163.com/#/song?id=1946098771",
210
+ ],
211
+ allow_flagging="never",
212
+ cache_examples=False,
213
+ )
214
+
215
+ iface.launch()
convert.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import fitz
4
+ import requests
5
+ import subprocess
6
+ from PIL import Image
7
+ from music21 import converter
8
+
9
+
10
+ def download(url: str, directory: str, filename: str):
11
+ if directory != "" and not os.path.exists(directory):
12
+ os.makedirs(directory)
13
+ # Create the full path for the file to be saved
14
+ file_path = os.path.join(directory, filename)
15
+ # Send a GET request to the URL
16
+ response = requests.get(url, stream=True)
17
+ # Check if the request was successful
18
+ if response.status_code == 200:
19
+ # Open the file in write-binary mode
20
+ with open(file_path, "wb") as file:
21
+ # Write the contents of the response to the file
22
+ for chunk in response.iter_content(chunk_size=1024):
23
+ if chunk: # Filter out keep-alive new chunks
24
+ file.write(chunk)
25
+
26
+ print(f"The file has been downloaded and saved to {file_path}")
27
+
28
+ else:
29
+ print(f"Failed to download the file. Status code: {response.status_code}")
30
+
31
+ return os.path.join(directory, filename)
32
+
33
+
34
+ if sys.platform.startswith("linux"):
35
+ apkname = "MuseScore.AppImage"
36
+ extra_dir = "squashfs-root"
37
+ if not os.path.exists(apkname):
38
+ download(
39
+ url="https://www.modelscope.cn/studio/Genius-Society/piano_transcription/resolve/master/MuseScore.AppImage",
40
+ directory="./",
41
+ filename=apkname,
42
+ )
43
+
44
+ if not os.path.exists(extra_dir):
45
+ subprocess.run(["chmod", "+x", f"./{apkname}"])
46
+ subprocess.run([f"./{apkname}", "--appimage-extract"])
47
+
48
+ MSCORE = f"./{extra_dir}/AppRun"
49
+ os.environ["QT_QPA_PLATFORM"] = "offscreen"
50
+
51
+ else:
52
+ MSCORE = os.getenv("mscore")
53
+
54
+
55
+ def add_title_to_xml(xml_path: str, title: str):
56
+ midi_data = converter.parse(xml_path)
57
+ # 将标题添加到 MIDI 文件中
58
+ midi_data.metadata.movementName = title
59
+ midi_data.metadata.composer = "Transcripted by AI"
60
+ # 保存修改后的 MIDI 文件
61
+ midi_data.write("musicxml", fp=xml_path)
62
+
63
+
64
+ def xml2abc(xml_path: str):
65
+ result = subprocess.run(
66
+ ["python", "xml2abc.py", xml_path], stdout=subprocess.PIPE, text=True
67
+ )
68
+ if result.returncode == 0:
69
+ return result.stdout
70
+
71
+ return ""
72
+
73
+
74
+ def xml2mxl(xml_path: str):
75
+ mxl_file = xml_path.replace(".musicxml", ".mxl")
76
+ command = [MSCORE, "-o", mxl_file, xml_path]
77
+ result = subprocess.run(command)
78
+ print(result)
79
+ return mxl_file
80
+
81
+
82
+ def midi2xml(mid_file: str, title: str):
83
+ xml_file = mid_file.replace(".mid", ".musicxml")
84
+ command = [MSCORE, "-o", xml_file, mid_file]
85
+ result = subprocess.run(command)
86
+ add_title_to_xml(xml_file, title)
87
+ print(result)
88
+ return xml_file
89
+
90
+
91
+ def xml2midi(xml_file: str):
92
+ midi_file = xml_file.replace(".musicxml", ".mid")
93
+ command = [MSCORE, "-o", midi_file, xml_file]
94
+ result = subprocess.run(command)
95
+ print(result)
96
+ return midi_file
97
+
98
+
99
+ def pdf2img(pdf_path: str):
100
+ output_path = pdf_path.replace(".pdf", ".jpg")
101
+ doc = fitz.open(pdf_path)
102
+ # 创建一个图像列表
103
+ images = []
104
+ for page_number in range(doc.page_count):
105
+ page = doc[page_number]
106
+ # 将页面渲染为图像
107
+ image = page.get_pixmap()
108
+ # 将图像添加到列表
109
+ images.append(
110
+ Image.frombytes("RGB", [image.width, image.height], image.samples)
111
+ )
112
+ # 竖向合并图像
113
+ merged_image = Image.new(
114
+ "RGB", (images[0].width, sum(image.height for image in images))
115
+ )
116
+ y_offset = 0
117
+ for image in images:
118
+ merged_image.paste(image, (0, y_offset))
119
+ y_offset += image.height
120
+ # 保存合并后的图像为JPG
121
+ merged_image.save(output_path, "JPEG")
122
+ # 关闭PDF文档
123
+ doc.close()
124
+ return output_path
125
+
126
+
127
+ def xml2jpg(xml_file: str):
128
+ pdf_score = xml_file.replace(".musicxml", ".pdf")
129
+ command = [MSCORE, "-o", pdf_score, xml_file]
130
+ result = subprocess.run(command)
131
+ print(result)
132
+ return pdf_score, pdf2img(pdf_score)
requirements.txt CHANGED
@@ -1,4 +1,7 @@
1
- requests
2
- beautifulsoup4
3
- tqdm
4
- schedule
 
 
 
 
1
+ librosa==0.9.2
2
+ piano_transcription_inference
3
+ pymupdf
4
+ music21
5
+ modelscope[framework]==1.18
6
+ torch
7
+ transformers
xml2abc.py ADDED
The diff for this file is too large to render. See raw diff