admin commited on
Commit
07c7745
1 Parent(s): d5ee5ce
Files changed (7) hide show
  1. .gitattributes +11 -11
  2. .gitignore +4 -0
  3. README.md +4 -4
  4. app.py +212 -0
  5. convert.py +132 -0
  6. requirements.txt +7 -0
  7. 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
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* 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 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: yellow
5
- colorTo: purple
6
  sdk: gradio
7
- sdk_version: 4.36.1
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