# TitleCardMaker: title cards for Plex, Jellyfin and Emby, packaged as a container

> TitleCardMaker is a small web application that generates title cards for the three big media servers, and it ships the way most self-hosted tools now do: a Docker image, a documented port, a health check and a Raspberry Pi build. The ImageMagick policy file in the image is the detail that tells you how the project handles its own security constraints.

**TitleCardMaker/TitleCardMaker** — Automated title card maker for Plex, Jellyfin, and Emby

- Repository: https://github.com/TitleCardMaker/TitleCardMaker
- Website: https://titlecardmaker.com/
- Stars: 863 · Forks: 30
- Language: Python
- License: GPL-3.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/titlecardmaker-titlecardmaker

## A narrow job for the three media servers that dominate self-hosting

TitleCardMaker is described in one line as an automated title card maker for the Plex, Jellyfin and Emby media servers, and the repository description repeats it as an automated title card maker for Plex, Jellyfin and Emby.

That is a deliberately small product, and picking the three servers is the whole scope decision. Plex, Jellyfin and Emby are the media servers that a self-hosted household is likely to run, and each of them can pull artwork and metadata for a library from various sources, with a title card being the one asset that has no automatic source. So the tool fills a specific gap rather than trying to be a general asset manager.

The project is written in Python, licensed under GPL-3.0, and developed on the main branch. The last push was on 2026-08-31, and the releases listed are v2.16.1 on 2026-08-25, v1.16.1 on 2026-08-10 and v1.16.0 on 2024-06-03.

The documentation lives on its own site, titlecardmaker.com, and the README says the in-depth documentation on how to use and set it up is there. The README itself is short: a description, a montage of the card types that can be created, a contributing section and a support section. The examples are shown as a single image in the docs directory rather than as prose, which tells you the card styles are the selling point and the configuration is where the documentation earns its keep.

The project is also distributed as a Docker image on Docker Hub under the titlecardmaker namespace, with a pull count badge in the README, and there is a Discord server and a funding page for the maintainer.

## The container is the installation method

The Dockerfile in the repository root is the real user documentation, because the Docker Hub image is how the project is meant to be run.

It is a two-stage build on python:3.13-slim, and the first stage is about making the dependency install reproducible. The dependency files are copied first, for better layer caching, and the dependencies are resolved with uv rather than being taken from a hand-written list:

```dockerfile
FROM python:3.13-slim AS builder

# Copy dependency files first for better layer caching
COPY backend/pyproject.toml backend/pyproject.lock* ./
```

The build step installs the compiler and setuptools and uv, then runs uv pip compile on the project file to produce a requirements file and installs from it. The runtime stage is a fresh python:3.13-slim with the compiled site-packages copied across, the OCI metadata labels attached, the working directory set to /tcm and the whole project copied in.

The runtime stage is where the operational details live:

```dockerfile
    # Create non-root user
    set -eux && \
    groupadd -g 314 titlecardmaker && \
    useradd -u 314 -g 314 -m -s /bin/bash titlecardmaker && \
    # Install imagemagick and curl (for healthcheck)
    apt-get update && \
    apt-get install -y \
        curl imagemagick && \
    cp backend/assets/policy.xml /etc/ImageMagick-7/policy.xml && \
```

So the container runs as a dedicated non-root user with a fixed uid and gid of 314, ImageMagick and curl are installed, and curl is there for a health check rather than for the application itself. The port is exposed as:

```dockerfile
EXPOSE 4242
```

A fixed uid matters more than it looks in a media server setup, because the container often has to read a library directory that the host service also writes to, and a fixed id can be matched on the host side.

There is also a Dockerfile.armv7 in the repository root, which is the build to use on a Raspberry Pi, and the project takes contributions through GitHub issues with the Discord as the informal channel.

## The ImageMagick policy file is the tell for how the project thinks about its own permissions

One line in the Dockerfile deserves more attention than the rest, and that is the copy of a policy file over the ImageMagick configuration:

```dockerfile
    cp backend/assets/policy.xml /etc/ImageMagick-7/policy.xml && \
```

ImageMagick ships with a policy file that restricts what it is allowed to do, and in a container that policy is deliberately restrictive by default. The restrictions exist because ImageMagick has a long history of being used as a file read primitive by web applications, so the shipped policy disables a set of formats and capabilities including some file and network operations. The result in a container is that a perfectly normal image conversion can fail with a policy error that looks like a bug in the application rather than a deliberate restriction.

Shipping your own policy.xml, taken from the backend's assets directory, means the project has decided exactly which capabilities it needs, and ships that decision with the code. That is a better answer than loosening the policy on the host at deploy time, because the configuration travels with the image and is reviewable in the repository.

It is also a good sign about the project's posture. The container is not running as root, the user is a fixed non-root account, and the image tool's own restrictions are replaced by a curated file rather than disabled wholesale. For a service that will be pointed at a media library, that combination is the right set of defaults.

The remaining runtime hygiene is in the same style. The apt cache and the temporary directories are removed in the same layer, and the second copy of the site-packages directory is repeated after the runtime dependencies are installed, which is how the built packages survive the layer that also changes the site-packages directory.

## A web application with a separate documentation site

The repository layout shows a normal web application split in two, plus its documentation.

There is a backend/ directory and a frontend/ directory, which matches the Dockerfile copying the whole project into /tcm and running the Python service behind a web interface. A tests/ directory sits at the top level, and the mkdocs.yml at the root is the configuration for the documentation site, with the docs/ directory holding the sources.

The documentation is a MkDocs site built from that directory, and the links in the README point at it through the titlecardmaker.com domain, which means the published documentation and the documentation in the repository are the same content with a domain in front. The card type montage also comes from the docs directory, which is where the images for the example styles live.

One detail in the README is a small sign of how the project is organised: the logo and the example images are referenced through the refs/heads/paths of specific branches, one named web-ui and one named docs, rather than through main. In other words, the web interface and the documentation live on their own branches, and the main branch holds the backend and the container build. That is a pattern you see in projects where a separate front end is developed and released independently of the service, and it is worth knowing before you go looking for a frontend directory in the default branch and do not find one.

The mkdocs.yml at the root and the docs/ directory together are what make the project usable: the configuration options, the card types and the setup steps live in a site rather than in a README, and the README points there for the in-depth documentation.

## The release history crosses a major version in August 2026

The version list is short and it contains one important event.

v1.16.0 was published on 2024-06-03. Then v1.16.1 followed on 2026-08-10, and v2.16.1 on 2026-08-25. So after a long gap the project shipped a patch on the 1.x line, and then two weeks later jumped to the 2.x line at the same minor and patch number.

The numbering pattern is consistent, since the minor and patch components are carried across the major bump, which suggests a scheme where the major version tracks a compatibility generation of the card output or of the configuration format rather than a reset. But the jump from 1 to 2 is a major version change by any convention, and the README does not include a migration note or a changelog summary in the sections given here, so a user moving from 1.16.1 to 2.16.1 has to work out for themselves what changed.

The last push was on 2026-08-31, six days after the 2.16.1 tag, so there is work after the release as well. Whether the project ships a changelog elsewhere is not stated in the README, and the documentation site is the place to look.

For an adopter, the practical reading is that 2.x is the line to run, that 1.16.1 is a very old code base if you are still on it, and that the two releases fifteen days apart suggest a project that is now moving quickly. The Docker image on Docker Hub is likely to track the latest tag, so a compose file pinned to a tag is worth doing rather than tracking latest.

## What to check before you point it at your library

A few practical questions come out of the repository rather than the documentation.

The first is reachability. The service listens on port 4242 inside the container, so the media server has to be able to reach that port, and if the container is on a bridge network the address has to be the container name or the host address, not localhost. The health check uses curl, so the orchestrator can tell a working instance from a broken one.

The second is architecture. The repository ships a Dockerfile and a Dockerfile.armv7, and a media server on a Raspberry Pi needs the second one. Building the wrong image on the wrong architecture produces a container that starts and then fails for reasons that look unrelated to the architecture.

The third is the ImageMagick policy, discussed above, and the reason it is on this list is that it is the kind of thing that fails at run time rather than at build time, and that it is already handled for you if you use the published image.

The fourth is the version, covered above. The 2.x line is current, the 1.x line predates it by a long gap, and the same 1.16 minor and patch numbers appearing on both sides of the major bump can mislead anyone reading the release list quickly.

The project is GPL-3.0, the Docker Hub image carries a pull count, and the maintainer asks for support through BuyMeACoffee or GitHub Sponsors. For a tool this focused, the interesting question is not whether it works but whether the card styles it produces match what you want, and the montage in the documentation is the fastest way to find out before you install anything.

## Conclusion

Adopt TitleCardMaker if you run one of the three supported media servers and want title cards generated automatically rather than by hand, and you are comfortable running a container that talks to your library. Do not adopt it if you need a hosted service, since there is no managed version mentioned here, and check the version you install because the August 2026 release moved from the 1.x line to 2.x, which is a major version change and a good place to look for breaking changes. Before you deploy, confirm the media server can reach the container on port 4242, and if you run on a Raspberry Pi use the armv7 image rather than building on the wrong architecture.

## FAQ

### What does TitleCardMaker do?

It is an automated title card maker for the Plex, Jellyfin and Emby media servers, generating the title cards for library entries automatically instead of by hand. The documentation site, titlecardmaker.com, holds the in-depth setup instructions and examples of the card styles.

### How is TitleCardMaker deployed?

As a Docker image published on Docker Hub, built from a two-stage Dockerfile on python:3.13-slim. The container runs as a non-root user with uid and gid 314, installs ImageMagick and curl, uses curl for the health check, and exposes port 4242. A separate Dockerfile.armv7 covers Raspberry Pi installations.

### Why does the TitleCardMaker image ship its own ImageMagick policy?

The Dockerfile copies backend/assets/policy.xml over the ImageMagick 7 policy file. ImageMagick ships with a deliberately restrictive policy that can block ordinary conversions in a container, so shipping a curated policy keeps the image's capabilities explicit and reviewable in the repository rather than loose at deploy time.

### What licence is TitleCardMaker released under?

The project is released under the GNU GPL-3.0 licence, with the LICENSE file at the root of the repository. The maintainer asks for support through BuyMeACoffee or GitHub Sponsors, and contributions are handled through GitHub issues.

### Which version of TitleCardMaker should I run?

The 2.x line. The listed releases are v2.16.1 from 2026-08-25, v1.16.1 from 2026-08-10 and v1.16.0 from 2024-06-03, so the 1.x line is old and the jump to 2.x is a major version change. Pin the image to a specific tag rather than tracking the latest tag.

## Sources

- [License: GPL-3.0](https://github.com/TitleCardMaker/TitleCardMaker/blob/main/LICENSE)
- [Project website](https://titlecardmaker.com/)
- [README](https://github.com/TitleCardMaker/TitleCardMaker/blob/main/README.md)
- [Releases](https://github.com/TitleCardMaker/TitleCardMaker/releases)
- [TitleCardMaker/TitleCardMaker on GitHub](https://github.com/TitleCardMaker/TitleCardMaker)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/titlecardmaker-titlecardmaker
