Spaces:
Running
Running
admin
commited on
Commit
•
07c7745
1
Parent(s):
d5ee5ce
sync
Browse files- .gitattributes +11 -11
- .gitignore +4 -0
- README.md +4 -4
- app.py +212 -0
- convert.py +132 -0
- requirements.txt +7 -0
- xml2abc.py +0 -0
.gitattributes
CHANGED
@@ -1,35 +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 |
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
-
*.ckpt 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 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
-
*.
|
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 |
+
*.AppImage filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
example/*
|
2 |
+
*__pycache__*
|
3 |
+
test.py
|
4 |
+
rename.sh
|
README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1 |
---
|
2 |
title: Piano Trans
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: gradio
|
7 |
-
sdk_version: 4.
|
8 |
app_file: app.py
|
9 |
pinned: false
|
10 |
license: mit
|
|
|
1 |
---
|
2 |
title: Piano Trans
|
3 |
+
emoji: 🎹🎵
|
4 |
+
colorFrom: purple
|
5 |
+
colorTo: gray
|
6 |
sdk: gradio
|
7 |
+
sdk_version: 4.24.0
|
8 |
app_file: app.py
|
9 |
pinned: false
|
10 |
license: mit
|
app.py
ADDED
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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("MuGeminorum/piano_transcription", cache_dir="./__pycache__")
|
18 |
+
+ "/CRNN_note_F1=0.9677_pedal_F1=0.9186.pth"
|
19 |
+
)
|
20 |
+
|
21 |
+
|
22 |
+
def clean_cache(cache_dir=CACHE_DIR):
|
23 |
+
if os.path.exists(cache_dir):
|
24 |
+
shutil.rmtree(cache_dir)
|
25 |
+
|
26 |
+
os.mkdir(cache_dir)
|
27 |
+
|
28 |
+
|
29 |
+
def get_audio_file_type(file_path: str):
|
30 |
+
try:
|
31 |
+
# 获取媒体信息
|
32 |
+
info = mediainfo(file_path)
|
33 |
+
# 返回文件格式
|
34 |
+
return "." + info["format_name"]
|
35 |
+
|
36 |
+
except Exception as e:
|
37 |
+
print(f"Error occurred: {e}")
|
38 |
+
return None
|
39 |
+
|
40 |
+
|
41 |
+
def download_audio(url: str, save_path: str):
|
42 |
+
with NamedTemporaryFile(delete=False, suffix="_temp") as tmp_file:
|
43 |
+
temp_file_path = tmp_file.name
|
44 |
+
# 发送HTTP GET请求并下载内容
|
45 |
+
response = requests.get(url, stream=True)
|
46 |
+
# 检查请求是否成功
|
47 |
+
if response.status_code == 200:
|
48 |
+
# 将音频内容写入临时文件
|
49 |
+
for chunk in response.iter_content(chunk_size=8192):
|
50 |
+
tmp_file.write(chunk)
|
51 |
+
|
52 |
+
else:
|
53 |
+
print(f"Failed to download file: HTTP {response.status_code}")
|
54 |
+
return ""
|
55 |
+
|
56 |
+
ext = get_audio_file_type(temp_file_path)
|
57 |
+
full_path = f"{save_path}{ext}"
|
58 |
+
# 重命名临时文件以包含正确的扩展名
|
59 |
+
shutil.move(temp_file_path, full_path)
|
60 |
+
return full_path
|
61 |
+
|
62 |
+
|
63 |
+
def is_url(s: str):
|
64 |
+
try:
|
65 |
+
# 解析字符串
|
66 |
+
result = urlparse(s)
|
67 |
+
# 检查scheme(如http, https)和netloc(域名)
|
68 |
+
return all([result.scheme, result.netloc])
|
69 |
+
|
70 |
+
except:
|
71 |
+
# 如果解析过程中发生异常,则返回False
|
72 |
+
return False
|
73 |
+
|
74 |
+
|
75 |
+
def audio2midi(audio_path: str):
|
76 |
+
# Load audio
|
77 |
+
audio, _ = load_audio(audio_path, sr=sample_rate, mono=True)
|
78 |
+
# Transcriptor
|
79 |
+
transcriptor = PianoTranscription(
|
80 |
+
device="cuda" if torch.cuda.is_available() else "cpu",
|
81 |
+
checkpoint_path=WEIGHTS_PATH,
|
82 |
+
)
|
83 |
+
# device: 'cuda' | 'cpu' Transcribe and write out to MIDI file
|
84 |
+
midi_path = f"{CACHE_DIR}/output.mid"
|
85 |
+
# midi_path = audio_path.replace(audio_path.split(".")[-1], "mid")
|
86 |
+
transcriptor.transcribe(audio, midi_path)
|
87 |
+
return midi_path, os.path.basename(audio_path).split(".")[-2].capitalize()
|
88 |
+
|
89 |
+
|
90 |
+
def upl_infer(audio_path: str):
|
91 |
+
clean_cache()
|
92 |
+
try:
|
93 |
+
midi, title = audio2midi(audio_path)
|
94 |
+
xml = midi2xml(midi, title)
|
95 |
+
abc = xml2abc(xml)
|
96 |
+
mxl = xml2mxl(xml)
|
97 |
+
pdf, jpg = xml2jpg(xml)
|
98 |
+
return midi, pdf, xml, mxl, abc, jpg
|
99 |
+
|
100 |
+
except Exception as e:
|
101 |
+
return None, None, None, None, f"{e}", None
|
102 |
+
|
103 |
+
|
104 |
+
def get_first_integer(input_string: str):
|
105 |
+
match = re.search(r"\d+", input_string)
|
106 |
+
if match:
|
107 |
+
return str(int(match.group()))
|
108 |
+
|
109 |
+
else:
|
110 |
+
return ""
|
111 |
+
|
112 |
+
|
113 |
+
def music163_song_info(id: str):
|
114 |
+
detail_api = "https://music.163.com/api/v3/song/detail"
|
115 |
+
parm_dict = {"id": id, "c": str([{"id": id}]), "csrf_token": ""}
|
116 |
+
free = False
|
117 |
+
song_name = "获取歌曲失败 Failed to get the song"
|
118 |
+
response = requests.get(detail_api, params=parm_dict)
|
119 |
+
# 检查请求是否成功
|
120 |
+
if response.status_code == 200:
|
121 |
+
# 处理成功响应
|
122 |
+
data = json.loads(response.text)
|
123 |
+
if data and "songs" in data and data["songs"]:
|
124 |
+
fee = int(data["songs"][0]["fee"])
|
125 |
+
free = fee == 0 or fee == 8
|
126 |
+
song_name = str(data["songs"][0]["name"])
|
127 |
+
|
128 |
+
else:
|
129 |
+
song_name = "歌曲不存在 Song not exist"
|
130 |
+
|
131 |
+
else:
|
132 |
+
raise ConnectionError(f"Error: {response.status_code}, {response.text}")
|
133 |
+
|
134 |
+
return song_name, free
|
135 |
+
|
136 |
+
|
137 |
+
def url_infer(audio_url: str):
|
138 |
+
clean_cache()
|
139 |
+
song_name = ""
|
140 |
+
download_path = f"{CACHE_DIR}/output"
|
141 |
+
try:
|
142 |
+
if is_url(audio_url):
|
143 |
+
if "163" in audio_url and not audio_url.endswith(".mp3"):
|
144 |
+
song_id = get_first_integer(audio_url.split("?id=")[1])
|
145 |
+
audio_url = (
|
146 |
+
f"https://music.163.com/song/media/outer/url?id={song_id}.mp3"
|
147 |
+
)
|
148 |
+
song_name, free = music163_song_info(song_id)
|
149 |
+
if not free:
|
150 |
+
raise AttributeError("付费歌曲无法解析 Unable to parse VIP songs")
|
151 |
+
|
152 |
+
download_path = download_audio(audio_url, download_path)
|
153 |
+
|
154 |
+
midi, title = audio2midi(download_path)
|
155 |
+
if song_name:
|
156 |
+
title = song_name
|
157 |
+
|
158 |
+
xml = midi2xml(midi, title)
|
159 |
+
abc = xml2abc(xml)
|
160 |
+
mxl = xml2mxl(xml)
|
161 |
+
pdf, jpg = xml2jpg(xml)
|
162 |
+
return download_path, midi, pdf, xml, mxl, abc, jpg
|
163 |
+
|
164 |
+
except Exception as e:
|
165 |
+
return None, None, None, None, None, f"{e}", None
|
166 |
+
|
167 |
+
|
168 |
+
if __name__ == "__main__":
|
169 |
+
with gr.Blocks() as iface:
|
170 |
+
with gr.Tab("上传模式 (Upload Mode)"):
|
171 |
+
gr.Interface(
|
172 |
+
fn=upl_infer,
|
173 |
+
inputs=gr.Audio(
|
174 |
+
label="上传音频 (Upload an audio)",
|
175 |
+
type="filepath",
|
176 |
+
),
|
177 |
+
outputs=[
|
178 |
+
gr.File(label="下载 MIDI (Download MIDI)"),
|
179 |
+
gr.File(label="下载 PDF 乐谱 (Download PDF score)"),
|
180 |
+
gr.File(label="下载 MusicXML (Download MusicXML)"),
|
181 |
+
gr.File(label="下载 MXL (Download MXL)"),
|
182 |
+
gr.Textbox(label="abc 乐谱 (abc notation)", show_copy_button=True),
|
183 |
+
gr.Image(label="五线谱 (Staff)", type="filepath"),
|
184 |
+
],
|
185 |
+
title="请上传音频 100% 后再点提交<br>Please make sure the audio is completely uploaded before clicking Submit",
|
186 |
+
allow_flagging="never",
|
187 |
+
)
|
188 |
+
|
189 |
+
with gr.Tab("直链模式 (Direct Link Mode)"):
|
190 |
+
gr.Interface(
|
191 |
+
fn=url_infer,
|
192 |
+
inputs=gr.Textbox(label="输入音频直链 URL (Input audio direct link)"),
|
193 |
+
outputs=[
|
194 |
+
gr.Audio(label="下载音频 (Download audio)", type="filepath"),
|
195 |
+
gr.File(label="下载 MIDI (Download MIDI)"),
|
196 |
+
gr.File(label="下载 PDF 乐谱 (Download PDF score)"),
|
197 |
+
gr.File(label="下载 MusicXML (Download MusicXML)"),
|
198 |
+
gr.File(label="下载 MXL (Download MXL)"),
|
199 |
+
gr.Textbox(label="abc 乐谱 (abc notation)", show_copy_button=True),
|
200 |
+
gr.Image(label="五线谱 (Staff)", type="filepath"),
|
201 |
+
],
|
202 |
+
title="网易云音乐可直接输入非 VIP 歌曲页面链接自动解析<br>For Netease Cloud music, you can directly input the non-VIP song page link",
|
203 |
+
examples=[
|
204 |
+
"https://music.163.com/#/song?id=1945798894",
|
205 |
+
"https://music.163.com/#/song?id=1945798973",
|
206 |
+
"https://music.163.com/#/song?id=1946098771",
|
207 |
+
],
|
208 |
+
allow_flagging="never",
|
209 |
+
cache_examples=False,
|
210 |
+
)
|
211 |
+
|
212 |
+
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://master.dl.sourceforge.net/project/musescore.mirror/v4.2.0/MuseScore-4.2.0.233521125-x86_64.AppImage?viasf=1",
|
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 = "D:/Program Files/MuseScore 3/bin/MuseScore3.exe"
|
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
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
librosa==0.9.2
|
2 |
+
piano_transcription_inference
|
3 |
+
pymupdf
|
4 |
+
music21
|
5 |
+
modelscope
|
6 |
+
torch
|
7 |
+
transformers
|
xml2abc.py
ADDED
The diff for this file is too large to render.
See raw diff
|
|