# Naming Templates & Metadata Embedding — Design Spec > Drafted 2026-04-04. Build target: next milestone. ## Overview Configurable filename templates with token-based variables, Plex-compatible metadata embedding via ffmpeg, and batch rename tooling for existing libraries. --- ## 1. Naming Template Engine ### Token Syntax Templates use `{token}` placeholders with optional formatters: | Token | Description | Example Output | |-------|-------------|---------------| | `{title}` | Content item title | `This is the video name!` | | `{channel}` | Channel name | `RockinWorms Homestead` | | `{platform}` | Platform name | `youtube` | | `{Platform}` | Platform name (capitalized) | `YouTube` | | `{date}` | Publish date (YYYY-MM-DD) | `2026-04-03` | | `{date:MM-DD-YYYY}` | Publish date (custom format) | `04-03-2026` | | `{date:YYYY}` | Year only | `2026` | | `{date:YYYYMMDD}` | Compact date | `20260403` | | `{quality}` | Resolution shorthand | `1080p` | | `{codec}` | Video codec | `h264` | | `{id}` | Platform content ID | `dQw4w9WgXcQ` | | `{ext}` | File extension | `mp4` | | `{duration}` | Duration (HH-MM-SS) | `00-12-34` | | `{index}` | Item index (zero-padded) | `001` | | `{type}` | Content type | `video` | ### Conditional Sections Wrap optional sections in `[]`: - `{channel} - {title} [{quality}]` → `RockinWorms - Title [1080p]` (quality present) - `{channel} - {title} [{quality}]` → `RockinWorms - Title` (quality null — brackets removed) ### Path Template (directory structure) Separate from filename template. Controls folder hierarchy: - Default: `{platform}/{channel}` - Custom: `{platform}/{channel}/{date:YYYY}` (year subfolders) - Custom: `{Platform} - {channel}` (flat by channel) ### Live Preview Settings UI shows a real-time preview as the user types: ``` Template: {channel} - {title} ({date:MM-DD-YYYY}) [{quality}] Preview: RockinWorms Homestead - Sifting Worm Castings (04-03-2026) [1080p].mp4 ``` Uses a sample content item from the platform's existing library for realistic preview. --- ## 2. Schema Changes ### `naming_profiles` table (new) | Column | Type | Notes | |--------|------|-------| | id | INTEGER PK | | | name | TEXT NOT NULL | e.g. "YouTube Default", "Music Archive" | | pathTemplate | TEXT NOT NULL | Directory structure: `{platform}/{channel}` | | filenameTemplate | TEXT NOT NULL | Filename: `{channel} - {title} ({date:MM-DD-YYYY}) [{quality}]` | | createdAt | TEXT | | | updatedAt | TEXT | | ### `platform_settings` additions | Column | Type | Notes | |--------|------|-------| | namingProfileId | INTEGER FK → naming_profiles | Per-platform naming override | ### `channels` additions | Column | Type | Notes | |--------|------|-------| | namingProfileId | INTEGER FK → naming_profiles | Per-channel override (trumps platform) | ### Resolution order 1. Channel-level `namingProfileId` (if set) 2. Platform-level `namingProfileId` (if set) 3. System default (hardcoded: `{platform}/{channel}/{title}`) --- ## 3. Plex Metadata Embedding ### Strategy Post-download step using ffmpeg (already available in the container). Zero re-encoding — metadata written via `-c copy -metadata key=value`. ### Tags Written (MP4 container — Plex Local Media Assets compatible) | ffmpeg key | Source | Plex field | |-----------|--------|------------| | `title` | Content item title | Title | | `artist` | Channel name | Studio/Artist | | `date` | publishedAt (YYYY-MM-DD) | Originally Available | | `year` | publishedAt year | Year | | `comment` | Channel description or content URL | Summary | | `description` | Content URL | Summary (fallback) | | `genre` | Platform name | Genre | | `show` | Channel name | Show (for TV-style libraries) | | `episode_sort` | Item index in channel | Sort Order | | `synopsis` | Content URL | (for some Plex agents) | ### Thumbnail embedding Already implemented via `--embed-thumbnail` in FormatProfile. Plex reads embedded cover art from MP4. ### MKV container MKV uses Matroska tags — different key names but ffmpeg handles the translation. Same `-metadata` flags work. ### Implementation New `MetadataEmbedder` service: ```typescript class MetadataEmbedder { async embedMetadata(filePath: string, metadata: MediaMetadata): Promise } interface MediaMetadata { title: string; artist: string; // channel name date: string; // YYYY-MM-DD year: string; // YYYY comment: string; // URL or description genre: string; // platform show: string; // channel name (for TV library mode) } ``` Called after download completes, before marking status as `downloaded`. Uses `ffmpeg -i input -c copy -metadata ... output && mv output input` (atomic replace via temp file). ### Toggle Add `embedMetadata: boolean` to FormatProfile (default: false). When enabled, the post-download step runs. --- ## 4. Batch Rename Tool ### Purpose Apply a naming profile retroactively to already-downloaded files. Handles: 1. Rename files on disk 2. Update `filePath` in database 3. Update `qualityMetadata` path references 4. Handle conflicts (duplicate names) ### API ``` POST /api/v1/channel/:id/rename-preview Body: { namingProfileId: number } Response: { renames: [{ contentId, oldPath, newPath, conflict: boolean }] } POST /api/v1/channel/:id/rename-apply Body: { namingProfileId: number } Response: { renamed: number, skipped: number, errors: number } ``` ### UI Channel detail page: "Rename Files" button → modal showing preview of all renames → confirm to apply. ### Safety - Preview-first: always show what WILL change before applying - Conflict detection: flag files that would collide - Dry-run by default - Rollback: store old paths in download_history for manual recovery --- ## 5. Frontend Components ### NamingProfileEditor - Template input with token autocomplete (type `{` to see suggestions) - Live preview using sample data from the platform - Path template + filename template as separate fields - Save as named profile ### Platform Settings Integration - "Naming Profile" dropdown in PlatformSettingsForm - Per-channel override in ChannelDetail settings section ### Batch Rename Modal - Channel-scoped: triggered from ChannelDetail - Shows table: Old Name → New Name with conflict highlighting - Apply button with confirmation - Progress indicator for large channels --- ## 6. Implementation Order ### Slice 1: Template Engine + Schema - `NamingTemplate` class with `resolve(tokens)` method - Token parsing, date formatting, conditional sections - Migration: `naming_profiles` table + FK columns - CRUD routes + repository ### Slice 2: Download Integration - `FileOrganizer.buildOutputPath` uses naming profile instead of hardcoded pattern - Resolution chain: channel → platform → system default - Settings UI: NamingProfileEditor component + platform/channel dropdowns ### Slice 3: Metadata Embedding - `MetadataEmbedder` service (ffmpeg -c copy -metadata) - Post-download hook in DownloadService - `embedMetadata` toggle on FormatProfile - Verify Plex reads the tags (manual UAT) ### Slice 4: Batch Rename - Preview API + Apply API - Rename modal in ChannelDetail - Conflict detection + rollback tracking --- ## 7. Plex Library Type Recommendations Document in the app's help/docs: | Content Type | Plex Library Type | Naming Pattern | |-------------|------------------|----------------| | YouTube channels (episodic) | TV Shows | `{channel} - s01e{index} - {title}` | | YouTube channels (non-episodic) | Other Videos / Personal Media | `{channel} - {title} ({date:YYYY-MM-DD})` | | Music (SoundCloud, Bandcamp) | Music | `{channel}/{title}` (+ audio tags) | | Generic (Vimeo, misc) | Other Videos | `{title} ({date:YYYY})` | --- ## Open Questions 1. Should naming profiles be global or per-user? (Currently single-user, so global is fine) 2. Should we support yt-dlp's own output template syntax (`%(title)s`) as an alternative to our `{title}` tokens? Pros: power users already know it. Cons: two template syntaxes to maintain. 3. TV-style episode numbering: auto-increment based on publish date order, or explicit mapping?