feat(transcoding): add FFmpeg HLS transcoding support

- Introduced `TranscodeManager` for managing on-demand transcoding of local video files.
- Added configuration options for transcoding in `Config` and `LocalFilesConfig`.
- Implemented new API routes for managing transcoding settings, stats, and cache.
- Updated `LocalFilesProvider` to support transcoding capabilities.
- Created frontend components for managing transcode settings and displaying stats.
- Added database migration for transcode settings.
- Enhanced existing routes and DTOs to accommodate new transcoding features.
This commit is contained in:
2026-03-15 00:34:23 +01:00
parent ead65e6be2
commit 1102e385f3
23 changed files with 865 additions and 31 deletions

View File

@@ -9,19 +9,26 @@ use domain::{
use super::config::LocalFilesConfig;
use super::index::{LocalIndex, decode_id};
use super::scanner::LocalFileItem;
use super::transcoder::TranscodeManager;
pub struct LocalFilesProvider {
pub index: Arc<LocalIndex>,
base_url: String,
transcode_manager: Option<Arc<TranscodeManager>>,
}
const SHORT_DURATION_SECS: u32 = 1200; // 20 minutes
impl LocalFilesProvider {
pub fn new(index: Arc<LocalIndex>, config: LocalFilesConfig) -> Self {
pub fn new(
index: Arc<LocalIndex>,
config: LocalFilesConfig,
transcode_manager: Option<Arc<TranscodeManager>>,
) -> Self {
Self {
index,
base_url: config.base_url.trim_end_matches('/').to_string(),
transcode_manager,
}
}
}
@@ -57,8 +64,13 @@ impl IMediaProvider for LocalFilesProvider {
tags: true,
decade: true,
search: true,
streaming_protocol: StreamingProtocol::DirectFile,
streaming_protocol: if self.transcode_manager.is_some() {
StreamingProtocol::Hls
} else {
StreamingProtocol::DirectFile
},
rescan: true,
transcode: self.transcode_manager.is_some(),
}
}
@@ -138,12 +150,27 @@ impl IMediaProvider for LocalFilesProvider {
.map(|item| to_media_item(item_id.clone(), &item)))
}
async fn get_stream_url(&self, item_id: &MediaItemId, _quality: &StreamQuality) -> DomainResult<String> {
Ok(format!(
"{}/api/v1/files/stream/{}",
self.base_url,
item_id.as_ref()
))
async fn get_stream_url(&self, item_id: &MediaItemId, quality: &StreamQuality) -> DomainResult<String> {
match quality {
StreamQuality::Transcode(_) if self.transcode_manager.is_some() => {
let tm = self.transcode_manager.as_ref().unwrap();
let rel = decode_id(item_id).ok_or_else(|| {
DomainError::InfrastructureError("invalid item id encoding".into())
})?;
let src = self.index.root_dir.join(&rel);
tm.ensure_transcoded(item_id.as_ref(), &src).await?;
Ok(format!(
"{}/api/v1/files/transcode/{}/playlist.m3u8",
self.base_url,
item_id.as_ref()
))
}
_ => Ok(format!(
"{}/api/v1/files/stream/{}",
self.base_url,
item_id.as_ref()
)),
}
}
async fn list_collections(&self) -> DomainResult<Vec<Collection>> {