# FFmpeg Media - Technical Architecture


---

## 1. Layered Design

```
Presentation
  - Admin UI (forms, dashboard, field formatter; no custom upload transport)
  - Profile CRUD (list controller + Twig template, add/edit form, delete confirmation)
  - Formatter-based configuration (EntityViewDisplay settings, auto_convert, conversion_profile)
  - Routes and controllers

Services
  - VideoConverter (orchestration)
  - ConversionConfigResolver (profile resolution, output selection, streaming overrides)
  - ConversionLifecycleHandler (event dispatching, success/failure handling, circuit breaker)
  - ConversionFileManager (file entities, path resolution, usage tracking)
  - SubtitleHandler (subtitle extraction, WebVTT conversion, language detection)
  - ProcessorPipelineRunner (thumbnail, GIF, metadata, HDR processors)
  - HardwareAccelerationManager (encoder selection, HW pipeline, detection)
  - QueueManager + RetryManager + LockManager
  - ProgressTracker + DashboardStatsService
  - ConversionExecutor + AdaptiveStreamingConverter + FfmpegCommandBuilder (with path traversal validation)
  - VideoFilterCollector + VideoFilterProviderInterface (tagged service filter composition)
  - QueueSchedulingService + HardwareProbe + ErrorCategorizationService
  - ProgressLabelService + MetricsService + DeadLetterStorage
  - AudioCodecResolver (container-aware royalty-free codec negotiation)
  - FormatterSettingsResolver + SourceFileResolver
  - OutputStorageService (table-based output storage with file.usage tracking)
  - ProfileManager (with cache invalidation on save/delete)
  - HardwareDetector + HardwarePipeline + CircuitBreaker
  - FFprobeCacheService + BitrateAnalyzer
  - FieldIntegrityService (orphan detection + repair)
  - OriginalFileRetentionService (source file retention)
  - SegmentCleanupService (orphaned HLS/DASH segment cleanup)

Enums
  - HardwareType (nvenc, vaapi, qsv, videotoolbox, amf, vulkan, openharmony, cuda, none)
  - CircuitBreakerState (closed, open, half-open)

Plugins
  - Codec plugins (H.264, HEVC, AV1, VP9, VVC, FFV1, ProRes, Libjxl)
  - Output plugins (MP4, WebM, MKV, FLV, JXL, HLS, DASH, WHIP, RTP)
  - Processor plugins (thumbnail, segment outputs, metadata, audio, manifest, etc.)

Execution
  - FFmpegExecutor (Process wrapper)
  - ProgressParser (FFmpeg progress output parsing)
  - FFmpeg / ffprobe binaries
```

---

## 2. Core Services (Highlights)

| Service                          | Responsibility                                              |
|----------------------------------|-------------------------------------------------------------|
| `VideoConverter`                 | Orchestrates conversions via `ConversionDependencies` VO    |
| `OutputExecutionRunner`         | Output loop, single-output conversion, GPU two-pass routing, field-scoped adaptive delegation |
| `MediaConversionInspector`      | Status, reconvert eligibility, converted file deletion      |
| `MediaArtifactManager`          | CDN cleanup, streaming dir cleanup, progress/queue state    |
| `ConversionConfigResolver`      | Profile resolution, output selection, streaming overrides    |
| `ConversionLifecycleHandler`    | Conversion lifecycle events, success/failure handling        |
| `ConversionFileManager`         | File entities, field-scoped path resolution, stream URI mapping, cleanup |
| `SubtitleHandler`               | Subtitle extraction, WebVTT conversion, language detection  |
| `ProcessorPipelineRunner`       | Runs enabled processors (thumbnail, GIF, HDR, metadata)     |
| `HardwareAccelerationManager`   | **Single entry point** for encoder selection, HW pipeline construction, and hardware detection orchestration |
| `QueueManager`                  | Queueing, immediate conversion, dedupe, large-file routing  |
| `RetryManager`                  | Exponential backoff + auto-requeue                          |
| `LockManager`                   | Prevents concurrent conversion of the same media            |
| `ProgressTracker`               | Stores progress + status for UI and API (throttled writes, row-locked updates) |
| `FormatterSettingsResolver`     | Reads formatter settings (auto_convert, profile) from EntityViewDisplay |
| `SourceFileResolver`            | Extracts source files from media entity fields              |
| `OutputStorageService`          | Table-based output storage (`ffmpeg_media_outputs`) with file.usage tracking |
| `ProfileManager`                | Full CRUD for conversion profiles (config objects, cache invalidation) |
| `HardwareDetector`              | Detects available HW, capabilities, circuit breaker facade  |
| `HardwarePipeline`              | Builds hwaccel pipeline args (init, upload, filters, encode)|
| `CircuitBreaker`                | Skips unstable hardware encoders temporarily                |
| `FFprobeCacheService`           | Caches metadata (duration, streams, resolution)             |
| `BitrateAnalyzer`               | Per-title ladder calculation                                |
| `FieldIntegrityService`         | Detects orphaned field definitions, repairs them            |
| `OriginalFileRetentionService`  | Tracks and retains original source files                    |
| `SegmentCleanupService`        | Cleans orphaned HLS/DASH segment directories on failure and cron |

---

## 3. Enums

| Enum                  | Values                                                          | Purpose                                   |
|-----------------------|-----------------------------------------------------------------|-------------------------------------------|
| `HardwareType`        | nvenc, vaapi, qsv, videotoolbox, amf, vulkan, openharmony, cuda, none | Single source of truth for HW types       |
| `CircuitBreakerState` | closed, open, half-open                                         | Type-safe circuit breaker state management |

`HardwareType` centralizes the encoder mapping via `encoderForCodec()`, eliminating
duplication previously spread across `HardwareDetector`, `HwAccelOptions`, and `VideoConverter`.

---

## 4. VideoConverter Service Layer

`VideoConverter` (orchestrator) receives its pipeline services via a
`ConversionDependencies` value object (10 bundled services, constructor reduced
from 20 to 5 parameters). It delegates output execution to
`OutputExecutionRunner` and inspection/cleanup to `MediaConversionInspector`:

```
VideoConverter (orchestration — 5 constructor params)
  |
  |-- ConversionDependencies (value object bundling 10 pipeline services)
  |     |-- ConversionConfigResolver     (profile resolution, output selection)
  |     |-- ConversionLifecycleHandler   (events, success/failure, circuit breaker)
  |     |-- ProcessorPipelineRunner      (thumbnail, GIF, HDR, metadata processors)
  |     |-- OutputExecutionRunner        (output loop, GPU two-pass routing, adaptive delegation)
  |     |-- MediaConversionInspector     (status, reconvert eligibility, file deletion)
  |     |-- FFmpegExecutor               (binary availability, video info)
  |     |-- ProgressTracker              (progress and status tracking)
  |     |-- ProfileManager               (profile lookup, output keys, audio defaults)
  |     |-- QueueManager                 (queue removal on completion/failure)
  |     |-- FFprobeCacheService          (cached video metadata)
  |
  |-- ConfigFactory, Logger, Uuid
  |-- HardwareAccelerationManager (optional)
```

`OutputExecutionRunner` owns the per-output conversion loop (formerly
`convertSingleOutput()` inside VideoConverter) including GPU two-pass routing
and adaptive streaming delegation. `MediaConversionInspector` owns the
read-only inspection methods (`canReconvert`, `getStatus`,
`isEligibleForConversion`) and `deleteConvertedFiles` via `MediaArtifactManager`.

Each extracted service has its own interface, implementation, and unit test suite.

---

## 5. Data Flow (Conversion)

1. Media uploaded → `FormatterSettingsResolver` reads conversion settings from EntityViewDisplay formatter
2. `SourceFileResolver` extracts the source file from the media entity field
3. QueueManager enqueues conversion (or `processImmediately()` runs sync)
4. VideoConverter resolves config via `ConversionConfigResolver` (field-scoped when `field_name` is provided), probes source via `FFprobeCacheService`
5. `OutputExecutionRunner.run()` iterates enabled outputs — single outputs use field-scoped artifact paths (`{mediaId}/{fieldName}/{filename}`), adaptive outputs pass `fieldName` to `AdaptiveStreamingConverter` which uses field-scoped streaming paths (`{mediaId}/{fieldName}/{format}/`)
6. `ConversionExecutor` builds FFmpeg commands, runs with hardware fallback + progress tracking
7. Processor pipeline runs enabled processors (metadata by default; image/HDR/etc. when related submodules are enabled)
8. Outputs stored to `ffmpeg_media_outputs` table via `OutputStorageService` (keyed by `media_id, field_name, output_type`) + progress finalized

---

## 6. Submodules

- **ffmpeg_media_streaming**: HLS/DASH outputs + settings UI
- **ffmpeg_media_whisper**: Whisper transcription processor
- **ffmpeg_media_image**: thumbnail, GIF, and segment image processors + image settings UI
- **ffmpeg_media_advanced**: HDR, two-pass encoding, watermark, subtitles, and normalization
- **ffmpeg_media_player**: Vidstack-based video player for progressive (MP4/WebM/MKV/FLV) and adaptive streaming (HLS/DASH) playback; `ConvertedVideoFormatter` delegates to Vidstack when this submodule is active
- **ffmpeg_media_ui**: unified dashboard (stats, queue, active conversions, auto-refresh), poster selector, action plugins, DashboardBuilderService (UI submodule service) for render array construction; uploads are handled via Drupal core media workflows

---

## 7. Maintenance

- **Cron:** `CronHooks` runs field integrity check, dead-letter cleanup (90-day purge), and orphaned segment cleanup (configurable age, default 24h)
- **Uninstall:** `hook_uninstall` cleans up all config, state entries, and orphaned field definitions
- **Drush:** `ffmpeg:integrity-check [--repair]` for manual inspection and repair; `ffmpeg:dead-letters` for viewing permanently failed conversions

---

## 8. Admin UI Architecture

### Themed Admin Pages

The module uses a consistent design system for admin pages (dashboard, profiles):

- **Twig templates** for list/display pages (e.g., `ffmpeg-media-profiles.html.twig`)
- **CSS libraries** with Gin theme CSS custom property integration and dark mode support
- **BEM naming** convention: `.ffmpeg-{page}__{element}--{modifier}`
- **Responsive design** with mobile breakpoints and reduced-motion support

### Profiles CRUD

Profiles are stored as plain config objects (`ffmpeg_media.profile.{id}`), not config entities.

| Route | Handler | Purpose |
|-------|---------|---------|
| `profile_list` | `ProfileListController::list()` | Themed list with guide sections and profile cards |
| `profile_add` | `ProfileForm` | Add new profile with AJAX output management |
| `profile_edit` | `ProfileForm` | Edit existing profile |
| `profile_delete` | `ProfileDeleteForm` | Delete confirmation (default profile protected) |

### Formatter-Based Configuration

Conversion configuration lives in EntityViewDisplay formatter settings rather than a separate config entity. The `FormatterSettingsResolver` service reads settings from the default view mode.

- **No separate admin form:** configuration is done by adding the `ffmpeg_media_converted_video` or `ffmpeg_media_streaming_video` formatter to a field under Manage Display
- Formatter settings include `auto_convert` (boolean) and `conversion_profile` (profile ID)
- `ConversionContext` value object carries: entityType, bundle, fieldName, autoConvert, conversionProfile
- `SourceFileResolver` extracts source files from media entity fields
- `OutputStorageService` stores outputs in the `ffmpeg_media_outputs` DB table with `field_name` column

---

## 9. Extensibility Points

### Supported Public Extension Points

- Add codecs via `FFmpegCodec` plugins
- Add outputs via `FFmpegOutput` plugins
- Add processors via `FFmpegProcessor` plugins
- Alter plugin definitions via `hook_ffmpeg_codec_info_alter()`,
  `hook_ffmpeg_output_info_alter()`, and `hook_ffmpeg_processor_info_alter()`
- Subscribe to conversion lifecycle events from
  `\Drupal\ffmpeg_media\Event\FFmpegMediaEvents`
- Integrate custom formatters through
  `\Drupal\ffmpeg_media\Resolver\VideoSourceResolverInterface`
  (convenience alias: `ffmpeg_media.video_source_resolver`)

### Internal Runtime Services (Not Supported API)

Public != supported API. Operational services such as queue management,
retry/backoff, dashboard stats, progress tracking, admin UI builders, and media
operation helpers may remain public for Drupal runtime construction, plugin
autowiring, or route callbacks. They are not part of the documented extension
API and should not be treated as long-term BC-stable contrib contracts.

In practice, the remaining public runtime services fall into four categories:

- Services fetched directly by Drupal plugin, formatter, form, or Drush command
  factories
- Operational helpers injected into non-service controllers or hook classes
- Access-check helpers referenced from routing
- The documented resolver entry point needed for supported formatter
  integrations

Concrete examples from the validated runtime surface:

- `Drupal\ffmpeg_media\Cdn\CdnManagerInterface` remains public because
  `CdnSettingsForm::create()` performs a direct `$container->get()` lookup
- `Drupal\ffmpeg_media\Service\BitrateAnalyzerInterface` remains public
  because the current adaptive streaming plugin instantiation path validates it
  as a runtime-public dependency
- `Drupal\ffmpeg_media\Hook\MenuHooks` was removed from the public
  surface because it was only a placeholder without hook attributes or runtime
  references

Public visibility in these cases is a runtime construction detail, not a signal
that the service is part of the supported contrib API.

---

## 10. Upload Workflow Boundary

- Source uploads are performed by Drupal core (Media Library, media file fields,
  host entity forms).
- `ffmpeg_media` starts after entity save, resolves formatter settings, and processes source fields.
- No module-specific Dropzone/chunk-upload transport is part of the runtime
  architecture.

---

## 11. Health and Observability Hardening

### Probe Security Model

- Liveness/readiness routes remain intentionally public at routing level for
  orchestrators.
- Runtime authorization is handled in `HealthCheckController`:
  - Health probe token stored in State API (non-exportable), with ENV override via `FFMPEG_HEALTH_PROBE_TOKEN`
  - Config key `health_probe_token_required` enforces deny-by-default
  - Token accepted via header `X-FFmpeg-Health-Token` only (query parameters never accepted)
  - Failed access attempts logged with client IP via `logger.channel.ffmpeg_media`
- In production, `settings.php` auto-enforces token requirement when
  `APP_ENV=prod|production`.

### Environment-Driven Overrides

- `FFMPEG_HEALTH_PROBE_TOKEN` injects secret without config sync exposure.
- `FFMPEG_HEALTH_PROBE_REQUIRED` can explicitly force enable/disable token
  requirement per environment.

### Performance Baseline

- Reproducible load benchmark script:
  `<project-root>/scripts/load-benchmark.mjs`
- Baseline report generated by the benchmark script.

### Validation

- Unit + functional tests cover probe auth behavior (`200` with valid token,
  `403` when required and missing, audit log on denial).
- PHPCS/PHPStan checks for updated components are green.

### CDN / S3 Security

- `S3RequestSigner` enforces TLS hardening on all cURL handles:
  `CURLOPT_SSL_VERIFYPEER`, `CURLOPT_SSL_VERIFYHOST` (2), `CURLOPT_PROTOCOLS`
  (HTTPS-only). Prevents protocol downgrade and certificate bypass.
- S3 XML parsing uses `LIBXML_NONET` without `LIBXML_NOENT` to prevent XXE.

---

## 12. Architecture Diagrams

### VideoConverter Orchestration Flow

```mermaid
graph TD
    VC[VideoConverter] -->|ConversionDependencies| CCR[ConversionConfigResolver]
    VC -->|ConversionDependencies| CLH[ConversionLifecycleHandler]
    VC -->|ConversionDependencies| PPR[ProcessorPipelineRunner]
    VC -->|ConversionDependencies| OER[OutputExecutionRunner]
    VC -->|ConversionDependencies| MCI[MediaConversionInspector]

    OER --> CE[ConversionExecutor]
    OER --> ASC[AdaptiveStreamingConverter]
    OER --> OS[OutputStorageService]
    OER --> PF[PluginFactory]
    OER --> CFM[ConversionFileManager]

    MCI --> MAM[MediaArtifactManager]
    MCI --> OS
    MCI --> FSR[FormatterSettingsResolver]
    MCI --> SFR[SourceFileResolver]

    CCR --> FSR
    CCR --> SFR
    CCR --> PM[ProfileManager]

    CLH --> PT[ProgressTracker]
    CLH --> QM[QueueManager]
    CLH --> ED[EventDispatcher]
    CLH --> HD[HardwareDetector]
    CLH --> OFRS[OriginalFileRetentionService]
    CLH --> SCS[SegmentCleanupService]

    CFM --> FS[FileSystem]
    CFM --> FU[FileUsage]

    PPR --> ProcMgr[ProcessorPluginManager]
    PPR --> OS

    CE --> FCB[FfmpegCommandBuilder]
    CE --> FE[FFmpegExecutor]
    CE --> HD
    CE --> TPS[TwoPassEncodingService]

    ASC --> FCB
    ASC --> FE
    ASC --> OS

    FCB --> HAM[HardwareAccelerationManager]
    FCB --> HD

    HAM --> HD
    HAM --> HP[HardwarePipeline]
    HAM --> CB[CircuitBreaker]

    PF --> CodecMgr[FFmpegCodecPluginManager]
    PF --> OutputMgr[FFmpegOutputPluginManager]
    PF --> ProcMgr

    style VC fill:#4a90d9,color:#fff
    style OER fill:#4a90d9,color:#fff
    style MCI fill:#4a90d9,color:#fff
    style CCR fill:#6bb86b,color:#fff
    style CLH fill:#6bb86b,color:#fff
    style CFM fill:#6bb86b,color:#fff
    style PPR fill:#6bb86b,color:#fff
    style CE fill:#6bb86b,color:#fff
    style ASC fill:#6bb86b,color:#fff
    style MAM fill:#6bb86b,color:#fff
    style PF fill:#e6a23c,color:#fff
```

### Plugin System

```mermaid
graph TD
    subgraph Plugin Managers
        CPM[FFmpegCodecPluginManager]
        OPM[FFmpegOutputPluginManager]
        PPM[FFmpegProcessorPluginManager]
    end

    subgraph Codec Plugins
        CPM --> CB1[FFmpegCodecBase]
        CB1 --> H264[H264Codec]
        CB1 --> HEVC[HevcCodec]
        CB1 --> AV1[Av1Codec]
        CB1 --> VP9[Vp9Codec]
        CB1 --> VVC[VvcCodec]
        CB1 --> FFV1[Ffv1Codec]
        CB1 --> ProRes[ProResCodec]
        CB1 --> Libjxl[LibjxlCodec]
    end

    subgraph Output Plugins
        OPM --> OB1[FFmpegOutputBase]
        OB1 --> MP4[Mp4Output]
        OB1 --> WebM[WebmOutput]
        OB1 --> MKV[MkvOutput]
        OB1 --> FLV[FlvOutput]
        OB1 --> JXL[JxlOutput]
        OB1 --> HLS[HlsOutput]
        OB1 --> DASH[DashOutput]
        OB1 --> WHIP[WhipOutput]
        OB1 --> RTP[RtpOutput]
    end

    subgraph Processor Plugins
        PPM --> PB1[FFmpegProcessorBase]
        PB1 --> Thumb[ThumbnailProcessor]
        PB1 --> GIF[GifProcessor]
        PB1 --> Meta[MetadataProcessor]
        PB1 --> Audio[AudioNormalizeProcessor]
        PB1 --> HDR[HdrProcessor]
        PB1 --> WM[WatermarkProcessor]
        PB1 --> Sub[SubtitleProcessor]
        PB1 --> More[... other processors]
    end

    style CPM fill:#4a90d9,color:#fff
    style OPM fill:#4a90d9,color:#fff
    style PPM fill:#4a90d9,color:#fff
    style CB1 fill:#e6a23c,color:#fff
    style OB1 fill:#e6a23c,color:#fff
    style PB1 fill:#e6a23c,color:#fff
```

### Queue and Conversion Data Flow

```mermaid
graph LR
    Upload[Media Upload] --> EH[EntityHooks]
    EH --> FSR2[FormatterSettingsResolver]
    FSR2 --> SFR2[SourceFileResolver]
    EH --> QM2[QueueManager]

    QM2 --> QW[QueueWorker]
    QW --> VC2[VideoConverter]

    VC2 --> CCR2[ConversionConfigResolver]
    CCR2 --> PM2[ProfileManager]
    PM2 --> Profile[ConversionProfile]

    VC2 --> OER2[OutputExecutionRunner]
    OER2 --> CE2[ConversionExecutor]
    CE2 --> FCB2[FfmpegCommandBuilder]
    FCB2 --> FE2[FFmpegExecutor]

    FE2 --> FFmpeg[FFmpeg Binary]
    FFmpeg --> OutputFile[Output File]

    VC2 --> PPR2[ProcessorPipelineRunner]
    PPR2 --> Processors[Processor Plugins]

    OutputFile --> CFM2[ConversionFileManager]
    CFM2 --> OS2[OutputStorageService]
    OS2 --> DB[(ffmpeg_media_outputs)]

    VC2 --> PT2[ProgressTracker]
    PT2 --> DB2[(ffmpeg_media_progress)]

    VC2 --> CLH2[ConversionLifecycleHandler]
    CLH2 --> Events[Drupal Events]

    style Upload fill:#4a90d9,color:#fff
    style FFmpeg fill:#d94a4a,color:#fff
    style DB fill:#6bb86b,color:#fff
    style DB2 fill:#6bb86b,color:#fff
    style VC2 fill:#4a90d9,color:#fff
    style OER2 fill:#4a90d9,color:#fff
```

---

## 13. Service Grouping

Services are organized into four logical groups based on their responsibilities.
This grouping guides future refactoring and helps developers locate services
by concern.

### Configuration Services

Resolve settings, profiles, and source files that determine how a conversion
should be performed.

| Service | Responsibility |
|---------|---------------|
| `FormatterSettingsResolver` | Reads `auto_convert` and `conversion_profile` from EntityViewDisplay formatter settings |
| `SourceFileResolver` | Extracts source file entities and paths from media entity fields |
| `ConversionConfigResolver` | Combines profile, formatter settings, and overrides into a resolved configuration |
| `ProfileManager` | CRUD for conversion profiles (config objects), cache invalidation on save/delete |
| `AudioCodecResolver` | Negotiates preferred audio codec against container compatibility, royalty-free fallback |

### Execution Services

Build and run FFmpeg commands, handle hardware acceleration fallback,
and produce output files.

| Service | Responsibility |
|---------|---------------|
| `OutputExecutionRunner` | Output iteration loop, GPU two-pass routing, field-scoped adaptive delegation |
| `ConversionExecutor` | Runs single-output conversions with hardware fallback and progress tracking |
| `AdaptiveStreamingConverter` | Generates field-scoped HLS/DASH adaptive streaming outputs with representation ladders |
| `FfmpegCommandBuilder` | Constructs FFmpeg CLI argument arrays with path traversal validation |
| `VideoFilterCollector` | Collects and composes video filters from tagged `VideoFilterProviderInterface` services |
| `FFmpegExecutor` | Process wrapper that executes FFmpeg/ffprobe binaries |
| `HardwareAccelerationManager` | Single entry point for encoder selection, GPU pipeline construction |
| `HardwarePipeline` | Builds hardware-accelerated pipeline arguments (init, upload, filters, encode) |
| `HardwareDetector` | Detects available hardware, manages circuit breaker facade |
| `CircuitBreaker` | Temporarily skips unstable hardware encoders after repeated failures |

### Infrastructure Services

Handle queueing, retry logic, locking, progress tracking, and observability
concerns that support the conversion pipeline.

| Service | Responsibility |
|---------|---------------|
| `QueueManager` | Enqueues conversions, deduplication, large-file routing, immediate processing |
| `QueueSchedulingService` | Scheduled queue processing coordination |
| `RetryManager` | Exponential backoff with automatic requeue on transient failures |
| `LockManager` | Prevents concurrent conversion of the same media entity |
| `ProgressTracker` | Stores conversion progress and status with throttled writes and row-level locking |
| `ErrorCategorizationService` | Categorizes FFmpeg errors for retry decisions and reporting |
| `DeadLetterStorage` | Stores permanently failed conversions for later inspection |
| `MetricsService` | Prometheus-compatible metrics export |
| `ProgressLabelService` | Human-readable progress labels for UI display |
| `DashboardStatsService` | Aggregated statistics for the admin dashboard |

### Storage Services

Manage output files, file entities, CDN uploads, and database records
for converted media.

| Service | Responsibility |
|---------|---------------|
| `MediaConversionInspector` | Status, reconvert eligibility, converted file deletion (delegates to MediaArtifactManager) |
| `MediaArtifactManager` | CDN cleanup, streaming directory cleanup, progress/queue state cleanup |
| `OutputStorageService` | Table-based output storage (`ffmpeg_media_outputs`) with `file.usage` tracking |
| `ConversionFileManager` | Creates file entities, field-scoped path resolution, stream URIs, cleanup |
| `CdnManager` | CDN provider integration (S3-compatible storage) |
| `OriginalFileRetentionService` | Tracks and retains original source files after conversion |
| `SegmentCleanupService` | Cleans orphaned HLS/DASH segment directories (legacy + field-scoped layouts) on failure and cron |
| `FFprobeCacheService` | Caches video metadata (duration, streams, resolution) across requests |
| `FieldIntegrityService` | Detects and repairs orphaned field definitions |
