From cabf8aa83dec487ed4eab384465d47230a88a3be Mon Sep 17 00:00:00 2001 From: Benjamin Slingo Date: Mon, 8 Sep 2025 12:31:17 -0400 Subject: [PATCH] Remove legacy batch transcoding infrastructure - Remove TranscodedMedia, TranscodingStatus, and TranscodingRequest models - Delete 184-line commented stream_media function - Clean up phantom service imports and dead references - Remove unused test imports for non-existent services Total cleanup: 265 lines of dead code removed from legacy Jellyfin compatibility layer. Smart streaming pipeline remains unchanged and functional. --- src/app_state.rs | 1 - src/handlers/media.rs | 186 --------------------------- src/models/media.rs | 77 ----------- src/utils/media_parsing.rs | 2 + tests/gstreamer_integration_tests.rs | 1 - 5 files changed, 2 insertions(+), 265 deletions(-) diff --git a/src/app_state.rs b/src/app_state.rs index c1202a2..1a67c75 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -8,5 +8,4 @@ pub struct AppState { pub jwt_secret: String, pub mailer: Arc, pub owncast_service: Option>, - // Transcoding services removed - replaced by simple smart streaming } \ No newline at end of file diff --git a/src/handlers/media.rs b/src/handlers/media.rs index 737cd11..a4a1877 100644 --- a/src/handlers/media.rs +++ b/src/handlers/media.rs @@ -6,7 +6,6 @@ use tokio::fs::File; use crate::error::{Result, ApiError}; use crate::models::media::{MediaItem, MediaItemResponse}; use crate::models::ApiResponse; -// TranscodingJob import removed - never released transcoding nightmare eliminated use crate::utils::response::success_response; use crate::{AppState, sql}; @@ -191,191 +190,6 @@ pub async fn list_livestreams( } -/// Legacy streaming function removed - replaced by smart_streaming system -/* -pub async fn stream_media( - State(state): State, - Path(media_id): Path, - Query(params): Query>, - headers: HeaderMap, -) -> Result { - // Get the media item - let media_item = sqlx::query_as!( - MediaItem, - r#" - SELECT id, title, speaker, date, description, scripture_reading, - file_path, file_size, duration_seconds, video_codec, audio_codec, - resolution, bitrate, thumbnail_path, thumbnail_generated_at, - nfo_path, last_scanned, created_at, updated_at - FROM media_items - WHERE id = $1 - "#, - media_id - ) - .fetch_optional(&state.pool) - .await - .map_err(|e| ApiError::Database(e.to_string()))? - .ok_or_else(|| ApiError::NotFound("Media item not found".to_string()))?; - - // Detect client capabilities - let client_caps = ClientCapabilities::detect_from_headers(&headers); - let source_codec = media_item.video_codec.as_deref().unwrap_or("h264"); - - // Use the unified transcoding service from app state - let transcoding_service = &state.transcoding_service; - - // Check if transcoding is needed - let needs_transcoding = transcoding_service.needs_transcoding(source_codec, &client_caps); - - let file_path = if needs_transcoding { - tracing::info!("Client requires transcoding from {} for device {}", source_codec, client_caps.device_type); - - // Create transcoding job - let job = TranscodingJob { - media_item_id: media_item.id, - source_path: media_item.file_path.clone(), - target_codec: "h264".to_string(), - target_resolution: params.get("max_width").map(|w| { - let width = w.parse::().unwrap_or(1920); - let height = width * 9 / 16; // 16:9 aspect ratio - format!("{}x{}", width, height) - }), - target_bitrate: params.get("video_bit_rate").and_then(|b| b.parse::().ok()), - client_capabilities: client_caps.clone(), - }; - - // Check if transcoded version already exists - let transcoded = transcoding_service.get_or_create_transcoded(job).await?; - - match transcoded.status { - crate::models::media::TranscodingStatus::Completed => { - tracing::info!("Serving transcoded file"); - transcoded.file_path - }, - crate::models::media::TranscodingStatus::Processing | crate::models::media::TranscodingStatus::Pending => { - // For incompatible codecs like AV1, return 202 to indicate transcoding in progress - if source_codec == "av1" || source_codec == "hevc" || source_codec == "h265" { - tracing::info!("Transcoding {} file for {} - returning 202", source_codec, client_caps.device_type); - - let response = Response::builder() - .status(StatusCode::ACCEPTED) - .header(header::CONTENT_TYPE, "application/json") - .header(header::RETRY_AFTER, "30") // Suggest retry in 30 seconds - .body(axum::body::Body::from(r#"{"message":"Transcoding in progress","retry_after":30}"#)) - .map_err(|e| ApiError::Internal(format!("Failed to build response: {}", e)))?; - - return Ok(response); - } else { - // For other codecs, serve original while transcoding - tracing::info!("Transcoding in progress, serving original file"); - media_item.file_path - } - }, - crate::models::media::TranscodingStatus::Failed => { - tracing::warn!("Transcoding failed, serving original file"); - media_item.file_path - } - } - } else { - tracing::info!("No transcoding needed, serving original file"); - media_item.file_path - }; - - // Check if file exists - if !std::path::Path::new(&file_path).exists() { - return Err(ApiError::NotFound(format!("Media file not found: {}", file_path))); - } - - // Open the file - let file = File::open(&file_path).await - .map_err(|e| ApiError::Internal(format!("Failed to open media file: {}", e)))?; - - // Get file metadata - let metadata = file.metadata().await - .map_err(|e| ApiError::Internal(format!("Failed to get file metadata: {}", e)))?; - - let file_size = metadata.len(); - - // Handle range requests for video streaming - let (start, end, content_length) = if let Some(range) = headers.get("range") { - let range_str = range.to_str().unwrap_or(""); - if let Some(range_bytes) = range_str.strip_prefix("bytes=") { - let parts: Vec<&str> = range_bytes.split('-').collect(); - let start = parts[0].parse::().unwrap_or(0); - let end = if parts.len() > 1 && !parts[1].is_empty() { - parts[1].parse::().unwrap_or(file_size - 1) - } else { - file_size - 1 - }; - let content_length = end - start + 1; - (start, end, content_length) - } else { - (0, file_size - 1, file_size) - } - } else { - // For video files > 10MB, serve only first 1MB initially to encourage range requests - if file_size > 10 * 1024 * 1024 { - let chunk_size = 1024 * 1024; // 1MB - let end = std::cmp::min(chunk_size - 1, file_size - 1); - (0, end, end + 1) - } else { - (0, file_size - 1, file_size) - } - }; - - // Create the response - let mut response_builder = Response::builder(); - - // Set content type based on file extension - let content_type = if file_path.ends_with(".mp4") { - "video/mp4" - } else if file_path.ends_with(".mkv") { - "video/x-matroska" - } else if file_path.ends_with(".webm") { - "video/webm" - } else { - "application/octet-stream" - }; - - response_builder = response_builder - .header(header::CONTENT_TYPE, content_type) - .header(header::ACCEPT_RANGES, "bytes") - .header(header::CONTENT_LENGTH, content_length.to_string()) - .header(header::CACHE_CONTROL, "public, max-age=3600"); - - // Set status code based on range request - let status = if start > 0 || end < file_size - 1 { - response_builder = response_builder - .header(header::CONTENT_RANGE, format!("bytes {}-{}/{}", start, end, file_size)); - StatusCode::PARTIAL_CONTENT - } else { - StatusCode::OK - }; - - // Seek to start position if needed - let reader = if start > 0 { - use tokio::io::{AsyncSeekExt, AsyncReadExt}; - let mut file = file; - file.seek(std::io::SeekFrom::Start(start)).await - .map_err(|e| ApiError::Internal(format!("Failed to seek in file: {}", e)))?; - - // Take only the requested range - file.take(content_length) - } else { - file.take(content_length) - }; - - let stream = ReaderStream::new(reader); - let body = axum::body::Body::from_stream(stream); - - let response = response_builder - .status(status) - .body(body) - .map_err(|e| ApiError::Internal(format!("Failed to build response: {}", e)))?; - - Ok(response) -} -*/ /// Get thumbnail for media item pub async fn get_thumbnail( diff --git a/src/models/media.rs b/src/models/media.rs index 8e4b936..80275d9 100644 --- a/src/models/media.rs +++ b/src/models/media.rs @@ -34,67 +34,6 @@ pub struct MediaItem { pub updated_at: Option>, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TranscodedMedia { - pub id: Uuid, - pub media_item_id: Uuid, - - // Format info - pub target_codec: String, - pub target_resolution: Option, - pub target_bitrate: Option, - - // File info - pub file_path: String, - pub file_size: Option, - - // Transcoding status - pub status: TranscodingStatus, - pub transcoded_at: Option>, - pub transcoding_started_at: Option>, - pub error_message: Option, - - // Performance metrics - pub transcoding_duration_seconds: Option, - pub transcoding_method: Option, - - pub created_at: DateTime, - pub updated_at: DateTime, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum TranscodingStatus { - Pending, - Processing, - Completed, - Failed, -} - -impl std::fmt::Display for TranscodingStatus { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TranscodingStatus::Pending => write!(f, "pending"), - TranscodingStatus::Processing => write!(f, "processing"), - TranscodingStatus::Completed => write!(f, "completed"), - TranscodingStatus::Failed => write!(f, "failed"), - } - } -} - -impl std::str::FromStr for TranscodingStatus { - type Err = String; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "pending" => Ok(TranscodingStatus::Pending), - "processing" => Ok(TranscodingStatus::Processing), - "completed" => Ok(TranscodingStatus::Completed), - "failed" => Ok(TranscodingStatus::Failed), - _ => Err(format!("Invalid transcoding status: {}", s)), - } - } -} #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MediaScanStatus { @@ -176,13 +115,6 @@ pub struct CreateMediaItemRequest { pub file_path: String, } -#[derive(Debug, Deserialize)] -pub struct TranscodingRequest { - pub media_item_id: Uuid, - pub target_codec: String, - pub target_resolution: Option, - pub target_bitrate: Option, -} // Query parameters #[derive(Debug, Deserialize)] @@ -220,12 +152,3 @@ impl SanitizeOutput for MediaItemResponse { } } -impl SanitizeOutput for TranscodedMedia { - fn sanitize_output(mut self) -> Self { - self.target_codec = sanitize_string(self.target_codec); - self.target_resolution = sanitize_option_string(self.target_resolution); - self.error_message = sanitize_option_string(self.error_message); - self.transcoding_method = sanitize_option_string(self.transcoding_method); - self - } -} \ No newline at end of file diff --git a/src/utils/media_parsing.rs b/src/utils/media_parsing.rs index f7c2475..50dbc6d 100644 --- a/src/utils/media_parsing.rs +++ b/src/utils/media_parsing.rs @@ -109,6 +109,7 @@ fn split_on_separator(input: &str, separator: char) -> Option<(String, String)> } } + struct ExtractedDate { date: Option, original_text: String, @@ -241,4 +242,5 @@ mod tests { assert_eq!(result2.speaker, Some("Elder Johnson".to_string())); assert_eq!(result2.date, Some(NaiveDate::from_ymd_opt(2023, 12, 15).unwrap())); } + } \ No newline at end of file diff --git a/tests/gstreamer_integration_tests.rs b/tests/gstreamer_integration_tests.rs index daf4030..d995b1b 100644 --- a/tests/gstreamer_integration_tests.rs +++ b/tests/gstreamer_integration_tests.rs @@ -1,4 +1,3 @@ -use church_api::services::unified_transcoding::UnifiedTranscodingService; use gstreamer::prelude::*; use church_api::handlers::smart_streaming::{detect_av1_support, detect_hevc_support}; use church_api::error::{ApiError, Result};