Skip to content

Instantly share code, notes, and snippets.

@brouberol
Created May 1, 2024 12:12
Show Gist options
  • Save brouberol/afdd5e947f835fdc06ee4c91e79c8f92 to your computer and use it in GitHub Desktop.
Save brouberol/afdd5e947f835fdc06ee4c91e79c8f92 to your computer and use it in GitHub Desktop.
tabletopaudio_dl.py
#!/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