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 trailerTrailers/ 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.mkvNaming Conventions β
The suffix of the trailer file is extracted as the display title:
| Suffix | Display Title |
|---|---|
-trailer | Trailer |
-teaser | Teaser |
-clip | Clip |
-featurette | Featurette |
-behind-the-scenes | Behind the Scenes |
-interview | Interview |
-deleted-scene | Deleted 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}/extrasReturns all trailers and extras (merged, sorted by type priority).
Response:
{
"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}/trailersReturns only trailers (not other extras).
Get Non-Trailer Extras β
GET /api/v1/media/{id}/extras/otherReturns extras that are not trailers (featurettes, behind the scenes, etc.).
Data Model β
Trailer DTO β
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 β
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:
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:
- When the TTL expires (on next request)
- When
MediaScannerrescans 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:
return [
'api_key' => 'your_tmdb_api_key',
// ...
];Folder Watcher Integration β
FolderWatcher is intended to detect changes in:
Trailers/directories- Files with
-trailer,-teaser,-clip,-featurettesuffixes
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:
php scripts/run-migrations.phpTesting β
Run unit tests:
./vendor/bin/phpunit tests/Unit/Media/Extras/Run integration tests:
./vendor/bin/phpunit tests/Integration/Media/Extras/