Spaces:
Runtime error
Runtime error
1lint
commited on
Commit
β’
517cc63
1
Parent(s):
1948c1d
init commit
Browse files- .gitignore +4 -0
- Dockerfile +13 -0
- README.md +7 -5
- app.py +126 -0
- header.md +4 -0
- requirements.txt +2 -0
- theme.py +91 -0
.gitignore
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
test*
|
2 |
+
test/
|
3 |
+
__pycache__/
|
4 |
+
videos/
|
Dockerfile
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.10.9
|
2 |
+
|
3 |
+
WORKDIR /code
|
4 |
+
|
5 |
+
COPY ./requirements.txt /code/requirements.txt
|
6 |
+
|
7 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
8 |
+
|
9 |
+
RUN sudo apt install ffmpeg
|
10 |
+
|
11 |
+
COPY . .
|
12 |
+
|
13 |
+
CMD ["python", "app.py"]
|
README.md
CHANGED
@@ -1,10 +1,12 @@
|
|
1 |
---
|
2 |
title: Acapellify
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
-
sdk:
|
|
|
|
|
7 |
pinned: false
|
8 |
---
|
9 |
|
10 |
-
|
|
|
1 |
---
|
2 |
title: Acapellify
|
3 |
+
emoji: π
|
4 |
+
colorFrom: red
|
5 |
+
colorTo: blue
|
6 |
+
sdk: gradio
|
7 |
+
sdk_version: 3.28.3
|
8 |
+
app_file: app.py
|
9 |
pinned: false
|
10 |
---
|
11 |
|
12 |
+
### Run with `python app.py`
|
app.py
ADDED
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from gradio_client import Client
|
2 |
+
import yt_dlp # pip install yt-dlp
|
3 |
+
|
4 |
+
import subprocess
|
5 |
+
|
6 |
+
import os
|
7 |
+
from pathlib import Path
|
8 |
+
import gradio as gr
|
9 |
+
import shutil
|
10 |
+
import random
|
11 |
+
from theme import theme
|
12 |
+
|
13 |
+
VIDEO_DIRECTORY = "videos"
|
14 |
+
|
15 |
+
|
16 |
+
client = Client("abidlabs/music-separation")
|
17 |
+
|
18 |
+
def acapellify(audio_path):
|
19 |
+
result = client.predict(audio_path, api_name="/predict")
|
20 |
+
return result[0]
|
21 |
+
|
22 |
+
# based on https://github.com/gradio-app/gradio/blob/bebfb72b353a4280155cf7070441fc476ac10172/guides/06_client-libraries/fastapi-app-with-the-gradio-client.md
|
23 |
+
def process_video(video_path):
|
24 |
+
old_audio = os.path.basename(video_path).split(".")[0] + ".m4a"
|
25 |
+
subprocess.run(['ffmpeg', '-y', '-i', video_path, '-vn', '-acodec', 'copy', old_audio])
|
26 |
+
|
27 |
+
new_audio = acapellify(old_audio)
|
28 |
+
|
29 |
+
new_video = f"acap_{Path(video_path).name}"
|
30 |
+
new_video_path = f"{VIDEO_DIRECTORY}/{new_video}"
|
31 |
+
subprocess.call(['ffmpeg', '-y', '-i', video_path, '-i', new_audio, '-map', '0:v', '-map', '1:a', '-c:v', 'copy', '-c:a', 'aac', '-strict', 'experimental', new_video_path])
|
32 |
+
|
33 |
+
# remove old audio and video
|
34 |
+
os.remove(old_audio)
|
35 |
+
os.remove(video_path)
|
36 |
+
|
37 |
+
new_audio_path = f"{VIDEO_DIRECTORY}/{old_audio}"
|
38 |
+
shutil.move(new_audio, new_audio_path)
|
39 |
+
|
40 |
+
return new_video_path, new_audio_path
|
41 |
+
|
42 |
+
# filename default value will return name of video on youtube
|
43 |
+
def download_yt_url(url: str, filename: str = "%(title)s", format = 'mp4'):
|
44 |
+
|
45 |
+
output_path = f"{VIDEO_DIRECTORY}/{filename}.{format}"
|
46 |
+
|
47 |
+
# restrict video length so one user doesn't take up all our bandwidth
|
48 |
+
def video_filter(info):
|
49 |
+
MAX_DURATION = 5*60
|
50 |
+
duration = info.get('duration')
|
51 |
+
if duration and duration > MAX_DURATION:
|
52 |
+
return f'The video is too long at {duration}s, choose a video less than {MAX_DURATION}s'
|
53 |
+
|
54 |
+
ydl_opts = {
|
55 |
+
'match_filter': video_filter,
|
56 |
+
'format': f'{format}/bestaudio',
|
57 |
+
'outtmpl': output_path,
|
58 |
+
}
|
59 |
+
|
60 |
+
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
61 |
+
error_code = ydl.download([url])
|
62 |
+
#info = ydl.extract_info(url, extra_info={"output_path": output_path})
|
63 |
+
|
64 |
+
if error_code:
|
65 |
+
raise Exception(f"Failed to download video, error code: {error_code}")
|
66 |
+
|
67 |
+
return output_path
|
68 |
+
|
69 |
+
def wrap_html(yt_url):
|
70 |
+
return f"""<h1><center>Original Video: <a href='{yt_url}'>{yt_url}</a></center><h1>"""
|
71 |
+
|
72 |
+
# ideally yt_url should be validated
|
73 |
+
def acapellify_url(yt_url, gr_request: gr.Request):
|
74 |
+
|
75 |
+
# example filename: https://www.youtube.com/watch?v=TasKo5HHWb4 -> TasKo5HHWb4
|
76 |
+
filename = yt_url.rsplit("=", 1)[-1]
|
77 |
+
|
78 |
+
video_path = download_yt_url(yt_url, filename)
|
79 |
+
|
80 |
+
new_video_path, new_audio_path = process_video(video_path)
|
81 |
+
return new_video_path, new_audio_path, wrap_html(yt_url)
|
82 |
+
|
83 |
+
|
84 |
+
def load_mystery_video(gr_request: gr.Request):
|
85 |
+
|
86 |
+
video_paths = list(Path(VIDEO_DIRECTORY).glob("*.mp4"))
|
87 |
+
|
88 |
+
selected_video = video_paths[random.randrange(len(video_paths))]
|
89 |
+
|
90 |
+
file_name = selected_video.name.replace("acap_", "").split(".", 1)[0]
|
91 |
+
selected_audio = Path(VIDEO_DIRECTORY) / f"{file_name}.m4a"
|
92 |
+
yt_url = f"https://www.youtube.com/watch?v={file_name}"
|
93 |
+
|
94 |
+
selected_video = str(selected_video)
|
95 |
+
selected_audio = str(selected_audio)
|
96 |
+
|
97 |
+
return selected_video, selected_audio, wrap_html(yt_url)
|
98 |
+
|
99 |
+
|
100 |
+
with open("header.md", "r") as markdown_file:
|
101 |
+
markdown_text = markdown_file.read()
|
102 |
+
|
103 |
+
with gr.Blocks(theme=theme) as demo:
|
104 |
+
|
105 |
+
with gr.Row():
|
106 |
+
header = gr.Markdown(markdown_text)
|
107 |
+
with gr.Row().style():
|
108 |
+
|
109 |
+
with gr.Column(scale=0.4, variant="panel"):
|
110 |
+
input_url = gr.Textbox(label="Youtube URL")
|
111 |
+
|
112 |
+
process_video_btn = gr.Button("Acapellify", variant="primary")
|
113 |
+
mystery_btn = gr.Button("Mysterious Video")
|
114 |
+
|
115 |
+
static_path_display = gr.HTML(label="Output File Paths", visible=True)
|
116 |
+
|
117 |
+
with gr.Column(scale=0.6, variant="panel"):
|
118 |
+
output_video = gr.Video(label="Acapellified Video")
|
119 |
+
output_audio = gr.Audio(label="Acapellified Audio")
|
120 |
+
|
121 |
+
process_video_btn.click(fn=acapellify_url, inputs=[input_url], outputs=[output_video, output_audio, static_path_display])
|
122 |
+
mystery_btn.click(fn=load_mystery_video, inputs=[], outputs=[output_video, output_audio, static_path_display])
|
123 |
+
|
124 |
+
if __name__ == "__main__":
|
125 |
+
os.makedirs(VIDEO_DIRECTORY, exist_ok=True)
|
126 |
+
demo.launch()
|
header.md
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# <p style="text-align: center;">Acapellify: Extract vocals from Youtube Video URL</p>
|
2 |
+
### <p style="text-align: center;">Based on https://gradio.app/fastapi-app-with-the-gradio-client/</p>
|
3 |
+
### <p style="text-align: center;">Backend uses gradio client linked to abidlabs/music-separation</p>
|
4 |
+
### <p style="text-align: center;">Free to Use (courtesy of HF Spaces)</p>
|
requirements.txt
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
gradio==3.28.3
|
2 |
+
yt-dlp
|
theme.py
ADDED
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import gradio as gr
|
3 |
+
from gradio.themes.utils import colors, fonts, sizes
|
4 |
+
|
5 |
+
from gradio.themes import Soft, Base
|
6 |
+
|
7 |
+
|
8 |
+
yfu_red = colors.Color(
|
9 |
+
name="yfu_red",
|
10 |
+
c50="#ff2c65",
|
11 |
+
c100="#fee2e2",
|
12 |
+
c200="#fecaca",
|
13 |
+
c300="#fca5a5",
|
14 |
+
c400="#fca5a5",
|
15 |
+
c500="#ff2c65",
|
16 |
+
c600="#ff2c65",
|
17 |
+
c700="#ff2c65",
|
18 |
+
c800="#ff2c65",
|
19 |
+
c900="#ff2c65",
|
20 |
+
c950="#fee2e2",
|
21 |
+
)
|
22 |
+
|
23 |
+
white = colors.Color(
|
24 |
+
name="white",
|
25 |
+
c50="#ff2c65",
|
26 |
+
c100="#ff2c65", # text color
|
27 |
+
c200="#ff2c65",
|
28 |
+
c300="#ff2c65",
|
29 |
+
c400="#ff2c65",
|
30 |
+
c500="#ff2c65", # secondary text color
|
31 |
+
c600="#ffffff",
|
32 |
+
c700="#ffffff",
|
33 |
+
c800="#ffffff",
|
34 |
+
c900="#ffffff",
|
35 |
+
c950="#ffffff",
|
36 |
+
)
|
37 |
+
|
38 |
+
theme = gr.themes.Soft(
|
39 |
+
primary_hue=yfu_red,
|
40 |
+
secondary_hue=yfu_red,
|
41 |
+
neutral_hue=white,
|
42 |
+
).set(
|
43 |
+
body_background_fill="white",
|
44 |
+
panel_background_fill="white",
|
45 |
+
|
46 |
+
background_fill_primary="white",
|
47 |
+
background_fill_primary_dark="white",
|
48 |
+
|
49 |
+
panel_background_fill_dark="white",
|
50 |
+
panel_border_color="*primary_50",
|
51 |
+
panel_border_color_dark="*primary_50",
|
52 |
+
|
53 |
+
body_text_color="*primary_50",
|
54 |
+
body_text_color_dark="*primary_50",
|
55 |
+
body_text_color_subdued="*primary_50",
|
56 |
+
body_text_color_subdued_dark="*primary_50",
|
57 |
+
|
58 |
+
table_even_background_fill="white",
|
59 |
+
table_even_background_fill_dark="white",
|
60 |
+
table_odd_background_fill="*primary_100",
|
61 |
+
table_odd_background_fill_dark="*primary_100",
|
62 |
+
table_row_focus="*primary_200",
|
63 |
+
table_row_focus_dark="*primary_200",
|
64 |
+
|
65 |
+
input_background_fill_focus="*primary_200",
|
66 |
+
input_background_fill_focus_dark="*primary_200",
|
67 |
+
|
68 |
+
checkbox_background_color_focus="*primary_200",
|
69 |
+
checkbox_background_color_focus_dark="*primary_200",
|
70 |
+
|
71 |
+
border_color_primary_dark="*primary_50",
|
72 |
+
checkbox_border_color_dark="*primary_50",
|
73 |
+
table_border_color_dark="*primary_50",
|
74 |
+
button_secondary_text_color="*primary_50",
|
75 |
+
block_info_text_color="*primary_50",
|
76 |
+
block_label_text_color="*primary_50",
|
77 |
+
block_title_text_color="*primary_50",
|
78 |
+
checkbox_label_text_color="*primary_50",
|
79 |
+
checkbox_label_text_color_selected="*primary_50",
|
80 |
+
button_cancel_text_color="white",
|
81 |
+
block_border_color="*primary_50",
|
82 |
+
|
83 |
+
#button_secondary_background_fill=None,
|
84 |
+
#button_secondary_background_fill_dark=None,
|
85 |
+
button_secondary_background_fill_hover="*primary_200",
|
86 |
+
button_secondary_background_fill_hover_dark="*primary_200",
|
87 |
+
|
88 |
+
stat_background_fill="*primary_200",
|
89 |
+
stat_background_fill_dark="*primary_200",
|
90 |
+
|
91 |
+
)
|