aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/src/config.rs61
-rw-r--r--common/src/jhls.rs27
-rw-r--r--common/src/stream.rs3
-rw-r--r--stream/src/fragment.rs99
-rw-r--r--stream/src/lib.rs3
-rw-r--r--transcoder/src/fragment.rs94
6 files changed, 124 insertions, 163 deletions
diff --git a/common/src/config.rs b/common/src/config.rs
index d7682df..a0dc459 100644
--- a/common/src/config.rs
+++ b/common/src/config.rs
@@ -4,7 +4,7 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use crate::{jhls::EncodingProfile, user::PermissionSet};
+use crate::user::PermissionSet;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::PathBuf};
@@ -20,11 +20,30 @@ pub struct GlobalConfig {
#[serde(default = "default::cache_path")] pub cache_path: PathBuf,
#[serde(default = "default::media_path")] pub media_path: PathBuf,
#[serde(default = "default::secrets_path")] pub secrets_path: PathBuf,
- #[serde(default = "default::transcoding_profiles")] pub transcoding_profiles: Vec<EncodingProfile>,
#[serde(default = "default::max_in_memory_cache_size")] pub max_in_memory_cache_size: usize,
#[serde(default)] pub admin_username: Option<String>,
#[serde(default = "default::login_expire")] pub login_expire: i64,
#[serde(default)] pub default_permission_set: PermissionSet,
+ #[serde(default)] encoders: EncoderPreferences,
+}
+
+#[derive(Debug, Deserialize, Serialize, Default)]
+pub struct EncoderPreferences {
+ avc: Option<EncoderClass>,
+ hevc: Option<EncoderClass>,
+ vp8: Option<EncoderClass>,
+ vp9: Option<EncoderClass>,
+ av1: Option<EncoderClass>,
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+enum EncoderClass {
+ Aom,
+ Svt,
+ X26n,
+ Vpx,
+ Vaapi,
+ Rkmpp,
}
#[derive(Serialize, Deserialize, Debug, Default)]
@@ -59,7 +78,6 @@ pub struct ApiSecrets {
}
mod default {
- use crate::jhls::EncodingProfile;
use std::path::PathBuf;
pub fn login_expire() -> i64 {
@@ -83,43 +101,6 @@ mod default {
pub fn max_in_memory_cache_size() -> usize {
50_000_000
}
- pub fn transcoding_profiles() -> Vec<EncodingProfile> {
- vec![
- EncodingProfile::Video {
- codec: "libsvtav1".to_string(),
- preset: Some(8),
- bitrate: 2_000_000,
- width: Some(1920),
- },
- EncodingProfile::Video {
- codec: "libsvtav1".to_string(),
- preset: Some(8),
- bitrate: 1_200_000,
- width: Some(1280),
- },
- EncodingProfile::Video {
- codec: "libsvtav1".to_string(),
- preset: Some(8),
- bitrate: 300_000,
- width: Some(640),
- },
- EncodingProfile::Audio {
- codec: "libopus".to_string(),
- bitrate: 128_000,
- sample_rate: None,
- channels: Some(2),
- },
- EncodingProfile::Audio {
- codec: "libopus".to_string(),
- bitrate: 64_000,
- sample_rate: None,
- channels: Some(2),
- },
- EncodingProfile::Subtitles {
- codec: "webvtt".to_string(),
- },
- ]
- }
}
fn return_true() -> bool {
diff --git a/common/src/jhls.rs b/common/src/jhls.rs
index 6dc976b..90f48f5 100644
--- a/common/src/jhls.rs
+++ b/common/src/jhls.rs
@@ -5,33 +5,6 @@
*/
use bincode::{Decode, Encode};
use serde::{Deserialize, Serialize};
-use std::ops::Range;
-
-#[derive(Debug, Clone, Deserialize, Serialize)]
-pub struct JhlsTrackIndex {
- pub extra_profiles: Vec<EncodingProfile>,
- pub fragments: Vec<Range<f64>>,
-}
-
-#[derive(Debug, Clone, Deserialize, Serialize)]
-#[serde(rename_all = "snake_case")]
-pub enum EncodingProfile {
- Video {
- codec: String,
- preset: Option<u8>,
- bitrate: usize,
- width: Option<usize>,
- },
- Audio {
- codec: String,
- bitrate: usize,
- channels: Option<usize>,
- sample_rate: Option<f64>,
- },
- Subtitles {
- codec: String,
- },
-}
#[derive(Debug, Serialize, Deserialize, Encode, Decode)]
pub struct SubtitleCue {
diff --git a/common/src/stream.rs b/common/src/stream.rs
index 75349cc..a14fd57 100644
--- a/common/src/stream.rs
+++ b/common/src/stream.rs
@@ -91,7 +91,8 @@ pub struct StreamFormatInfo {
pub remux: bool,
pub containers: Vec<StreamContainer>,
- pub pixel_count: Option<u64>,
+ pub width: Option<u64>,
+ pub height: Option<u64>,
pub samplerate: Option<f64>,
pub channels: Option<usize>,
pub bit_depth: Option<u8>,
diff --git a/stream/src/fragment.rs b/stream/src/fragment.rs
index 52d32f4..b2e254b 100644
--- a/stream/src/fragment.rs
+++ b/stream/src/fragment.rs
@@ -6,9 +6,10 @@
use crate::{stream_info, SMediaInfo};
use anyhow::{anyhow, Result};
use jellybase::common::stream::StreamContainer;
+use jellytranscoder::fragment::transcode;
use log::warn;
use std::sync::Arc;
-use tokio::io::DuplexStream;
+use tokio::{fs::File, io::DuplexStream};
use tokio_util::io::SyncIoBridge;
pub async fn fragment_stream(
@@ -17,7 +18,7 @@ pub async fn fragment_stream(
track: usize,
segment: usize,
index: usize,
- format: usize,
+ format_num: usize,
container: StreamContainer,
) -> Result<()> {
let (iinfo, info) = stream_info(info).await?;
@@ -26,52 +27,56 @@ pub async fn fragment_stream(
.get(track)
.ok_or(anyhow!("track not found"))?;
let path = iinfo.paths[file_index].clone();
+ let seg = info
+ .segments
+ .get(segment)
+ .ok_or(anyhow!("segment not found"))?;
+ let track = seg.tracks.get(track).ok_or(anyhow!("track not found"))?;
+ let format = track
+ .formats
+ .get(format_num)
+ .ok_or(anyhow!("format not found"))?;
- // if let Some(profile) = None {
- // perms.assert(&UserPermission::Transcode)?;
- // let location = transcode(
- // &format!("{track} {index} {:?}", node), // TODO maybe not use the entire source
- // CONF.transcoding_profiles
- // .get(profile)
- // .ok_or(anyhow!("profile out of range"))?,
- // move |b| {
- // tokio::task::spawn_blocking(move || {
- // if let Err(err) = jellyremuxer::write_fragment_into(
- // SyncIoBridge::new(b),
- // &CONF.media_path,
- // &node,
- // &local_track,
- // track as usize,
- // false,
- // index,
- // ) {
- // warn!("segment stream error: {err}");
- // }
- // });
- // },
- // )
- // .await?;
- // let mut output = File::open(location.abs()).await?;
- // tokio::task::spawn(async move {
- // if let Err(err) = tokio::io::copy(&mut output, &mut b).await {
- // warn!("cannot write stream: {err}")
- // }
- // });
- // } else {
- let b = SyncIoBridge::new(b);
- tokio::task::spawn_blocking(move || {
- if let Err(err) = jellyremuxer::write_fragment_into(
- b,
- &path,
- track_num,
- container == StreamContainer::WebM,
- &info.name.unwrap_or_default(),
- index,
- ) {
- warn!("segment stream error: {err}");
- }
- });
- // }
+ if format.remux {
+ tokio::task::spawn_blocking(move || {
+ if let Err(err) = jellyremuxer::write_fragment_into(
+ SyncIoBridge::new(b),
+ &path,
+ track_num,
+ container == StreamContainer::WebM,
+ &info.name.unwrap_or_default(),
+ index,
+ ) {
+ warn!("segment stream error: {err}");
+ }
+ });
+ } else {
+ let location = transcode(
+ &format!("{path:?} {track_num} {index} {format_num} {container}"), // TODO maybe not use the entire source
+ format,
+ move |b| {
+ tokio::task::spawn_blocking(move || {
+ if let Err(err) = jellyremuxer::write_fragment_into(
+ SyncIoBridge::new(b),
+ &path,
+ track_num,
+ container == StreamContainer::WebM,
+ &info.name.unwrap_or_default(),
+ index,
+ ) {
+ warn!("segment stream error: {err}");
+ }
+ });
+ },
+ )
+ .await?;
+ let mut output = File::open(location.abs()).await?;
+ tokio::task::spawn(async move {
+ if let Err(err) = tokio::io::copy(&mut output, &mut b).await {
+ warn!("cannot write stream: {err}")
+ }
+ });
+ }
Ok(())
}
diff --git a/stream/src/lib.rs b/stream/src/lib.rs
index a6faf54..eb56529 100644
--- a/stream/src/lib.rs
+++ b/stream/src/lib.rs
@@ -136,7 +136,8 @@ async fn stream_info(info: Arc<SMediaInfo>) -> Result<(InternalStreamInfo, Strea
bit_depth: t.audio.as_ref().and_then(|a| a.bit_depth.map(|e| e as u8)),
samplerate: t.audio.as_ref().map(|a| a.sampling_frequency),
channels: t.audio.as_ref().map(|a| a.channels as usize),
- pixel_count: t.video.as_ref().map(|v| v.pixel_width * v.pixel_height),
+ width: t.video.as_ref().map(|v| v.pixel_width),
+ height: t.video.as_ref().map(|v| v.pixel_height),
..Default::default()
});
tracks.push(StreamTrackInfo {
diff --git a/transcoder/src/fragment.rs b/transcoder/src/fragment.rs
index ff6a9db..b88339c 100644
--- a/transcoder/src/fragment.rs
+++ b/transcoder/src/fragment.rs
@@ -7,7 +7,7 @@
use crate::LOCAL_VIDEO_TRANSCODING_TASKS;
use jellybase::{
cache::{async_cache_file, CachePath},
- common::jhls::EncodingProfile,
+ common::stream::StreamFormatInfo,
};
use log::{debug, info};
use std::process::Stdio;
@@ -21,7 +21,7 @@ use tokio::{
pub async fn transcode(
key: &str,
- enc: &EncodingProfile,
+ enc: &StreamFormatInfo,
input: impl FnOnce(ChildStdin),
) -> anyhow::Result<CachePath> {
async_cache_file(
@@ -30,51 +30,51 @@ pub async fn transcode(
let _permit = LOCAL_VIDEO_TRANSCODING_TASKS.acquire().await?;
debug!("transcoding fragment with {enc:?}");
- let mut args = Vec::new();
- match enc {
- EncodingProfile::Video {
- codec,
- preset,
- bitrate,
- width,
- } => {
- if let Some(width) = width {
- args.push("-vf".to_string());
- args.push(format!("scale={width}:-1"));
- }
- args.push("-c:v".to_string());
- args.push(codec.to_string());
- if let Some(preset) = preset {
- args.push("-preset".to_string());
- args.push(format!("{preset}"));
- }
- args.push("-b:v".to_string());
- args.push(format!("{bitrate}"));
- }
- EncodingProfile::Audio {
- codec,
- bitrate,
- sample_rate,
- channels,
- } => {
- if let Some(channels) = channels {
- args.push("-ac".to_string());
- args.push(format!("{channels}"))
- }
- if let Some(sample_rate) = sample_rate {
- args.push("-ar".to_string());
- args.push(format!("{sample_rate}"))
- }
- args.push("-c:a".to_string());
- args.push(codec.to_string());
- args.push("-b:a".to_string());
- args.push(format!("{bitrate}"));
- }
- EncodingProfile::Subtitles { codec } => {
- args.push("-c:s".to_string());
- args.push(codec.to_string());
- }
- };
+ let mut args = Vec::<String>::new();
+ // match enc {
+ // EncodingProfile::Video {
+ // codec,
+ // preset,
+ // bitrate,
+ // width,
+ // } => {
+ // if let Some(width) = width {
+ // args.push("-vf".to_string());
+ // args.push(format!("scale={width}:-1"));
+ // }
+ // args.push("-c:v".to_string());
+ // args.push(codec.to_string());
+ // if let Some(preset) = preset {
+ // args.push("-preset".to_string());
+ // args.push(format!("{preset}"));
+ // }
+ // args.push("-b:v".to_string());
+ // args.push(format!("{bitrate}"));
+ // }
+ // EncodingProfile::Audio {
+ // codec,
+ // bitrate,
+ // sample_rate,
+ // channels,
+ // } => {
+ // if let Some(channels) = channels {
+ // args.push("-ac".to_string());
+ // args.push(format!("{channels}"))
+ // }
+ // if let Some(sample_rate) = sample_rate {
+ // args.push("-ar".to_string());
+ // args.push(format!("{sample_rate}"))
+ // }
+ // args.push("-c:a".to_string());
+ // args.push(codec.to_string());
+ // args.push("-b:a".to_string());
+ // args.push(format!("{bitrate}"));
+ // }
+ // EncodingProfile::Subtitles { codec } => {
+ // args.push("-c:s".to_string());
+ // args.push(codec.to_string());
+ // }
+ // };
info!("encoding with {:?}", args.join(" "));
let mut proc = Command::new("ffmpeg")