aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/src/config.rs66
-rw-r--r--common/src/lib.rs19
-rw-r--r--common/src/stream.rs13
-rw-r--r--stream/src/segment.rs22
-rw-r--r--transcoder/src/snippet.rs33
5 files changed, 103 insertions, 50 deletions
diff --git a/common/src/config.rs b/common/src/config.rs
index aded4ff..e30e70b 100644
--- a/common/src/config.rs
+++ b/common/src/config.rs
@@ -4,7 +4,7 @@
Copyright (C) 2023 metamuffin <metamuffin.org>
*/
-use crate::user::PermissionSet;
+use crate::{user::PermissionSet, EncodingProfile};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::PathBuf};
@@ -19,6 +19,7 @@ pub struct GlobalConfig {
#[serde(default = "default::temp_path")] pub temp_path: PathBuf,
#[serde(default = "default::cache_path")] pub cache_path: PathBuf,
#[serde(default = "default::admin_username")] pub admin_username: String,
+ #[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,
pub admin_password: String,
#[serde(default)] pub cookie_key: Option<String>,
@@ -28,16 +29,61 @@ pub struct GlobalConfig {
#[serde(default)] pub default_permission_set: PermissionSet,
}
-#[rustfmt::skip]
mod default {
+ use crate::EncodingProfile;
use std::path::PathBuf;
- pub fn admin_username() -> String { "admin".into() }
- pub fn login_expire() -> i64 { 60*60*24 }
- pub fn asset_path() -> PathBuf { "data/assets".into() }
- pub fn database_path() -> PathBuf { "data/database".into() }
- pub fn library_path() -> PathBuf { "data/library".into() }
- pub fn cache_path() -> PathBuf { "data/cache".into() }
- pub fn temp_path() -> PathBuf { "/tmp".into() }
- pub fn max_in_memory_cache_size() -> usize { 50_000_000 }
+ pub fn admin_username() -> String {
+ "admin".into()
+ }
+ pub fn login_expire() -> i64 {
+ 60 * 60 * 24
+ }
+ pub fn asset_path() -> PathBuf {
+ "data/assets".into()
+ }
+ pub fn database_path() -> PathBuf {
+ "data/database".into()
+ }
+ pub fn library_path() -> PathBuf {
+ "data/library".into()
+ }
+ pub fn cache_path() -> PathBuf {
+ "data/cache".into()
+ }
+ pub fn temp_path() -> PathBuf {
+ "/tmp".into()
+ }
+ 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: 8,
+ bitrate: 2_000_000,
+ width: 1920,
+ },
+ EncodingProfile::Video {
+ codec: "libsvtav1".to_string(),
+ preset: 8,
+ bitrate: 1_500_000,
+ width: 1280,
+ },
+ EncodingProfile::Audio {
+ codec: "libopus".to_string(),
+ bitrate: 128_000,
+ sample_rate: None,
+ },
+ EncodingProfile::Audio {
+ codec: "libopus".to_string(),
+ bitrate: 64_000,
+ sample_rate: None,
+ },
+ EncodingProfile::Subtitles {
+ codec: "webvtt".to_string(),
+ },
+ ]
+ }
}
diff --git a/common/src/lib.rs b/common/src/lib.rs
index c425c21..6d1a78c 100644
--- a/common/src/lib.rs
+++ b/common/src/lib.rs
@@ -142,3 +142,22 @@ pub enum SourceTrackKind {
},
Subtitles,
}
+
+#[derive(Debug, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub enum EncodingProfile {
+ Video {
+ codec: String,
+ preset: u8,
+ bitrate: usize,
+ width: usize,
+ },
+ Audio {
+ codec: String,
+ bitrate: usize,
+ sample_rate: Option<f64>,
+ },
+ Subtitles {
+ codec: String,
+ },
+}
diff --git a/common/src/stream.rs b/common/src/stream.rs
index 1fb3761..8a7ff36 100644
--- a/common/src/stream.rs
+++ b/common/src/stream.rs
@@ -13,8 +13,7 @@ pub struct StreamSpec {
pub tracks: Vec<usize>,
pub format: StreamFormat,
pub webm: Option<bool>,
- pub bitrate: Option<usize>,
- pub width: Option<usize>,
+ pub profile: Option<usize>,
pub index: Option<usize>,
}
@@ -37,8 +36,7 @@ impl Default for StreamSpec {
tracks: Vec::new(),
format: StreamFormat::Matroska,
webm: Some(true),
- width: None,
- bitrate: None,
+ profile: None,
index: None,
}
}
@@ -62,8 +60,8 @@ impl StreamSpec {
)
.unwrap();
}
- if let Some(bitrate) = self.bitrate {
- write!(u, "&bitrate={bitrate}").unwrap();
+ if let Some(profile) = self.profile {
+ write!(u, "&profile={profile}").unwrap();
}
if let Some(index) = self.index {
write!(u, "&index={index}").unwrap();
@@ -71,9 +69,6 @@ impl StreamSpec {
if let Some(webm) = self.webm {
write!(u, "&webm={webm}").unwrap();
}
- if let Some(width) = self.width {
- write!(u, "&width={width}").unwrap();
- }
u
}
}
diff --git a/stream/src/segment.rs b/stream/src/segment.rs
index fdef01c..51674d7 100644
--- a/stream/src/segment.rs
+++ b/stream/src/segment.rs
@@ -6,7 +6,7 @@
use anyhow::{anyhow, bail, Result};
use jellybase::{AssetLocationExt, CONF};
use jellycommon::{stream::StreamSpec, LocalTrack, Node};
-use jellytranscoder::snippet::{transcode, Encoding};
+use jellytranscoder::snippet::transcode;
use log::warn;
use tokio::{fs::File, io::DuplexStream};
use tokio_util::io::SyncIoBridge;
@@ -23,16 +23,12 @@ pub async fn segment_stream(
let track = spec.tracks[0];
let n = spec.index.ok_or(anyhow!("segment index missing"))?;
- if let Some(width) = spec.width {
- let width = width_steps(width);
+ if let Some(profile) = spec.profile {
let location = transcode(
"",
- Encoding::Video {
- codec: "libsvtav1",
- preset: 8,
- bitrate: spec.bitrate.unwrap_or(2_000_000),
- width,
- },
+ 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_snippet_into(
@@ -75,11 +71,3 @@ pub async fn segment_stream(
Ok(())
}
-
-fn width_steps(width: usize) -> usize {
- match width {
- x if x >= 3840 => 3840,
- x if x >= 1920 => 1920,
- _ => 720,
- }
-}
diff --git a/transcoder/src/snippet.rs b/transcoder/src/snippet.rs
index 3cc9736..9253c2c 100644
--- a/transcoder/src/snippet.rs
+++ b/transcoder/src/snippet.rs
@@ -5,7 +5,7 @@
*/
use jellybase::cache::async_cache_file;
-use jellycommon::AssetLocation;
+use jellycommon::{AssetLocation, EncodingProfile};
use log::info;
use std::process::Stdio;
use tokio::{
@@ -13,19 +13,9 @@ use tokio::{
process::{ChildStdin, Command},
};
-#[derive(Debug)]
-pub enum Encoding {
- Video {
- codec: &'static str,
- preset: u8,
- bitrate: usize,
- width: usize,
- },
-}
-
pub async fn transcode(
key: &str,
- enc: Encoding,
+ enc: &EncodingProfile,
input: impl FnOnce(ChildStdin),
) -> anyhow::Result<AssetLocation> {
Ok(async_cache_file(
@@ -33,7 +23,7 @@ pub async fn transcode(
move |mut output| async move {
info!("transcoding snippet {key}");
let args = match enc {
- Encoding::Video {
+ EncodingProfile::Video {
codec,
preset,
bitrate,
@@ -49,6 +39,21 @@ pub async fn transcode(
format!("{bitrate}"),
]
.to_vec(),
+ EncodingProfile::Audio {
+ codec,
+ bitrate,
+ sample_rate: _,
+ } => [
+ // TODO resample?
+ "-c:a".to_string(),
+ codec.to_string(),
+ "-b:a".to_string(),
+ format!("{bitrate}"),
+ ]
+ .to_vec(),
+ EncodingProfile::Subtitles { codec } => {
+ ["-c:s".to_string(), codec.to_string()].to_vec()
+ }
};
info!("encoding with {:?}", args.join(" "));
@@ -65,7 +70,7 @@ pub async fn transcode(
input(stdin);
copy(&mut stdout, &mut output).await?;
-
+
proc.wait().await.unwrap().exit_ok()?;
info!("done");
Ok(())