Created
May 1, 2024 12:12
-
-
Save brouberol/afdd5e947f835fdc06ee4c91e79c8f92 to your computer and use it in GitHub Desktop.
tabletopaudio_dl.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import requests | |
import argparse | |
import re | |
import json | |
from pathlib import Path | |
from bs4 import BeautifulSoup | |
from dataclasses import dataclass | |
BASE_URL = "https://tabletopaudio.com" | |
SOUND_BASE_URL = "https://sounds.tabletopaudio.com" | |
PICO_MIXER_ROOT_DIR = Path.home() / "code" / "pico-mixer" | |
PICO_MIXER_CONFIG_PATH = PICO_MIXER_ROOT_DIR / "config.json" | |
PICO_MIXER_SOUNDS_DIR = PICO_MIXER_ROOT_DIR / "pico_mixer_web" / "assets" / "sounds" | |
@dataclass | |
class Track: | |
filename: str | |
url: str | |
tags: list[str] | |
def parse_args() -> argparse.Namespace: | |
parser = argparse.ArgumentParser( | |
description=f"Download tracks from {BASE_URL} by name" | |
) | |
parser.add_argument( | |
"--tracks", | |
nargs="+", | |
help=f"The name of the tracks to download, as displayed on {BASE_URL}", | |
required=True, | |
) | |
return parser.parse_args() | |
def download_file(url: str, local_filename: Path): | |
with requests.get(url, stream=True) as r: | |
r.raise_for_status() | |
with open(local_filename, "wb") as f: | |
for chunk in r.iter_content(chunk_size=8192): | |
f.write(chunk) | |
def parse_homepage() -> BeautifulSoup: | |
html = requests.get(BASE_URL).text | |
return BeautifulSoup(html, features="html.parser") | |
def sanitize_track_filename(filename: str) -> str: | |
filename = re.sub(r"^\d+", "", filename) | |
return filename.replace("_", " ").strip() | |
def parse_track_details(soup: BeautifulSoup, track_name: str) -> str: | |
track_title_divs = soup.find_all("div", class_="track_title") | |
for track_title_div in track_title_divs: | |
h3 = track_title_div.find("h3") | |
if not h3: | |
continue | |
if h3.text == track_name: | |
break | |
else: | |
msg = f"Track {track_name} not found" | |
raise ValueError(msg) | |
track_download_link = track_title_div.find_next("span", class_="saveButton").find( | |
"a" | |
) | |
track_name = re.search( | |
r"saveAs\('(\w+)'\)", track_download_link.attrs["onclick"] | |
).group(1) | |
track_filename = sanitize_track_filename(f"{track_name}.mp3") | |
track_url = f"{SOUND_BASE_URL}/{track_name}.mp3" | |
tags_div = track_title_div.find_previous("div") | |
tags = [tag for tag in tags_div.attrs["class"] if tag != "col-md-3"] | |
return Track(filename=track_filename, url=track_url, tags=tags) | |
def download_track(track_details: Track): | |
local_track_file_path = PICO_MIXER_SOUNDS_DIR / Path(track_details.filename) | |
if local_track_file_path.exists(): | |
print(f"{local_track_file_path} already exists. Skipping download") | |
else: | |
print(f"Downloading track {track_details.filename}") | |
download_file(track_details.url, local_track_file_path) | |
def add_track_to_pico_mixer_config_file(track_details: Track): | |
config = json.load(open(PICO_MIXER_CONFIG_PATH)) | |
for entry in config: | |
if entry["title"] == track_details.filename: | |
break | |
else: | |
print(f"Registering track {track_details.filename} to pico mixer config") | |
config.append({"title": track_details.filename, "tags": track_details.tags}) | |
with open(PICO_MIXER_CONFIG_PATH, "w") as out: | |
json.dump(config, out, indent=2) | |
def main(): | |
args = parse_args() | |
soup = parse_homepage() | |
for track in args.tracks: | |
track_details = parse_track_details(soup, track.strip()) | |
download_track(track_details) | |
add_track_to_pico_mixer_config_file(track_details) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment