mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Implement a naive identify invoking mediainfo
This commit is contained in:
parent
f42eaeb953
commit
f7f40be956
7
transcoder/Cargo.lock
generated
7
transcoder/Cargo.lock
generated
@ -673,6 +673,12 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "json"
|
||||||
|
version = "0.12.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "language-tags"
|
name = "language-tags"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@ -1290,6 +1296,7 @@ dependencies = [
|
|||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
|
"json",
|
||||||
"rand",
|
"rand",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -12,3 +12,4 @@ rand = "0.8.5"
|
|||||||
derive_more = "0.99.17"
|
derive_more = "0.99.17"
|
||||||
reqwest = { version = "0.11.16", default_features = false, features = ["json", "rustls-tls"] }
|
reqwest = { version = "0.11.16", default_features = false, features = ["json", "rustls-tls"] }
|
||||||
utoipa = { version = "3", features = ["actix_extras"] }
|
utoipa = { version = "3", features = ["actix_extras"] }
|
||||||
|
json = "0.12.4"
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use std::str;
|
||||||
|
use tokio::process::Command;
|
||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::transcode::Quality;
|
use crate::transcode::Quality;
|
||||||
@ -20,7 +22,7 @@ pub struct VideoTrack {
|
|||||||
/// The codec of this stream (defined as the RFC 6381).
|
/// The codec of this stream (defined as the RFC 6381).
|
||||||
codec: String,
|
codec: String,
|
||||||
/// The language of this stream (as a ISO-639-2 language code)
|
/// The language of this stream (as a ISO-639-2 language code)
|
||||||
language: String,
|
language: Option<String>,
|
||||||
/// The max quality of this video track.
|
/// The max quality of this video track.
|
||||||
quality: Quality,
|
quality: Quality,
|
||||||
/// The width of the video stream
|
/// The width of the video stream
|
||||||
@ -28,10 +30,7 @@ pub struct VideoTrack {
|
|||||||
/// The height of the video stream
|
/// The height of the video stream
|
||||||
height: u32,
|
height: u32,
|
||||||
/// The average bitrate of the video in bytes/s
|
/// The average bitrate of the video in bytes/s
|
||||||
average_bitrate: u32,
|
bitrate: u32,
|
||||||
// TODO: Figure out if this is doable
|
|
||||||
/// The max bitrate of the video in bytes/s
|
|
||||||
max_bitrate: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, ToSchema)]
|
#[derive(Serialize, ToSchema)]
|
||||||
@ -39,9 +38,9 @@ pub struct Track {
|
|||||||
/// The index of this track on the media.
|
/// The index of this track on the media.
|
||||||
index: u32,
|
index: u32,
|
||||||
/// The title of the stream.
|
/// The title of the stream.
|
||||||
title: String,
|
title: Option<String>,
|
||||||
/// The language of this stream (as a ISO-639-2 language code)
|
/// The language of this stream (as a ISO-639-2 language code)
|
||||||
language: String,
|
language: Option<String>,
|
||||||
/// The codec of this stream.
|
/// The codec of this stream.
|
||||||
codec: String,
|
codec: String,
|
||||||
/// Is this stream the default one of it's type?
|
/// 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...
|
name: String, // TODO: add a type field for Opening, Credits...
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn identify(_path: String) -> Result<MediaInfo, std::io::Error> {
|
pub async fn identify(path: String) -> Result<MediaInfo, std::io::Error> {
|
||||||
todo!()
|
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![],
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ async fn identify_resource(
|
|||||||
.await
|
.await
|
||||||
.map_err(|_| ApiError::NotFound)?;
|
.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);
|
eprintln!("Unhandled error occured while transcoding: {}", e);
|
||||||
ApiError::InternalError
|
ApiError::InternalError
|
||||||
})
|
})
|
||||||
|
@ -14,7 +14,7 @@ use tokio::sync::watch;
|
|||||||
|
|
||||||
const SEGMENT_TIME: u32 = 10;
|
const SEGMENT_TIME: u32 = 10;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Serialize, Display)]
|
#[derive(PartialEq, Eq, Serialize, Display, Clone, Copy)]
|
||||||
pub enum Quality {
|
pub enum Quality {
|
||||||
#[display(fmt = "240p")]
|
#[display(fmt = "240p")]
|
||||||
P240,
|
P240,
|
||||||
@ -95,6 +95,13 @@ impl Quality {
|
|||||||
Self::Original => panic!("Original quality must be handled specially"),
|
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)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user