A Quick and Dirty Plex Test Server

I can build a Plex server with 100 days of content in about 10 minutes

I've been working on Chronolists recently, hoping to make it more accurate at matching media and adding support for media libraries in other languages. The way I did that was to start matching based on the Plex GUID, which is a standard ID for any piece of media, rather than the title, which falls over if the title doesn't match the one on TMDB. I pushed the update the other day and it worked well. Actually fixed some issues I was having with Harry Potter ("Philosophers' Stone" vs "Sorcerer's Stone").

A day or so later I got an email from someone who had been using the site and it suddenly stopped working, but only for older stuff. Turns out older libraries may still be using an older format of GUID that can be an ID from either TVDB, TMDB, IMDB or a few other sources. By replacing the title match with a ID match I'd broken support for all these libraries, and I had no idea because I don't have an infinite number of Plex servers to test with.

How I got an infinite number of Plex servers

I run Plex in docker on my home server, so I figured it would make sense to do the same for my test server. I used the excellent Plex image from linuxserver.io and threw a compose file together:

// @file: yml
--- version: "2.1" services: plex: image: lscr.io/linuxserver/plex:latest container_name: chronolists_plex network_mode: host environment: - PUID=1000 - PGID=1000 volumes: - ./library:/library

I'm not mounting the config directory because I don't care about it, I can reset it in a few minutes with my testing account. Technically not "infinite", but it's close enough.

Generating 3.5k video files

But how am I supposed to provide all the media so that I can actually test the matching? The playlists on Chronolists total about 100 days now, that would be a bit big just for testing. I tried symlinking a few fake movies to a sample MP4 file, but Plex seems to prevent dupes. So I'd need each file to be an actual file then. Great! What's the smallest size an MP4 video can be? This very useful repo of the smallest possible valid file types has the answer and it turns out it's pretty small. A svelte 262 bytes!

With my new adorable video file I could just use the stub, and Plex thinks I've got the whole show or movie. Perfect.

An empty Plex server isn't much use though, and I don't fancy copying the files for every single show and movie. Not to mention having to keep it updated with new episodes in the future. Luckily I've got a big pile of JSON with all the media data, so I can just use that and a quick node script!

// @file: js
const fs = require("fs"); const path = require("path"); // Pass in a number between 0 and 1 to simulate a partial library. Not perfect, but it works. let [threshold] = process.argv.slice(2); threshold = parseFloat(threshold); threshold = isNaN(threshold) ? 1 : threshold; // Delete existing stubs. if (fs.existsSync(path.join('library', 'tv'))) { fs.rmSync(path.join('library', 'tv'), { recursive: true }); } if (fs.existsSync(path.join('library', 'movies'))) { fs.rmSync(path.join('library', 'movies'), { recursive: true }); } // Loop all data files, create required files. const dataFiles = fs.readdirSync('../data/json'); dataFiles.forEach((dataFilePath) => { const testData = JSON.parse(fs.readFileSync(path.join('../data/json', dataFilePath))); // Generate Stubs. testData.forEach((item) => { // Threshold check. if (threshold < Math.random()) { return; } // Use recommended Plex naming to guarantee it finds the right info. let directory, fileName = ''; if (item.type === 'tv') { directory = path.join('library', 'tv', item.name, `Season ${item.season}`); fileName = `${item.name} - S${item.season.padStart(2, '0')}E${item.episode.padStart(2, '0')}.mp4`; } else { directory = path.join('library', 'movies', item.name); fileName = `${item.name}.mp4`; } fs.mkdirSync(directory, { recursive: true }); fs.copyFileSync('./stub.mp4', path.join(directory, fileName)); }); });

All in all this creates about 3,500 video files, totalling ~18Mb. Perfectly manageable for test data, especially since it doesn't need to sit in the repo, I can generate it in a couple of seconds.

Hopefully this should make my life easier reproducing issues in the future, and it might even stop me pushing buggy code at 1am (it won't, I've literally just pushed another change).