Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 9 additions & 48 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ on:
workflow_dispatch:

env:
# Canonical Docker Hub image name. The deprecated mirror below keeps
# publishing alongside this one until 2026-10-29 so users on the old
# name keep getting updates while they migrate.
# Canonical Docker Hub image name. The old mirror
# (stevezzau/plex_generate_vid_previews) was retired — a one-shot tombstone
# (retire-deprecated-image.yml) now sits on its tags telling users to switch.
# Change DOCKER_IMAGE for forks; see CONTRIBUTING.md.
DOCKER_IMAGE: stevezzau/media_preview_generator
DOCKER_IMAGE_DEPRECATED: stevezzau/plex_generate_vid_previews

concurrency:
# Cancel superseded PR runs; never cancel in-flight pushes to main/dev/tags
Expand Down Expand Up @@ -366,7 +365,6 @@ jobs:
head_sha: ${{ steps.ctx.outputs.head_sha }}
is_tag: ${{ steps.ctx.outputs.is_tag }}
docker_tags_canonical: ${{ steps.ctx.outputs.docker_tags_canonical }}
docker_tags_deprecated: ${{ steps.ctx.outputs.docker_tags_deprecated }}
steps:
- name: Compute build context
id: ctx
Expand All @@ -380,12 +378,10 @@ jobs:
if [[ "$REF_TYPE" == "tag" ]]; then
echo "is_tag=true" >> "$GITHUB_OUTPUT"
echo "docker_tags_canonical=-t ${{ env.DOCKER_IMAGE }}:$HEAD_BRANCH -t ${{ env.DOCKER_IMAGE }}:latest" >> "$GITHUB_OUTPUT"
echo "docker_tags_deprecated=-t ${{ env.DOCKER_IMAGE_DEPRECATED }}:$HEAD_BRANCH -t ${{ env.DOCKER_IMAGE_DEPRECATED }}:latest" >> "$GITHUB_OUTPUT"
else
# dev branch push or manual workflow_dispatch
echo "is_tag=false" >> "$GITHUB_OUTPUT"
echo "docker_tags_canonical=-t ${{ env.DOCKER_IMAGE }}:dev" >> "$GITHUB_OUTPUT"
echo "docker_tags_deprecated=-t ${{ env.DOCKER_IMAGE_DEPRECATED }}:dev" >> "$GITHUB_OUTPUT"
fi

build:
Expand All @@ -394,24 +390,16 @@ jobs:
fail-fast: true
matrix:
include:
# 4 jobs total: 2 platforms × 2 image flavours. The deprecated
# mirror and the canonical image differ only in the published
# name + the DOCKER_IMAGE_NAME build-arg baked in (drives the
# in-app deprecation banner). Layer cache is shared across
# flavours via cache-scope so the second flavour's build is
# near-instant.
# 2 jobs: amd64 + arm64 of the canonical image. The deprecated
# mirror (stevezzau/plex_generate_vid_previews) was retired — a
# one-shot tombstone image (see retire-deprecated-image.yml) now
# sits on its tags telling users to switch to the canonical name.
- platform: linux/amd64
runner: ubuntu-latest
image_flavour: canonical
- platform: linux/amd64
runner: ubuntu-latest
image_flavour: deprecated
- platform: linux/arm64
runner: ubuntu-24.04-arm
image_flavour: canonical
- platform: linux/arm64
runner: ubuntu-24.04-arm
image_flavour: deprecated
runs-on: ${{ matrix.runner }}

steps:
Expand All @@ -437,14 +425,9 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Resolve image name for this flavour
- name: Resolve image name
id: image
run: |
if [[ "${{ matrix.image_flavour }}" == "canonical" ]]; then
echo "name=${{ env.DOCKER_IMAGE }}" >> "$GITHUB_OUTPUT"
else
echo "name=${{ env.DOCKER_IMAGE_DEPRECATED }}" >> "$GITHUB_OUTPUT"
fi
run: echo "name=${{ env.DOCKER_IMAGE }}" >> "$GITHUB_OUTPUT"

- name: Generate bundled release notes
# Fetches GitHub Releases into media_preview_generator/release_notes.json
Expand Down Expand Up @@ -512,13 +495,6 @@ jobs:
pattern: digest-*-canonical
merge-multiple: true

- name: Download deprecated digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests/deprecated
pattern: digest-*-deprecated
merge-multiple: true

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

Expand All @@ -535,13 +511,6 @@ jobs:
${{ needs.context.outputs.docker_tags_canonical }} \
$(printf '${{ env.DOCKER_IMAGE }}@sha256:%s ' *)

- name: Create deprecated mirror manifest list and push
working-directory: /tmp/digests/deprecated
run: |
docker buildx imagetools create \
${{ needs.context.outputs.docker_tags_deprecated }} \
$(printf '${{ env.DOCKER_IMAGE_DEPRECATED }}@sha256:%s ' *)

update-dockerhub-description:
runs-on: ubuntu-latest
needs: context
Expand All @@ -558,11 +527,3 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: stevezzau/media_preview_generator
readme-filepath: ./DOCKERHUB_README.md

- name: Update deprecated mirror Docker Hub description
uses: peter-evans/dockerhub-description@v5
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: stevezzau/plex_generate_vid_previews
readme-filepath: ./DOCKERHUB_DEPRECATED_README.md
54 changes: 54 additions & 0 deletions .github/workflows/retire-deprecated-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Retire deprecated Docker image

# One-shot, manually triggered. Builds the tombstone image (a static "this
# image moved" page) and pushes it OVER the deprecated repo's :latest and :dev
# tags, so anyone still pulling stevezzau/plex_generate_vid_previews gets a
# clear "switch to stevezzau/media_preview_generator" notice. Run this ONCE,
# then the deprecated build is already removed from ci.yml.
on:
workflow_dispatch:
inputs:
confirm:
description: 'Type "retire" to confirm pushing the tombstone over the old image'
required: true
default: ''

env:
DEPRECATED_IMAGE: stevezzau/plex_generate_vid_previews

jobs:
tombstone:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.confirm == 'retire' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Validate Docker secrets
run: |
if [ -z "${{ secrets.DOCKER_USERNAME }}" ] || [ -z "${{ secrets.DOCKER_PASSWORD }}" ]; then
echo "::error::DOCKER_USERNAME and DOCKER_PASSWORD must be set in repository secrets"
exit 1
fi

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build & push tombstone (multi-arch) over :latest and :dev
uses: docker/build-push-action@v6
with:
context: ./tombstone
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ env.DEPRECATED_IMAGE }}:latest
${{ env.DEPRECATED_IMAGE }}:dev
43 changes: 0 additions & 43 deletions DOCKERHUB_DEPRECATED_README.md

This file was deleted.

2 changes: 1 addition & 1 deletion DOCKERHUB_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

GPU-accelerated video preview thumbnail generation for **Plex, Emby, and Jellyfin**. **Web UI only** — no CLI.

> Previously named **Plex Generate Previews** at `stevezzau/plex_generate_vid_previews`. That image keeps mirroring updates until **2026-10-29**; after that, only this repo (`stevezzau/media_preview_generator`) is published. Update your `compose` to the new name when convenient — settings and volumes carry over unchanged.
> Previously named **Plex Generate Previews** at `stevezzau/plex_generate_vid_previews`. **That image has been retired and no longer receives updates** — this repo (`stevezzau/media_preview_generator`) is the only one published. If you're still on the old name, update your `compose` file's `image:` line to `stevezzau/media_preview_generator` and re-pull — settings and volumes carry over unchanged.

**The Problem:** Built-in preview generation has gaps depending on which server you run:

Expand Down
13 changes: 4 additions & 9 deletions media_preview_generator/web/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,22 +520,17 @@ def _log_image_deprecation_warning() -> None:
rather than open the dashboard. Silent when the env var is unset
(local dev) or set to the canonical name.
"""
from .notifications import (
CANONICAL_IMAGE_NAME,
DEPRECATED_IMAGE_NAME,
DEPRECATED_IMAGE_SUNSET_DATE,
)
from .notifications import CANONICAL_IMAGE_NAME, DEPRECATED_IMAGE_NAME

image_name = (os.environ.get("DOCKER_IMAGE_NAME") or "").strip()
if image_name != DEPRECATED_IMAGE_NAME:
return
logger.warning(
"Running deprecated Docker image {!r} — switch your compose to {!r} "
"before {} to keep getting updates. (Both image names mirror the same "
"builds until then; only the canonical name receives updates after.)",
"Running RETIRED Docker image {!r} — it no longer receives updates. "
"Switch your compose 'image:' line to {!r} and re-pull to keep getting "
"updates. Your volumes and settings are unchanged.",
DEPRECATED_IMAGE_NAME,
CANONICAL_IMAGE_NAME,
DEPRECATED_IMAGE_SUNSET_DATE,
)


Expand Down
26 changes: 13 additions & 13 deletions media_preview_generator/web/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@
DEPRECATED_IMAGE_ID = "deprecated_docker_image_name"
MEDIA_MOUNT_UNHEALTHY_ID = "media_mount_unhealthy"

# Image names recognised by the deprecation banner. The deprecated image
# keeps publishing alongside the canonical name until 2026-10-29 (six months
# after the rename); after that, only the canonical name receives updates.
# Image names recognised by the deprecation banner. The deprecated image was
# retired — a one-shot tombstone image now sits on its tags. This banner still
# fires for anyone running the last real build of the old image (no auto-update
# yet) to push them to switch to the canonical name.
DEPRECATED_IMAGE_NAME = "stevezzau/plex_generate_vid_previews"
CANONICAL_IMAGE_NAME = "stevezzau/media_preview_generator"
DEPRECATED_IMAGE_SUNSET_DATE = "2026-10-29"


_SESSION_DISMISSED: set[str] = set()
Expand Down Expand Up @@ -201,21 +201,21 @@ def _build_deprecated_image_notification() -> dict[str, Any] | None:

body = (
f"<p class='mb-1'>You're running the Docker image "
f"<code>{DEPRECATED_IMAGE_NAME}</code>, which has been renamed to "
f"<code>{CANONICAL_IMAGE_NAME}</code> to reflect that this app now "
f"supports Plex, Emby, and Jellyfin.</p>"
f"<p class='mb-1'>Both image names mirror the same builds until "
f"<strong>{DEPRECATED_IMAGE_SUNSET_DATE}</strong>; after that, only "
f"<code>{CANONICAL_IMAGE_NAME}</code> receives updates. Update your "
f"<code>compose</code> file&apos;s <code>image:</code> line and pull "
f"the new image to keep getting updates.</p>"
f"<code>{DEPRECATED_IMAGE_NAME}</code>, which has been <strong>retired</strong> "
f"and renamed to <code>{CANONICAL_IMAGE_NAME}</code> (this app now supports "
f"Plex, Emby, and Jellyfin).</p>"
f"<p class='mb-1'>The old image name <strong>no longer receives updates</strong>. "
f"Update your <code>compose</code> file&apos;s <code>image:</code> line to "
f"<code>{CANONICAL_IMAGE_NAME}</code> and run "
f"<code>docker compose pull &amp;&amp; docker compose up -d</code> to keep "
f"getting updates.</p>"
f"<p class='mb-0 small text-muted'>Existing volumes, settings, and "
f"configuration are unchanged — only the image name moves.</p>"
)
return {
"id": DEPRECATED_IMAGE_ID,
"severity": "warning",
"title": "Update your Docker image",
"title": "Your Docker image has been retired",
"body_html": body,
"dismissable": True,
"source": "image_deprecation",
Expand Down
5 changes: 3 additions & 2 deletions tests/test_notifications_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,9 @@ def test_fires_when_running_deprecated_image(self, monkeypatch):
# The body names both the old and the new image so users can copy-paste.
assert "stevezzau/plex_generate_vid_previews" in deprecated["body_html"]
assert "stevezzau/media_preview_generator" in deprecated["body_html"]
# And carries the sunset date.
assert "2026-10-29" in deprecated["body_html"]
# And makes clear the old image is retired (no longer updated).
assert "retired" in deprecated["body_html"].lower()
assert deprecated["title"] == "Your Docker image has been retired"


class TestSettingsManagerDismissedNotifications:
Expand Down
10 changes: 10 additions & 0 deletions tombstone/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Minimal "this image was retired" tombstone for the old Docker Hub repo
# (stevezzau/plex_generate_vid_previews). Built + pushed ONCE by
# .github/workflows/retire-deprecated-image.yml, then the deprecated build is
# removed from ci.yml. Anyone who pulls the old image after that gets a page
# telling them to switch to stevezzau/media_preview_generator.
FROM nginx:1.27-alpine
COPY index.html /usr/share/nginx/html/index.html
COPY default.conf /etc/nginx/conf.d/default.conf
EXPOSE 8080
HEALTHCHECK CMD wget -qO- http://127.0.0.1:8080/ >/dev/null 2>&1 || exit 1
8 changes: 8 additions & 0 deletions tombstone/default.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Tombstone server: every request returns the "this image moved" page on the
# app's normal port (8080) so existing compose port-maps still hit it.
server {
listen 8080 default_server;
server_name _;
root /usr/share/nginx/html;
location / { try_files /index.html =404; }
}
36 changes: 36 additions & 0 deletions tombstone/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>This image moved — Media Preview Generator</title>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; background:#0f172a; color:#e2e8f0;
display:flex; min-height:100vh; align-items:center; justify-content:center; margin:0; padding:24px; }
.card { max-width:680px; background:#1e293b; border:1px solid #334155; border-radius:14px; padding:32px 36px; }
h1 { margin:0 0 8px; font-size:1.5rem; }
.tag { display:inline-block; background:#7f1d1d; color:#fecaca; padding:2px 10px; border-radius:999px; font-size:.8rem; }
code { background:#0f172a; border:1px solid #334155; border-radius:6px; padding:2px 8px; color:#7dd3fc; }
.old { color:#fca5a5; text-decoration:line-through; }
.new { color:#86efac; font-weight:600; }
.steps { background:#0f172a; border-radius:10px; padding:16px 18px; margin-top:18px; }
a { color:#7dd3fc; }
.muted { color:#94a3b8; font-size:.9rem; margin-top:18px; }
</style>
</head>
<body>
<div class="card">
<span class="tag">Image retired</span>
<h1>This Docker image has moved 🚚</h1>
<p><code class="old">stevezzau/plex_generate_vid_previews</code> is no longer published.
The project was renamed (it now supports Plex, Emby &amp; Jellyfin).</p>
<p>Switch your <code>image:</code> line to the new name — your config, volumes and settings carry over unchanged:</p>
<div class="steps">
<div><span class="old">image: stevezzau/plex_generate_vid_previews:latest</span></div>
<div><span class="new">image: stevezzau/media_preview_generator:latest</span></div>
</div>
<p class="muted">Then <code>docker compose pull &amp;&amp; docker compose up -d</code>. Same app, same data — just the new image name.
Docs: <a href="https://github.com/stevezau/media_preview_generator">github.com/stevezau/media_preview_generator</a></p>
</div>
</body>
</html>
Loading