mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Add transmux to the hls playlist
This commit is contained in:
parent
dbe85322ea
commit
63f7a75394
@ -9,55 +9,55 @@ use crate::transcode::Quality;
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct MediaInfo {
|
||||
/// The length of the media in seconds.
|
||||
length: f32,
|
||||
container: String,
|
||||
video: VideoTrack,
|
||||
audios: Vec<Track>,
|
||||
subtitles: Vec<Track>,
|
||||
fonts: Vec<String>,
|
||||
chapters: Vec<Chapter>,
|
||||
pub length: f32,
|
||||
pub container: String,
|
||||
pub video: VideoTrack,
|
||||
pub audios: Vec<Track>,
|
||||
pub subtitles: Vec<Track>,
|
||||
pub fonts: Vec<String>,
|
||||
pub chapters: Vec<Chapter>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct VideoTrack {
|
||||
/// The codec of this stream (defined as the RFC 6381).
|
||||
codec: String,
|
||||
pub codec: String,
|
||||
/// The language of this stream (as a ISO-639-2 language code)
|
||||
language: Option<String>,
|
||||
pub language: Option<String>,
|
||||
/// The max quality of this video track.
|
||||
quality: Quality,
|
||||
pub quality: Quality,
|
||||
/// The width of the video stream
|
||||
width: u32,
|
||||
pub width: u32,
|
||||
/// The height of the video stream
|
||||
height: u32,
|
||||
pub height: u32,
|
||||
/// The average bitrate of the video in bytes/s
|
||||
bitrate: u32,
|
||||
pub bitrate: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct Track {
|
||||
/// The index of this track on the media.
|
||||
index: u32,
|
||||
pub index: u32,
|
||||
/// The title of the stream.
|
||||
title: Option<String>,
|
||||
pub title: Option<String>,
|
||||
/// The language of this stream (as a ISO-639-2 language code)
|
||||
language: Option<String>,
|
||||
pub language: Option<String>,
|
||||
/// The codec of this stream.
|
||||
codec: String,
|
||||
pub codec: String,
|
||||
/// Is this stream the default one of it's type?
|
||||
default: bool,
|
||||
pub default: bool,
|
||||
/// Is this stream tagged as forced? (useful only for subtitles)
|
||||
forced: bool,
|
||||
pub forced: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct Chapter {
|
||||
/// The start time of the chapter (in second from the start of the episode).
|
||||
start: f32,
|
||||
pub start: f32,
|
||||
/// The end time of the chapter (in second from the start of the episode).
|
||||
end: f32,
|
||||
pub end: f32,
|
||||
/// The name of this chapter. This should be a human-readable name that could be presented to the user.
|
||||
name: String, // TODO: add a type field for Opening, Credits...
|
||||
pub name: String, // TODO: add a type field for Opening, Credits...
|
||||
}
|
||||
|
||||
pub async fn identify(path: String) -> Result<MediaInfo, std::io::Error> {
|
||||
@ -80,7 +80,6 @@ pub async fn identify(path: String) -> Result<MediaInfo, std::io::Error> {
|
||||
v.as_str().and_then(|x| x.parse::<F>().ok())
|
||||
}
|
||||
|
||||
// TODO: Every number is wrapped by "" in mediainfo json's mode so the number parsing is wrong.
|
||||
Ok(MediaInfo {
|
||||
length: parse::<f32>(&general["Duration"]).unwrap(),
|
||||
container: general["Format"].as_str().unwrap().to_string(),
|
||||
|
@ -68,7 +68,10 @@ async fn get_master(
|
||||
transcoder: web::Data<Transcoder>,
|
||||
) -> Result<String, ApiError> {
|
||||
let (resource, slug) = query.into_inner();
|
||||
Ok(transcoder.build_master(resource, slug).await)
|
||||
transcoder
|
||||
.build_master(resource, slug)
|
||||
.await
|
||||
.ok_or(ApiError::InternalError)
|
||||
}
|
||||
|
||||
/// Identify
|
||||
@ -84,7 +87,7 @@ async fn get_master(
|
||||
("slug" = String, Path, description = "The slug of the movie/episode."),
|
||||
)
|
||||
)]
|
||||
#[get("/{resource}/{slug}/identify")]
|
||||
#[get("/{resource}/{slug}/info")]
|
||||
async fn identify_resource(
|
||||
query: web::Path<(String, String)>,
|
||||
) -> Result<Json<MediaInfo>, ApiError> {
|
||||
@ -94,7 +97,10 @@ async fn identify_resource(
|
||||
.map_err(|_| ApiError::NotFound)?;
|
||||
|
||||
identify(path).await.map(|info| Json(info)).map_err(|e| {
|
||||
eprintln!("Unhandled error occured while identifing the resource: {}", e);
|
||||
eprintln!(
|
||||
"Unhandled error occured while identifing the resource: {}",
|
||||
e
|
||||
);
|
||||
ApiError::InternalError
|
||||
})
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
use crate::transcode::*;
|
||||
use crate::identify::identify;
|
||||
use crate::paths::get_path;
|
||||
use crate::utils::Signalable;
|
||||
use crate::transcode::*;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::RwLock;
|
||||
@ -17,15 +19,30 @@ impl Transcoder {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn build_master(&self, _resource: String, _slug: String) -> String {
|
||||
pub async fn build_master(&self, resource: String, slug: String) -> Option<String> {
|
||||
let mut master = String::from("#EXTM3U\n");
|
||||
// TODO: Add transmux (original quality) in this master playlist.
|
||||
// Transmux should be the first variant since it's used to test bandwidth
|
||||
// and serve as a hint for preffered variant for clients.
|
||||
let path = get_path(resource, slug).await.ok()?;
|
||||
let info = identify(path).await.ok()?;
|
||||
|
||||
// TODO: Fetch kyoo to retrieve the max quality and the aspect_ratio
|
||||
let aspect_ratio = 16.0 / 9.0;
|
||||
for quality in Quality::iter() {
|
||||
// TODO: Only add this if transmuxing is possible.
|
||||
if true {
|
||||
// Doc: https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/creating_a_multivariant_playlist
|
||||
master.push_str("#EXT-X-STREAM-INF:");
|
||||
master.push_str(format!("AVERAGE-BANDWIDTH={},", info.video.bitrate).as_str());
|
||||
// Approximate a bit more because we can't know the maximum bandwidth.
|
||||
master.push_str(format!("BANDWIDTH={},", (info.video.bitrate as f32 * 1.2) as u32).as_str());
|
||||
master.push_str(
|
||||
format!("RESOLUTION={}x{},", info.video.width, info.video.height).as_str(),
|
||||
);
|
||||
// TODO: Find codecs in the RFC 6381 format.
|
||||
// master.push_str("CODECS=\"avc1.640028\",");
|
||||
// TODO: With multiple audio qualities, maybe switch qualities depending on the video quality.
|
||||
master.push_str("AUDIO=\"audio\"\n");
|
||||
master.push_str(format!("./{}/index.m3u8\n", Quality::Original).as_str());
|
||||
}
|
||||
|
||||
let aspect_ratio = info.video.width as f32 / info.video.height as f32;
|
||||
for quality in Quality::iter().filter(|x| x.height() <= info.video.quality.height()) {
|
||||
// Doc: https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/creating_a_multivariant_playlist
|
||||
master.push_str("#EXT-X-STREAM-INF:");
|
||||
master.push_str(format!("AVERAGE-BANDWIDTH={},", quality.average_bitrate()).as_str());
|
||||
@ -39,26 +56,29 @@ impl Transcoder {
|
||||
.as_str(),
|
||||
);
|
||||
master.push_str("CODECS=\"avc1.640028\",");
|
||||
// With multiple audio qualities, maybe switch qualities depending on the video quality.
|
||||
// TODO: With multiple audio qualities, maybe switch qualities depending on the video quality.
|
||||
master.push_str("AUDIO=\"audio\"\n");
|
||||
master.push_str(format!("./{}/index.m3u8\n", quality).as_str());
|
||||
}
|
||||
// TODO: Fetch audio stream list/metadata from kyoo.
|
||||
for audio in vec![0] {
|
||||
for audio in info.audios {
|
||||
// Doc: https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/adding_alternate_media_to_a_playlist
|
||||
master.push_str("#EXT-X-MEDIA:TYPE=AUDIO,");
|
||||
// The group-id allows to distinguish multiple qualities from multiple variants.
|
||||
// We could create another quality set and use group-ids hiqual and lowqual.
|
||||
master.push_str("GROUP-ID=\"audio\",");
|
||||
// master.push_str(format!("LANGUAGE=\"{}\",", "eng").as_str());
|
||||
master.push_str(format!("NAME=\"{}\",", "Default").as_str());
|
||||
if let Some(language) = audio.language {
|
||||
master.push_str(format!("LANGUAGE=\"{}\",", language).as_str());
|
||||
}
|
||||
if let Some(title) = audio.title {
|
||||
master.push_str(format!("NAME=\"{}\",", title).as_str());
|
||||
}
|
||||
// TODO: Support aac5.1 (and specify the number of channel bellow)
|
||||
// master.push_str(format!("CHANNELS=\"{}\",", 2).as_str());
|
||||
master.push_str("DEFAULT=YES,");
|
||||
master.push_str(format!("URI=\"./audio/{}/index.m3u8\"\n", audio).as_str());
|
||||
master.push_str(format!("URI=\"./audio/{}/index.m3u8\"\n", audio.index).as_str());
|
||||
}
|
||||
|
||||
master
|
||||
Some(master)
|
||||
}
|
||||
|
||||
pub async fn transcode(
|
||||
|
Loading…
x
Reference in New Issue
Block a user