From f7f40be9569dda05676f0d718667c335ac793110 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 25 May 2023 15:30:30 +0900 Subject: [PATCH] Implement a naive identify invoking mediainfo --- transcoder/Cargo.lock | 7 ++++ transcoder/Cargo.toml | 1 + transcoder/src/identify.rs | 77 ++++++++++++++++++++++++++++++++----- transcoder/src/main.rs | 2 +- transcoder/src/transcode.rs | 9 ++++- 5 files changed, 85 insertions(+), 11 deletions(-) diff --git a/transcoder/Cargo.lock b/transcoder/Cargo.lock index ae6510ff..abec39ae 100644 --- a/transcoder/Cargo.lock +++ b/transcoder/Cargo.lock @@ -673,6 +673,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "language-tags" version = "0.3.2" @@ -1290,6 +1296,7 @@ dependencies = [ "actix-files", "actix-web", "derive_more", + "json", "rand", "reqwest", "serde", diff --git a/transcoder/Cargo.toml b/transcoder/Cargo.toml index 88d9c1d7..23d66708 100644 --- a/transcoder/Cargo.toml +++ b/transcoder/Cargo.toml @@ -12,3 +12,4 @@ rand = "0.8.5" derive_more = "0.99.17" reqwest = { version = "0.11.16", default_features = false, features = ["json", "rustls-tls"] } utoipa = { version = "3", features = ["actix_extras"] } +json = "0.12.4" diff --git a/transcoder/src/identify.rs b/transcoder/src/identify.rs index 00ddd1a4..db54ed8a 100644 --- a/transcoder/src/identify.rs +++ b/transcoder/src/identify.rs @@ -1,4 +1,6 @@ use serde::Serialize; +use std::str; +use tokio::process::Command; use utoipa::ToSchema; use crate::transcode::Quality; @@ -20,7 +22,7 @@ pub struct VideoTrack { /// The codec of this stream (defined as the RFC 6381). codec: String, /// The language of this stream (as a ISO-639-2 language code) - language: String, + language: Option, /// The max quality of this video track. quality: Quality, /// The width of the video stream @@ -28,10 +30,7 @@ pub struct VideoTrack { /// The height of the video stream height: u32, /// The average bitrate of the video in bytes/s - average_bitrate: u32, - // TODO: Figure out if this is doable - /// The max bitrate of the video in bytes/s - max_bitrate: u32, + bitrate: u32, } #[derive(Serialize, ToSchema)] @@ -39,9 +38,9 @@ pub struct Track { /// The index of this track on the media. index: u32, /// The title of the stream. - title: String, + title: Option, /// The language of this stream (as a ISO-639-2 language code) - language: String, + language: Option, /// The codec of this stream. codec: String, /// Is this stream the default one of it's type? @@ -60,6 +59,66 @@ pub struct Chapter { name: String, // TODO: add a type field for Opening, Credits... } -pub fn identify(_path: String) -> Result { - todo!() +pub async fn identify(path: String) -> Result { + let mediainfo = Command::new("mediainfo") + .arg("--Output=JSON") + .arg("--Language=raw") + .arg(path) + .output() + .await?; + assert!(mediainfo.status.success()); + let output = json::parse(str::from_utf8(mediainfo.stdout.as_slice()).unwrap()).unwrap(); + + let general = output["media"]["tracks"] + .members() + .find(|x| x["@type"] == "General") + .unwrap(); + + Ok(MediaInfo { + length: general["Duration"].as_f32().unwrap(), + container: general["Format"].as_str().unwrap().to_string(), + video: { + let v = output["media"]["tracks"] + .members() + .find(|x| x["@type"] == "Video") + .expect("File without video found. This is not supported"); + VideoTrack { + // This codec is not in the right format (does not include bitdepth...). + codec: v["Format"].as_str().unwrap().to_string(), + language: v["Language"].as_str().map(|x| x.to_string()), + quality: Quality::from_height(v["Height"].as_u32().unwrap()), + width: v["Width"].as_u32().unwrap(), + height: v["Height"].as_u32().unwrap(), + bitrate: v["BitRate"].as_u32().unwrap(), + } + }, + audios: output["media"]["tracks"] + .members() + .filter(|x| x["@type"] == "Audio") + .map(|a| Track { + index: a["StreamOrder"].as_u32().unwrap(), + title: a["Title"].as_str().map(|x| x.to_string()), + language: a["Language"].as_str().map(|x| x.to_string()), + // TODO: format is invalid. Channels count missing... + codec: a["Format"].as_str().unwrap().to_string(), + default: a["Default"] == "Yes", + forced: a["Forced"] == "No", + }) + .collect(), + subtitles: output["media"]["tracks"] + .members() + .filter(|x| x["@type"] == "Text") + .map(|a| Track { + index: a["StreamOrder"].as_u32().unwrap(), + title: a["Title"].as_str().map(|x| x.to_string()), + language: a["Language"].as_str().map(|x| x.to_string()), + // TODO: format is invalid. Channels count missing... + codec: a["Format"].as_str().unwrap().to_string(), + default: a["Default"] == "Yes", + forced: a["Forced"] == "No", + }) + .collect(), + fonts: vec![], + chapters: vec![], + }) } diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index e841d629..769a83d5 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -93,7 +93,7 @@ async fn identify_resource( .await .map_err(|_| ApiError::NotFound)?; - identify(path).map(|info| Json(info)).map_err(|e| { + identify(path).await.map(|info| Json(info)).map_err(|e| { eprintln!("Unhandled error occured while transcoding: {}", e); ApiError::InternalError }) diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index 13fc3532..730d3331 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -14,7 +14,7 @@ use tokio::sync::watch; const SEGMENT_TIME: u32 = 10; -#[derive(PartialEq, Eq, Serialize, Display)] +#[derive(PartialEq, Eq, Serialize, Display, Clone, Copy)] pub enum Quality { #[display(fmt = "240p")] P240, @@ -95,6 +95,13 @@ impl Quality { Self::Original => panic!("Original quality must be handled specially"), } } + + pub fn from_height(height: u32) -> Self { + Self::iter() + .find(|x| x.height() <= height) + .unwrap_or(&Quality::P240) + .clone() + } } #[derive(Debug, PartialEq, Eq)]