Using TSV data with Jekyll

I recently added a new page to my website: /playlist. I have been kicking the idea around for some time, unsure of the best path forward for both archival reasons and ease of access (actually listening to the music). A custom web component that you feed an M3U playlist that does all the web audio streaming magic? I decided to tackle the most pressing problem first - data retrieval and archival. In other words: how do we get the song data, and how can we display it? Thankfully, Apple Music has some built-in methods for exporting a playlist! It can do XML, but it ignores the track ordering as far as I could tell. It claims to be able to do an M3U, but that resulted in an empty file for me. Finally, plain text to the rescue! Except… it’s not plain text, it’s a hidden tab-separated value (TSV1) file! Does that count as plain text?

Playlist.tsv </>
Name	Artist	Composer	Album	Grouping	Work	Movement Number	Movement Count	Movement Name	Genre	Size	Time	Disc Number	Disc Count	Track Number	Track Count	Year	Date Modified	Date Added	Bit Rate	Sample Rate	Volume Adjustment	Kind	Equalizer	Comments	Plays	Last Played	Skips	Last Skipped	My Rating	Location
All Your Hiding	Moonrisers	Elizabeth Ann Nowicki	Harsh & Exciting						Instrumental	7126978	172	1	1	3	10		4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			1	4/30/25, 12:13 PM			
So Hot You're Hurting My Feelings	Squirrel Flower		Take It or Leave It / So Hot You're Hurting My Feelings - Single						Alternative	7475706	209	1	1	2	2	2020	12/1/23, 2:16 PM		256	44100		Apple Music AAC audio file			1	4/30/25, 12:17 PM			
Here's Your Song	Fatal Flowers	Fatal Flowers	Fatal Flowers / Younger Days						Pop	6362459	176	1	1	16	16	1986	4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			1	4/30/25, 12:20 PM			
Barbarian Kings	Morgan Delt		Morgan Delt						Indie Pop	8453329	233	1	1	2	11	2014	4/24/25, 6:29 PM		256	44100		Apple Music AAC audio file			3	4/30/25, 12:23 PM			
Should've Been a Cowboy	Toby Keith	Toby Keith	Should've Been a Cowboy (25th Anniversary Edition)						Country	8108461	209	1	1	1	13	1993	4/26/25, 3:22 PM	4/26/25, 3:22 PM	256	44100		Apple Music AAC audio file			4	4/30/25, 12:27 PM			
A Horse with No Name	America	Dewey Bunnell	Made in America						Rock	9612543	252	1	1	1	22	1971	4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			2	4/30/25, 12:31 PM			
On the Road	Rattlesnake Milk	SEAN LEWIS	Chicken Fried Snake						Americana	8035663	219	1	1	1	9	2022	4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			1	4/30/25, 12:35 PM			
Bei Mir Bist Du Schoen	Charley Crockett	Sholom Secunda	A Stolen Jewel						Blues	6146267	166	1	1	13	13	2015	4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			1	4/30/25, 12:38 PM			
Hi Dee Dee	Ty Segall	Ty Segall	Three Bells						Alternative	6918311	191	1	1	4	15	2024	4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			1	4/30/25, 12:41 PM			
Her Eyes Are a Blue Million Miles	Captain Beefheart & His Magic Band	Captain Beefheart	Clear Spot						Rock	6281232	177	1	1	10	12	1972	4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			1	4/30/25, 12:44 PM			
King Of The Road	Roger Miller	Roger Miller	Golden Hits						Country	5339317	147	1	1	1	11	1965	4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			1	4/30/25, 12:46 PM			
Touch Me I'm Sick	Mudhoney	Mudhoney, Steve Turner, Dan Peters, Mark McLaughlin & Matt Lukin	Superfuzz Bigmuff (Deluxe Edition)						Alternative	5568110	152	1	2	1	17	1988	4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			1	4/30/25, 12:49 PM			
Thick As a Brick (Edit No. 1)	Jethro Tull	Ian Anderson	The Best of Acoustic Jethro Tull (Remastered)						Rock	7427506	182	1	1	6	24	2001	4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			1	4/30/25, 12:52 PM			
Steam Powered Aereo Plane	John Hartford	John Hartford	Aereo-Plain						Singer/Songwriter	8838764	223	1	1	11	16	1971	4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			1	4/30/25, 12:56 PM			
The Landkeeper	Men I Trust	Jessy Caron, Emmanuelle Proulx & Dragos Chiriac	Equus Asinus						Folk	8179625	224	1	1	6	14	2025	4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			1	4/30/25, 1:04 PM			
Con Te Partiro	Andrea Bocelli	Francesco Sartori	Bocelli						Pop	8895872	250	1	1	1	10	1995	4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			1	4/30/25, 1:08 PM			
Pyramid of Health	Viagra Boys	Sebastian Murphy, Henrik Höckert, Tor Sjödén, Elias Jungqvist, Oskar Carls, Linus Hillborg & Pelle Gunnerfeldt	viagr aboys						Punk	7095223	195	1	1	4	11	2025	4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			1	4/30/25, 1:12 PM			
Thruck Diebes	YYARD	Yannis Anft	Pattern Gardening						House	14223591	389	1	1	9	22	2025	4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			1	4/30/25, 1:18 PM			
So Hot You're Hurting My Feelings	Caroline Polachek	Caroline Polachek, Daniel Nigro & Teddy Geiger	Pang						Indie Pop	6656021	183	1	1	12		2019	4/30/25, 8:52 AM		256	44100		Apple Music AAC audio file			1	4/30/25, 1:21 PM			

It turns out, Jekyll has the native ability to work with TSV files2. Nice! There’s even a pretty nice blog post to show you how to get started (with a CSV at least)3. However, in Jekyll, so-called “Data Files” are unique from regular posts. They’re just raw YAML, JSON, CSV, or TSV data. You are expected to import and use the data on individual pages. The data files are not rendered to anything directly4. A good example of (another) place I make use of data files is my /about page, which is just a bunch of tables organized by a single .yml file5.

There is a similar construct in Jekyll called the collection6. However, a collection is more like a fancy category of pages. In fact, the default _posts and _drafts directories are actually just collections under the hood. So a custom collection allows you to have a custom directory, such as _playlists. A collection, like a page, is designed to be rendered to an HTML file.

Possibly the most straightforward approach would be to write some kind of TSV to YAML converter and store all the song and playlist data in the collection item itself. But that just sounded bad to me, not very fun. Plus, we have super clean and high-quality TSV data from Apple, and Jekyll can read them natively, and I really just did not want to deal with it. I think of the TSV files as the gold source of truth and leaving those clean and as-is will be useful down the road.

Leaving the TSV’s clean and as-is meant that I also couldn’t easily add my own metadata. Maybe some playlists I want to include a little note for? I also want to include the Apple Music and Spotify playlist IDs for easy linking. I also need something for Jekyll to render.

What I settled on was a joint approach. The easiest way to illustrate is to examine the file structure:

catskull.net/ </>
_data/playlists
└── 2025
    ├── 7.tsv
    ├── 8.tsv
    └── 9.tsv
_playlists
└── 2025
    ├── 7.md
    ├── 8.md
    └── 9.md

Each TSV playlist file has an accompanying playlist markdown file inside the _playlists collection. As we discussed before, the TSV files contain the data directly from Apple Music as-is, which Jekyll will helpfully process into a hash for us. The markdown file contains optional front-matter for my own metadata such as the notes and IDs I mentioned before. An example markdown file with front matter might look like:

_playlists/2025/9.md </>
---
notes: A note about the playlist.
apple: 2025-week-17/pl.u-gxblYeJs3lYDvk
spotify: 6uz3rlHSLGW8R4TVO2o8T8
---

However, most playlist markdown files are empty since the front matter is optional and I’m too lazy to go take the time to go fill it out for all the old playlists.

Note that the only thing that “links” a playlist markdown file to a playlist TSV file is the naming convention. There may have been a way to link to the specific playlist with a variable in front matter, but it for sure would require me to at least type a few extra characters and I’m literally too lazy. So a nice naming and file structure convention fits the bill quite nicely for me!

Inside of my liquid template, I can find the correct data for the current markdown file by parsing out some of the current file’s information:

_layouts/playlist.html </>
<!-- page.path: "_playlists/2025/17.md" -->
{% assign id = page.path | split: "/" %}
{% assign year = id[1] %}
{% assign week = id[2] | split: "." | first %}
{% assign songs = site.data.playlists[year][week] %}

At this point, songs is an array of hashes with all our glorious song data!

I went an extra step and made my playlist UI it’s own Jekyll include. See my previous post about Jekyll includes as re-usable components7 for more information on motivation and implementation details there. The full source of my playlist “component” (and everything else on this site) is available on GitHub8.

I wanted to have a dedicated /playlist page in addition to the usual index. In fact, that’s the primary interface into the data. You can dig deeper into the “archive”, but I wanted my fancy playlist partial to render in both places cleanly. Because I’d done the work to break out the playlist into a partial, it’s as simple as grabbing a few data points. I had to do some tricky logic to sort all my posts by year and get the most recent one, but I think it’s working.

playlist_archive.md </>
<!-- get the most recent playlist -->
{% assign year = site.playlists | group_by_exp: "playlist", "playlist.path | split: '/' | slice: 1, 1 | first" | last %}
{% assign playlists = year.items | sort: "slug" %}
{% assign playlist = playlists.last %}
{% assign id = playlist.path | split: "/" %}
{% assign year = id[1] %}
{% assign week = id[2] | split: "." | first %}
{% assign songs = site.data.playlists[year][week] %}

Once I have a reference to the collection item (either the current page, or the playlist variable above), I can simply render my partial:

Use anywhere! </>
{% include playlist.html apple=playlist.apple spotify=playlist.spotify songs=songs year=year week=week notes=playlist.notes %}

Adding new playlists consists of exporting the TSV from Apple Music, and then touching the markdown file (and adding notes manually). I’d really like to get rid of that last step, needing the markdown file at all - but like I outlined above, I want to keep the Apple Music TSV “pristine” and keep my personal metadata in it’s own place.

The two-pronged approach to using raw Jekyll data files, in conjunction with a Jekyll collection are what make this work. Following the same naming convention for both file types allows me to programmatically generate pages for each playlist data file. I’m sure there will be future iterations here, but for now I’m pretty happy to finally scratch this one off the to-do list!