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.
This commit is contained in:
Benjamin Slingo 2025-09-08 12:31:17 -04:00
parent c607aa135c
commit cabf8aa83d
5 changed files with 2 additions and 265 deletions

View file

@ -8,5 +8,4 @@ pub struct AppState {
pub jwt_secret: String,
pub mailer: Arc<Mailer>,
pub owncast_service: Option<Arc<OwncastService>>,
// Transcoding services removed - replaced by simple smart streaming
}

View file

@ -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<AppState>,
Path(media_id): Path<uuid::Uuid>,
Query(params): Query<HashMap<String, String>>,
headers: HeaderMap,
) -> Result<impl IntoResponse> {
// 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::<u32>().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::<i32>().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::<u64>().unwrap_or(0);
let end = if parts.len() > 1 && !parts[1].is_empty() {
parts[1].parse::<u64>().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(

View file

@ -34,67 +34,6 @@ pub struct MediaItem {
pub updated_at: Option<DateTime<Utc>>,
}
#[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<String>,
pub target_bitrate: Option<i32>,
// File info
pub file_path: String,
pub file_size: Option<i64>,
// Transcoding status
pub status: TranscodingStatus,
pub transcoded_at: Option<DateTime<Utc>>,
pub transcoding_started_at: Option<DateTime<Utc>>,
pub error_message: Option<String>,
// Performance metrics
pub transcoding_duration_seconds: Option<i32>,
pub transcoding_method: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[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<Self, Self::Err> {
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<String>,
pub target_bitrate: Option<i32>,
}
// 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
}
}

View file

@ -109,6 +109,7 @@ fn split_on_separator(input: &str, separator: char) -> Option<(String, String)>
}
}
struct ExtractedDate {
date: Option<NaiveDate>,
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()));
}
}

View file

@ -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};