Spaces:
Running
Running
Create audio_sep_splitter.py
Browse files- audio_sep_splitter.py +247 -0
audio_sep_splitter.py
ADDED
@@ -0,0 +1,247 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/python3
|
2 |
+
|
3 |
+
# Copyright (c) 2021 LALAL.AI
|
4 |
+
#
|
5 |
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
# of this software and associated documentation files (the "Software"), to deal
|
7 |
+
# in the Software without restriction, including without limitation the rights
|
8 |
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
# copies of the Software, and to permit persons to whom the Software is
|
10 |
+
# furnished to do so, subject to the following conditions:
|
11 |
+
#
|
12 |
+
# The above copyright notice and this permission notice shall be included in all
|
13 |
+
# copies or substantial portions of the Software.
|
14 |
+
#
|
15 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
+
# SOFTWARE.
|
22 |
+
|
23 |
+
|
24 |
+
import cgi
|
25 |
+
import json
|
26 |
+
import os
|
27 |
+
import sys
|
28 |
+
import time
|
29 |
+
from argparse import ArgumentParser
|
30 |
+
from urllib.parse import quote, unquote, urlencode
|
31 |
+
from urllib.request import urlopen, Request
|
32 |
+
from dotenv import load_dotenv
|
33 |
+
|
34 |
+
|
35 |
+
CURRENT_DIR_PATH = os.path.dirname(os.path.realpath(__file__))
|
36 |
+
URL_API = "https://www.lalal.ai/api/"
|
37 |
+
|
38 |
+
|
39 |
+
def update_percent(pct):
|
40 |
+
pct = str(pct)
|
41 |
+
sys.stdout.write("\b" * len(pct))
|
42 |
+
sys.stdout.write(" " * len(pct))
|
43 |
+
sys.stdout.write("\b" * len(pct))
|
44 |
+
sys.stdout.write(pct)
|
45 |
+
sys.stdout.flush()
|
46 |
+
|
47 |
+
|
48 |
+
def make_content_disposition(filename, disposition="attachment"):
|
49 |
+
try:
|
50 |
+
filename.encode("ascii")
|
51 |
+
file_expr = f'filename="{filename}"'
|
52 |
+
except UnicodeEncodeError:
|
53 |
+
quoted = quote(filename)
|
54 |
+
file_expr = f"filename*=utf-8''{quoted}"
|
55 |
+
return f"{disposition}; {file_expr}"
|
56 |
+
|
57 |
+
|
58 |
+
def upload_file(file_path, license):
|
59 |
+
url_for_upload = URL_API + "upload/"
|
60 |
+
_, filename = os.path.split(file_path)
|
61 |
+
headers = {
|
62 |
+
"Content-Disposition": make_content_disposition(filename),
|
63 |
+
"Authorization": f"license {license}",
|
64 |
+
}
|
65 |
+
with open(file_path, "rb") as f:
|
66 |
+
request = Request(url_for_upload, f, headers)
|
67 |
+
with urlopen(request) as response:
|
68 |
+
upload_result = json.load(response)
|
69 |
+
if upload_result["status"] == "success":
|
70 |
+
return upload_result["id"]
|
71 |
+
else:
|
72 |
+
raise RuntimeError(upload_result["error"])
|
73 |
+
|
74 |
+
|
75 |
+
def split_file(file_id, license, stem, filter_type, splitter):
|
76 |
+
url_for_split = URL_API + "split/"
|
77 |
+
headers = {
|
78 |
+
"Authorization": f"license {license}",
|
79 |
+
}
|
80 |
+
query_args = {
|
81 |
+
"id": file_id,
|
82 |
+
"stem": stem,
|
83 |
+
"filter": filter_type,
|
84 |
+
"splitter": splitter,
|
85 |
+
}
|
86 |
+
encoded_args = urlencode(query_args).encode("utf-8")
|
87 |
+
request = Request(url_for_split, encoded_args, headers=headers)
|
88 |
+
with urlopen(request) as response:
|
89 |
+
split_result = json.load(response)
|
90 |
+
if split_result["status"] == "error":
|
91 |
+
raise RuntimeError(split_result["error"])
|
92 |
+
|
93 |
+
|
94 |
+
def check_file(file_id):
|
95 |
+
url_for_check = URL_API + "check/?"
|
96 |
+
query_args = {"id": file_id}
|
97 |
+
encoded_args = urlencode(query_args)
|
98 |
+
|
99 |
+
is_queueup = False
|
100 |
+
|
101 |
+
while True:
|
102 |
+
with urlopen(url_for_check + encoded_args) as response:
|
103 |
+
check_result = json.load(response)
|
104 |
+
|
105 |
+
if check_result["status"] == "error":
|
106 |
+
raise RuntimeError(check_result["error"])
|
107 |
+
|
108 |
+
task_state = check_result["task"]["state"]
|
109 |
+
|
110 |
+
if task_state == "error":
|
111 |
+
raise RuntimeError(check_result["task"]["error"])
|
112 |
+
|
113 |
+
if task_state == "progress":
|
114 |
+
progress = int(check_result["task"]["progress"])
|
115 |
+
if progress == 0 and not is_queueup:
|
116 |
+
print("Queue up...")
|
117 |
+
is_queueup = True
|
118 |
+
elif progress > 0:
|
119 |
+
update_percent(f"Progress: {progress}%")
|
120 |
+
|
121 |
+
if task_state == "success":
|
122 |
+
update_percent("Progress: 100%\n")
|
123 |
+
stem_track_url = check_result["split"]["stem_track"]
|
124 |
+
back_track_url = check_result["split"]["back_track"]
|
125 |
+
return stem_track_url, back_track_url
|
126 |
+
|
127 |
+
time.sleep(15)
|
128 |
+
|
129 |
+
|
130 |
+
def get_filename_from_content_disposition(header):
|
131 |
+
_, params = cgi.parse_header(header)
|
132 |
+
filename = params.get("filename")
|
133 |
+
if filename:
|
134 |
+
return filename
|
135 |
+
filename = params.get("filename*")
|
136 |
+
if filename:
|
137 |
+
encoding, quoted = filename.split("''")
|
138 |
+
unquoted = unquote(quoted, encoding)
|
139 |
+
return unquoted
|
140 |
+
raise ValueError("Invalid header Content-Disposition")
|
141 |
+
|
142 |
+
|
143 |
+
def download_file(url_for_download, output_path):
|
144 |
+
with urlopen(url_for_download) as response:
|
145 |
+
filename = get_filename_from_content_disposition(
|
146 |
+
response.headers["Content-Disposition"]
|
147 |
+
)
|
148 |
+
file_path = os.path.join(output_path, filename)
|
149 |
+
with open(file_path, "wb") as f:
|
150 |
+
while True:
|
151 |
+
chunk = response.read(8196)
|
152 |
+
if not chunk:
|
153 |
+
break
|
154 |
+
f.write(chunk)
|
155 |
+
return file_path
|
156 |
+
|
157 |
+
|
158 |
+
def batch_process_for_file(input_path, output_path, stem, filter_type, splitter):
|
159 |
+
load_dotenv("/home/airflow/utils/deepsync_dub_utils/.env.lalalai")
|
160 |
+
license = os.environ.get("LALALAI_LICENCE")
|
161 |
+
|
162 |
+
try:
|
163 |
+
print(f'Uploading the file "{input_path}"...')
|
164 |
+
file_id = upload_file(file_path=input_path, license=license)
|
165 |
+
print(
|
166 |
+
f'The file "{input_path}" has been successfully uploaded (file id: {file_id})'
|
167 |
+
)
|
168 |
+
|
169 |
+
print(f'Processing the file "{input_path}"...')
|
170 |
+
split_file(file_id, license, stem, filter_type, splitter)
|
171 |
+
stem_track_url, back_track_url = check_file(file_id)
|
172 |
+
|
173 |
+
print(f'Downloading the stem track file "{stem_track_url}"...')
|
174 |
+
downloaded_file = download_file(stem_track_url, output_path)
|
175 |
+
print(f'The stem track file has been downloaded to "{downloaded_file}"')
|
176 |
+
|
177 |
+
print(f'Downloading the back track file "{back_track_url}"...')
|
178 |
+
downloaded_file = download_file(back_track_url, output_path)
|
179 |
+
print(f'The back track file has been downloaded to "{downloaded_file}"')
|
180 |
+
|
181 |
+
print(f'The file "{input_path}" has been successfully split')
|
182 |
+
except Exception as err:
|
183 |
+
print(f'Cannot process the file "{input_path}": {err}')
|
184 |
+
|
185 |
+
|
186 |
+
def batch_process(input_path, output_path, stem, filter_type, splitter):
|
187 |
+
if os.path.isfile(input_path):
|
188 |
+
batch_process_for_file(input_path, output_path, stem, filter_type, splitter)
|
189 |
+
else:
|
190 |
+
for path in os.listdir(input_path):
|
191 |
+
path = os.path.join(input_path, path)
|
192 |
+
if os.path.isfile(path):
|
193 |
+
batch_process_for_file(path, output_path, stem, filter_type, splitter)
|
194 |
+
|
195 |
+
|
196 |
+
def main():
|
197 |
+
parser = ArgumentParser(description="Lalalai splitter")
|
198 |
+
parser.add_argument(
|
199 |
+
"--input", type=str, required=True, help="Input directory or a file"
|
200 |
+
)
|
201 |
+
parser.add_argument(
|
202 |
+
"--output", type=str, default=CURRENT_DIR_PATH, help="Output directory"
|
203 |
+
)
|
204 |
+
parser.add_argument(
|
205 |
+
"--stem",
|
206 |
+
type=str,
|
207 |
+
default="vocals",
|
208 |
+
choices=[
|
209 |
+
"vocals",
|
210 |
+
"drum",
|
211 |
+
"bass",
|
212 |
+
"piano",
|
213 |
+
"electric_guitar",
|
214 |
+
"acoustic_guitar",
|
215 |
+
"synthesizer",
|
216 |
+
"voice",
|
217 |
+
"strings",
|
218 |
+
"wind",
|
219 |
+
],
|
220 |
+
help='Stem option. Stems "voice", "strings", "wind" are not supported by Cassiopeia',
|
221 |
+
)
|
222 |
+
parser.add_argument(
|
223 |
+
"--filter",
|
224 |
+
type=int,
|
225 |
+
default=1,
|
226 |
+
choices=[0, 1, 2],
|
227 |
+
help="0 (mild), 1 (normal), 2 (aggressive)",
|
228 |
+
)
|
229 |
+
parser.add_argument(
|
230 |
+
"--splitter",
|
231 |
+
type=str,
|
232 |
+
default="phoenix",
|
233 |
+
choices=["phoenix", "cassiopeia"],
|
234 |
+
help="The type of neural network used to split audio",
|
235 |
+
)
|
236 |
+
|
237 |
+
args = parser.parse_args()
|
238 |
+
|
239 |
+
os.makedirs(args.output, exist_ok=True)
|
240 |
+
batch_process(args.input, args.output, args.stem, args.filter, args.splitter)
|
241 |
+
|
242 |
+
|
243 |
+
if __name__ == "__main__":
|
244 |
+
try:
|
245 |
+
main()
|
246 |
+
except Exception as err:
|
247 |
+
print(err)
|