use std::str::FromStr; use crate::{ error::ApiError, paths, state::Transcoder, transcode::{Quality, TranscodeError}, utils::get_client_id, }; use actix_files::NamedFile; use actix_web::{get, web, HttpRequest, Result}; /// Transcode video /// /// Transcode the video to the selected quality. /// This route can take a few seconds to respond since it will way for at least one segment to be /// available. #[utoipa::path( responses( (status = 200, description = "Get the m3u8 playlist."), (status = NOT_FOUND, description = "Invalid slug.") ), params( ("resource" = String, Path, description = "Episode or movie"), ("slug" = String, Path, description = "The slug of the movie/episode."), ("quality" = Quality, Path, description = "Specify the quality you want"), ("x-client-id" = String, Header, description = "A unique identify for a player's instance. Used to cancel unused transcode"), ) )] #[get("/{resource}/{slug}/{quality}/index.m3u8")] async fn get_transcoded( req: HttpRequest, query: web::Path<(String, String, String)>, transcoder: web::Data, ) -> Result { let (resource, slug, quality) = query.into_inner(); let quality = Quality::from_str(quality.as_str()).map_err(|_| ApiError::BadRequest { error: "Invalid quality".to_string(), })?; let client_id = get_client_id(req)?; let path = paths::get_path(resource, slug) .await .map_err(|_| ApiError::NotFound)?; // TODO: Handle start_time that is not 0 transcoder .transcode(client_id, path, quality, 0) .await .map_err(|e| match e { TranscodeError::ArgumentError(err) => ApiError::BadRequest { error: err }, TranscodeError::FFmpegError(err) => { eprintln!( "Unhandled ffmpeg error occured while transcoding video: {}", err ); ApiError::InternalError } TranscodeError::ReadError(err) => { eprintln!( "Unhandled read error occured while transcoding video: {}", err ); ApiError::InternalError } }) } /// Get transmuxed chunk /// /// Retrieve a chunk of a transmuxed video. #[utoipa::path( responses( (status = 200, description = "Get a hls chunk."), (status = NOT_FOUND, description = "Invalid slug.") ), params( ("resource" = String, Path, description = "Episode or movie"), ("slug" = String, Path, description = "The slug of the movie/episode."), ("quality" = Quality, Path, description = "Specify the quality you want"), ("chunk" = u32, Path, description = "The number of the chunk"), ("x-client-id" = String, Header, description = "A unique identify for a player's instance. Used to cancel unused transcode"), ) )] #[get("/{resource}/{slug}/{quality}/segments-{chunk}.ts")] async fn get_chunk( req: HttpRequest, query: web::Path<(String, String, String, u32)>, transcoder: web::Data, ) -> Result { let (resource, slug, quality, chunk) = query.into_inner(); let quality = Quality::from_str(quality.as_str()).map_err(|_| ApiError::BadRequest { error: "Invalid quality".to_string(), })?; let client_id = get_client_id(req)?; let path = paths::get_path(resource, slug) .await .map_err(|_| ApiError::NotFound)?; // TODO: Handle start_time that is not 0 transcoder .get_segment(client_id, path, quality, chunk) .await .map_err(|_| ApiError::BadRequest { error: "No transcode started for the selected show/quality.".to_string(), }) .and_then(|path| { NamedFile::open(path).map_err(|_| ApiError::BadRequest { error: "Invalid segment number.".to_string(), }) }) }