Source code for my personal website at davidfuchs.ca.
A Hugo-based static site featuring:
Uses the m10c theme for a clean, minimal design.
The music page (/music/) is a ranked list of songs, rendered by
src/layouts/page/music.html from src/data/music.yaml.
src/data/music.yaml has a single songs: key holding a list. Rank is the
order of the list - there is no rank field, so reordering the entries
reorders the page. Each song looks like this:
- song_name: "Cirice"
artist: "Ghost"
featured_artists: [] # list, may be empty
album: "Meliora (Deluxe Edition)"
album_artist: "Ghost"
release_date: "2015-05-31" # ORIGINAL release date of the song (see below); may be "YYYY" or "YYYY-MM"
genre: "Metal" # one of the 12 genres below; used for the page filter
duration_sec: 362
version: original # original | remaster | live | acoustic | remix | edit | demo | instrumental | cover | other
explicit: false
isrc: null # a string like "USUM71234567" when a match was found
apple_music_id: "1440946621" # iTunes/Apple Music track id (kept as a string)
apple_music_url: "https://music.apple.com/ca/album/cirice/1440946435?i=1440946621"
artwork_url: "https://is1-ssl.mzstatic.com/image/thumb/Music114/v4/0d/be/08/0dbe08cb-a9bc-1783-b346-86eb51648f43/00888072015258.rgb.jpg/300x300bb.jpg"
Notes:
apple_music_id is the key for everything else: it builds the "Apple Music"
link and is how previews are fetched. isrc identifies the recording across
other services (Spotify, Deezer, etc.) if we ever need to match them.release_date is the original release. It is the earlier of Apple's date
and MusicBrainz's first-release date for the recording, so a 1983 song that
was later put on a 1991 album shows 1983. MusicBrainz sometimes only knows a
year or month, which is why some dates are "1983" or "1983-05".artist is the first-credited act; anyone else on the track is in
featured_artists. Real bands whose names contain "&" or "and" (Simon &
Garfunkel, Earth, Wind & Fire, ...) are kept whole. For classical pieces the
ensemble or lead performer is the artist and the conductor and soloists are
featured.version is inferred from the title and album text, so it is a best
guess. "Remastered ..." is stripped from song_name because the version
already says so; "(Radio Edit)" and similar are kept in the title.genre is one of: Metal, Hard Rock, Rock, Alternative, Pop, R&B/Soul,
Hip-Hop, Country, Electronic/Dance, Classical, Soundtrack, Folk. They are
mapped from Apple's 38 genres, an artist you tagged by hand in the old
music.yaml keeps that tag, and an artist with several songs gets the
majority genre so their songs filter together./gb/ link).isrc is null on about 50 songs where neither Deezer nor MusicBrainz had a
recording of the same length (mostly radio edits, soundtrack and Ghost tracks).apple_music_id in batches of 150
(resources.GetRemote, cached by Hugo), builds an id -> previewUrl map,
and renders a play button for each song that has one. IDs missing from the
Canadian storefront are retried against the UK one. If a lookup fails the
page still builds, just without those buttons (a warning is printed). Preview
URLs live on Apple's CDN and can change, which is why they are resolved at
build time instead of being stored.<audio> element that is only given a source
when a button is pressed, so nothing is downloaded on page load, and starting
a preview stops the previous one.loading="lazy", 100px thumbnails
derived from the stored 300px URL), and rows use CSS
content-visibility: auto so off-screen rows are skipped. The page is fine
at under about 1,500 songs; far beyond ~5,000 it would be worth paginating.music.yaml (first 500 at the top of music.html). The rest stay in the
file but generate no HTML, genre chip counts or preview lookups, so moving a
song above or below #500 adds or removes it from the page.?genre=<slug>; a small script hides
non-matching rows. Without JavaScript the full ranked list still shows and
the Apple Music links still work.src/assets/css/_extra.scss (.song-* classes).The list was generated once from my Apple Music library playlists; it is not regenerated by the build:
fill_isrc.py) added MusicBrainz and looser title matching.tools/music_scratch/playlists/) were merged into
music.yaml, de-duplicating by apple_music_id and ISRC (the first
occurrence wins), with Favourite Songs first.tools/music_scratch/): songs on
compilations were re-pointed at their original albums
(repoint_albums.py), titles/collaborations/versions/genres were
normalised (tidy.py), and release dates were replaced with the original
release date from MusicBrainz (original_dates.py).The tooling for the steps above lives in tools/music_scratch/. Like
tools/list-editor, it is outside src/, so it is never part of the Hugo
build or the Docker image (Hugo would also load anything under src/data/
as site data, which is why it does not live there).
tools/music_scratch/
scrape.js console snippet that reads a library playlist page
raw/ scraped output, one <playlist>.json per playlist
build.py raw/ -> playlists/*.yaml via the iTunes Lookup/Search API
enrich.py fills in ISRCs from Deezer
fill_isrc.py second pass for songs still missing one (Deezer + MusicBrainz)
repoint_albums.py moves compilation tracks to the original album (iTunes)
tidy.py normalises titles, collaborations, versions and genres
original_dates.py original release dates from MusicBrainz
playlists/ per-playlist YAML in playlist order, plus unmatched.yaml
music.yaml is now the source of truth: it has been cleaned up by the scripts
above, had a few songs added from other storefronts, and is the file you will
be ranking by hand. playlists/ is the pre-cleanup snapshot, so do not
merge it over music.yaml wholesale. To pick up new music:
scrape.js
and save the result as raw/<playlist-slug>.json (a new playlist just
needs a new file; its id is linked from the library sidebar).cd tools/music_scratch && python3 build.py regenerates playlists/ and
unmatched.yaml, then python3 enrich.py fills ISRCs. Both need network
access and only use the Python standard library. API responses are cached
in cache.json / isrc_cache.json (git-ignored), so reruns are quick.playlists/ against src/data/music.yaml by apple_music_id
(or isrc), copy over the entries that are new, and place them where you
want them in the ranking. Then run the cleanup scripts so the new entries
follow the same conventions: python3 repoint_albums.py, review
repoint_candidates.json and apply with --apply, then python3 tidy.py --apply, then python3 original_dates.py followed by --apply. The
scripts only touch entries that still need work, so they are safe to re-run. Check unmatched.yaml for songs the Canadian
storefront could not match, and look for them under another storefront
(the track lists from an artist lookup, lookup?id=<artistId>&entity=song,
find things the name search misses).tools/list-editor still expects the old schema (artists: with a
genres: array) and cannot edit the new songs list yet. Reorder
music.yaml by hand until it is updated.Content type
Image
Digest
sha256:061f6679d…
Size
132.5 MB
Last updated
8 days ago
docker pull davidfuchs/davidfuchs-ca