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 jwt_secret: String,
|
||||||
pub mailer: Arc<Mailer>,
|
pub mailer: Arc<Mailer>,
|
||||||
pub owncast_service: Option<Arc<OwncastService>>,
|
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::error::{Result, ApiError};
|
||||||
use crate::models::media::{MediaItem, MediaItemResponse};
|
use crate::models::media::{MediaItem, MediaItemResponse};
|
||||||
use crate::models::ApiResponse;
|
use crate::models::ApiResponse;
|
||||||
// TranscodingJob import removed - never released transcoding nightmare eliminated
|
|
||||||
use crate::utils::response::success_response;
|
use crate::utils::response::success_response;
|
||||||
use crate::{AppState, sql};
|
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
|
/// Get thumbnail for media item
|
||||||
pub async fn get_thumbnail(
|
pub async fn get_thumbnail(
|
||||||
|
|
|
@ -34,67 +34,6 @@ pub struct MediaItem {
|
||||||
pub updated_at: Option<DateTime<Utc>>,
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct MediaScanStatus {
|
pub struct MediaScanStatus {
|
||||||
|
@ -176,13 +115,6 @@ pub struct CreateMediaItemRequest {
|
||||||
pub file_path: String,
|
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
|
// Query parameters
|
||||||
#[derive(Debug, Deserialize)]
|
#[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 {
|
struct ExtractedDate {
|
||||||
date: Option<NaiveDate>,
|
date: Option<NaiveDate>,
|
||||||
original_text: String,
|
original_text: String,
|
||||||
|
@ -241,4 +242,5 @@ mod tests {
|
||||||
assert_eq!(result2.speaker, Some("Elder Johnson".to_string()));
|
assert_eq!(result2.speaker, Some("Elder Johnson".to_string()));
|
||||||
assert_eq!(result2.date, Some(NaiveDate::from_ymd_opt(2023, 12, 15).unwrap()));
|
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 gstreamer::prelude::*;
|
||||||
use church_api::handlers::smart_streaming::{detect_av1_support, detect_hevc_support};
|
use church_api::handlers::smart_streaming::{detect_av1_support, detect_hevc_support};
|
||||||
use church_api::error::{ApiError, Result};
|
use church_api::error::{ApiError, Result};
|
||||||
|
|
Loading…
Reference in a new issue