diff options
Diffstat (limited to 'grabnsplit-project')
| -rw-r--r-- | grabnsplit-project/pyproject.toml | 23 | ||||
| -rw-r--r-- | grabnsplit-project/src/grabnsplit.py | 99 |
2 files changed, 122 insertions, 0 deletions
diff --git a/grabnsplit-project/pyproject.toml b/grabnsplit-project/pyproject.toml new file mode 100644 index 0000000..5ae4b65 --- /dev/null +++ b/grabnsplit-project/pyproject.toml @@ -0,0 +1,23 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "grabnsplit" +version = "1.0" +authors = [ + { name = "Eddy Pedroni", email = "epedroni@pm.me" }, +] +description = "A CLI tool for grabbing songs from youtube and splitting them into stems" +requires-python = ">=3.13" +dependencies = [ + "click", + "python-ffmpeg", + "demucs", + "yt-dlp", + "numpy", + "requests" +] + +[project.scripts] +grabnsplit = "grabnsplit:main" diff --git a/grabnsplit-project/src/grabnsplit.py b/grabnsplit-project/src/grabnsplit.py new file mode 100644 index 0000000..c4409dc --- /dev/null +++ b/grabnsplit-project/src/grabnsplit.py @@ -0,0 +1,99 @@ +import click +from pathlib import Path + +SAMPLE_RATE = "48000" +FB_URL = "https://files.0xf7.com" + +def download(yt_url: str, destination: Path) -> None: + import yt_dlp + + options = { + "format": "bestaudio/best", + "outtmpl": str(destination), + "playlist_items": "1", + "postprocessors": [ + { + "key": "FFmpegExtractAudio", + "preferredcodec": "wav" + } + ], + "postprocessor_args": [ + "-ar", SAMPLE_RATE, + ], + } + + with yt_dlp.YoutubeDL(options) as downloader: + downloader.download([yt_url]) + +def split(original: Path, destination: Path) -> None: + from demucs.api import Separator + from demucs.audio import save_audio + + separator = Separator(model="htdemucs_6s") + + origin, separated = separator.separate_audio_file(original) + + for stem_name, audio in separated.items(): + output_file = destination / f"{stem_name}.wav" + save_audio( + audio, + str(output_file), + samplerate=separator.samplerate, + ) + +def combine(destination: Path, name: str) -> None: + from ffmpeg import FFmpeg + + inputs = [] + options = { + "map": [], + "c:a": "libmp3lame", + "b:a": "320k", + "ar": SAMPLE_RATE + } + + stems = [ + ("vocals.wav", "Vocals"), + ("guitar.wav", "Guitars"), + ("piano.wav", "Piano"), + ("bass.wav", "Bass"), + ("drums.wav", "Drums"), + ("other.wav", "Other"), + ] + + ffmpeg = FFmpeg().option("y") + + for i, stem in enumerate(stems): + #inputs.append(ffmpeg.input(str(destination / stem[0]))) + ffmpeg.input(str(destination / stem[0])) + options["map"].append(f"{i}:a") + options[f"metadata:s:a:{i}"] = f"title={stem[1]}" + + ffmpeg.output(str(destination / f"{name}.mka"), options) + ffmpeg.execute() + +def upload(file_path: Path) -> None: + from requests import post + from os import getenv + + api_key = post(f"{FB_URL}/api/login", json={"username":getenv("ST_USER"), "password":getenv("ST_PASS")}).content + url = f"{FB_URL}/api/resources/songs/{file_path.name}" + with open(file_path, "rb") as file: + post(url, headers={"X-Auth" : api_key}, data=file).raise_for_status() + +@click.command() +@click.argument("url", required=True) +@click.argument("output_file", required=True) +@click.option("--work_dir", type=click.Path()) +def main(url: str, output_file: str, work_dir: Path): + from tempfile import TemporaryDirectory + with TemporaryDirectory(prefix="grabnsplit") as temp: + dir = Path(work_dir if work_dir is not None else temp) + + original = dir / "original.wav" + + download(url, original.parent / original.stem) + split(original, dir) + combine(dir, output_file) + upload(dir / f"{output_file}.mka") + |
