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:
parent
c607aa135c
commit
cabf8aa83d
|
@ -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
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
|
@ -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};
|
||||
|
|
Loading…
Reference in a new issue