Skip to content

Trailers and Extras ​

Since: 0.14.0

Overview ​

This document describes the trailers and extras support in Phlix, including local Trailers/ folder support, -trailer.mkv naming conventions, and TMDB trailer URL integration.

Local Trailer Discovery ​

Phlix scans media directories for trailers in two locations:

Same-Level Trailers ​

Trailers can be placed at the same level as the main media file:

Movies/
  Avatar (2009)/
    Avatar (2009).mkv
    Avatar (2009)-trailer.mkv        ← same-level trailer

Trailers/ Subfolder ​

Trailers can also be placed in a Trailers/ subfolder:

Movies/
  Avatar (2009)/
    Avatar (2009).mkv
    Trailers/
      Avatar (2009)-teaser.mkv
      Avatar (2009)-official-trailer.mkv

Naming Conventions ​

The suffix of the trailer file is extracted as the display title:

SuffixDisplay Title
-trailerTrailer
-teaserTeaser
-clipClip
-featuretteFeaturette
-behind-the-scenesBehind the Scenes
-interviewInterview
-deleted-sceneDeleted Scene

Supported File Extensions ​

  • mkv, mp4, avi, mov, wmv, flv, webm, m4v, mpg, mpeg, ts

API Endpoints ​

Get All Extras ​

GET /api/v1/media/{id}/extras

Returns all trailers and extras (merged, sorted by type priority).

Response:

json
{
  "extras": [
    {
      "id": "...",
      "media_item_id": "...",
      "title": "Official Trailer",
      "source": "local",
      "url": "file:///path/to/trailer.mkv",
      "duration": 120,
      "quality": 1080,
      "is_local": true,
      "file_path": "/path/to/trailer.mkv"
    }
  ],
  "count": 1
}

Get Trailers Only ​

GET /api/v1/media/{id}/trailers

Returns only trailers (not other extras).

Get Non-Trailer Extras ​

GET /api/v1/media/{id}/extras/other

Returns extras that are not trailers (featurettes, behind the scenes, etc.).

Data Model ​

Trailer DTO ​

php
final readonly class Trailer
{
    public function __construct(
        public string $id,
        public string $mediaItemId,
        public string $title,
        public string $source,     // 'local' | 'tmdb'
        public string $url,
        public int $duration,        // seconds
        public int $quality,        // 480/720/1080/2160
        public bool $isLocal,
        public string $filePath,
    ) {}
}

Extra DTO ​

php
final readonly class Extra
{
    public const TYPE_FEATURETTE = 'featurette';
    public const TYPE_BEHIND_THE_SCENES = 'behind_the_scenes';
    public const TYPE_INTERVIEW = 'interview';
    public const TYPE_CLIP = 'clip';
    public const TYPE_DELETED_SCENE = 'deleted_scene';
    public const TYPE_TRAILER = 'trailer';

    public function __construct(
        public string $id,
        public string $mediaItemId,
        public string $title,
        public string $type,
        public string $source,
        public string $url,
        public int $duration,
        public int $quality,
        public bool $isLocal,
        public string $filePath,
    ) {}
}

Database Schema ​

The media_extras table stores cached trailer and extra data:

sql
CREATE TABLE media_extras (
    id CHAR(36) NOT NULL PRIMARY KEY,
    media_item_id CHAR(36) NOT NULL,
    title VARCHAR(256) NOT NULL,
    extra_type VARCHAR(32) NOT NULL,
    source VARCHAR(16) NOT NULL,
    url VARCHAR(1024) NOT NULL,
    file_path VARCHAR(1024) NULL,
    duration INT NOT NULL DEFAULT 0,
    quality INT NOT NULL DEFAULT 0,
    cached_at DATETIME NOT NULL,
    INDEX idx_me_media (media_item_id),
    INDEX idx_me_type (extra_type)
);

Caching ​

Trailer and extra data is cached in the media_extras table with a 24-hour TTL. Cache is refreshed:

  1. When the TTL expires (on next request)
  2. When MediaScanner rescans the library

FolderWatcher does not contribute a third trigger: its change-detection method checkForChanges() has no production caller, so nothing watches Trailers/ folders at runtime. See Scan triggering.

TMDB Integration ​

Trailers from TMDB are fetched via the TmdbProvider::getTrailers() method. Local trailers take priority over TMDB trailers with the same title.

TMDB Configuration ​

Ensure your config/tmdb.php has a valid API key:

php
return [
    'api_key' => 'your_tmdb_api_key',
    // ...
];

Folder Watcher Integration ​

FolderWatcher is intended to detect changes in:

  • Trailers/ directories
  • Files with -trailer, -teaser, -clip, -featurette suffixes

and to signal that extras should be rescanned for the affected media item.

Inert at runtime

Creating a library registers its roots with FolderWatcher, but nothing ever calls FolderWatcher::checkForChanges(), which is the only method that compares directory checksums and reacts. No change is detected and no LibraryUpdated event is dispatched. New or changed extras are picked up by the next scan/rescan, not by the watcher. See Scan triggering.

Architecture ​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      ExtrasController                        β”‚
β”‚  GET /api/v1/media/{id}/extras                               β”‚
β”‚  GET /api/v1/media/{id}/trailers                            β”‚
β”‚  GET /api/v1/media/{id}/extras/other                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚
                      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     TrailerResolver                          β”‚
β”‚  - Merges local + TMDB trailers                             β”‚
β”‚  - Local takes priority over TMDB                           β”‚
β”‚  - Caches results in media_extras (24h TTL)                β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚                  β”‚                  β”‚
       β–Ό                  β–Ό                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ExtrasRepo β”‚  β”‚TrailerFinderβ”‚  β”‚  TmdbProvider   β”‚
β”‚  (cache)   β”‚  β”‚ (scanning)  β”‚  β”‚  (remote)       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Migration ​

Run the migration to create the media_extras table:

bash
php scripts/run-migrations.php

Testing ​

Run unit tests:

bash
./vendor/bin/phpunit tests/Unit/Media/Extras/

Run integration tests:

bash
./vendor/bin/phpunit tests/Integration/Media/Extras/

See Also ​

BSD-3-Clause