almost transcoding

This commit is contained in:
2026-02-16 22:05:54 -06:00
parent 48e121b042
commit 62d408337e
2 changed files with 62 additions and 9 deletions

View File

@@ -7,9 +7,9 @@ pub struct Config {
pub base_url: String, pub base_url: String,
pub access_token: Option<String>, pub access_token: Option<String>,
pub user_id: Option<String>, pub user_id: Option<String>,
pub transcode_mbps: f32,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum ConfigError { pub enum ConfigError {
NotFound(PathBuf), NotFound(PathBuf),
@@ -45,6 +45,7 @@ fn parse_config(s: &str) -> Result<Config, String> {
let mut base_url: Option<String> = None; let mut base_url: Option<String> = None;
let mut access_token: Option<String> = None; let mut access_token: Option<String> = None;
let mut user_id: Option<String> = None; let mut user_id: Option<String> = None;
let mut transcode_mbps: Option<f32> = None;
for (i, line) in s.lines().enumerate() { for (i, line) in s.lines().enumerate() {
let line = line.trim(); let line = line.trim();
@@ -68,6 +69,12 @@ fn parse_config(s: &str) -> Result<Config, String> {
"base_url" => base_url = Some(val), "base_url" => base_url = Some(val),
"access_token" => access_token = Some(val), "access_token" => access_token = Some(val),
"user_id" => user_id = Some(val), "user_id" => user_id = Some(val),
"transcode_mbps" => {
// allow raw numbers or quoted numbers
if let Ok(n) = val.parse::<f32>() {
transcode_mbps = Some(n);
}
}
_ => {} _ => {}
} }
} }
@@ -78,6 +85,7 @@ fn parse_config(s: &str) -> Result<Config, String> {
base_url, base_url,
access_token, access_token,
user_id, user_id,
transcode_mbps: transcode_mbps.unwrap_or(5.0),
}) })
} }
@@ -100,6 +108,8 @@ pub fn save_config(cfg: &Config) -> Result<(), ConfigError> {
out.push_str(&format!("user_id = \"{}\"\n", u)); out.push_str(&format!("user_id = \"{}\"\n", u));
} }
out.push_str(&format!("transcode_mbps = {}\n", cfg.transcode_mbps));
std::fs::write(path, out)?; std::fs::write(path, out)?;
Ok(()) Ok(())
} }

View File

@@ -185,6 +185,7 @@ struct App {
login_focus: LoginFocus, login_focus: LoginFocus,
login_error: Option<String>, login_error: Option<String>,
offline: bool, offline: bool,
transcoding: bool,
search: Option<SearchState>, search: Option<SearchState>,
mpv: Option<MpvSession>, mpv: Option<MpvSession>,
@@ -271,6 +272,7 @@ impl Default for App {
downloads_selected: 0, downloads_selected: 0,
downloaded_ids: downloads::scan_downloaded_ids().unwrap_or_default(), downloaded_ids: downloads::scan_downloaded_ids().unwrap_or_default(),
last_download_message: None, last_download_message: None,
transcoding: false,
} }
} }
@@ -598,7 +600,23 @@ fn handle_key(app: &mut App, code: KeyCode) {
} }
return; return;
} }
// Toggle transcoding (affects remote playback URL selection only).
if matches!(code, KeyCode::Char('t')) {
app.transcoding = !app.transcoding;
let mbps = app
.config
.as_ref()
.map(|c| c.transcode_mbps)
.unwrap_or(5.0);
app.status = if app.transcoding {
format!("transcoding: ON ({} Mbps)", mbps)
} else {
"transcoding: OFF".into()
};
return;
}
// -------------------- // --------------------
// Downloads Focus Modal // Downloads Focus Modal
@@ -775,7 +793,7 @@ fn handle_key(app: &mut App, code: KeyCode) {
let url = if let Some(p) = maybe_path.or(local) { let url = if let Some(p) = maybe_path.or(local) {
p.to_string_lossy().to_string() p.to_string_lossy().to_string()
} else { } else {
build_jellyfin_play_url(&cfg, maybe_item_id.as_deref().unwrap()) build_jellyfin_play_url(&cfg, maybe_item_id.as_deref().unwrap(), app.transcoding)
}; };
// If mpv running -> replace, else spawn // If mpv running -> replace, else spawn
@@ -831,7 +849,7 @@ fn handle_key(app: &mut App, code: KeyCode) {
let url = if let Some(p) = local { let url = if let Some(p) = local {
p.to_string_lossy().to_string() p.to_string_lossy().to_string()
} else { } else {
build_jellyfin_play_url(&cfg, &item_id) build_jellyfin_play_url(&cfg, &item_id, app.transcoding)
}; };
if let Some(mpv) = &app.mpv { if let Some(mpv) = &app.mpv {
@@ -936,7 +954,7 @@ fn handle_key(app: &mut App, code: KeyCode) {
app.mpv_now_playing = Some(item_name); app.mpv_now_playing = Some(item_name);
let url = build_jellyfin_play_url(&cfg, &item_id); let url = build_jellyfin_play_url(&cfg, &item_id, app.transcoding);
if let Some(mpv) = &app.mpv { if let Some(mpv) = &app.mpv {
match mpv_cmd(&mpv.socket_path, &format!(r#"{{"command":["loadfile","{}","replace"]}}"#, url)) { match mpv_cmd(&mpv.socket_path, &format!(r#"{{"command":["loadfile","{}","replace"]}}"#, url)) {
@@ -990,7 +1008,7 @@ fn handle_key(app: &mut App, code: KeyCode) {
app.autoplay_next_index = if selected + 1 < len { Some(selected + 1) } else { None }; app.autoplay_next_index = if selected + 1 < len { Some(selected + 1) } else { None };
app.autoplay_eof_latched = false; app.autoplay_eof_latched = false;
let url = build_jellyfin_play_url(&cfg, &item_id); let url = build_jellyfin_play_url(&cfg, &item_id, app.transcoding);
if let Some(mpv) = &app.mpv { if let Some(mpv) = &app.mpv {
match mpv_cmd(&mpv.socket_path, &format!(r#"{{"command":["loadfile","{}","replace"]}}"#, url)) { match mpv_cmd(&mpv.socket_path, &format!(r#"{{"command":["loadfile","{}","replace"]}}"#, url)) {
@@ -1836,8 +1854,20 @@ fn ui(frame: &mut Frame, app: &App) {
.map(|c| c.base_url.as_str()) .map(|c| c.base_url.as_str())
.unwrap_or("-"); .unwrap_or("-");
let mbps = app
.config
.as_ref()
.map(|c| c.transcode_mbps)
.unwrap_or(5.0);
let transcode_line = if app.transcoding {
format!("Transcoding On ({}Mbps)", mbps)
} else {
"Transcoding Off".to_string()
};
let right_text = format!( let right_text = format!(
"{connected}\n\np/P: play/autoplay "{connected}\n{transcode_line}\np/P: play/autoplay
r/R: resume/autoplay r/R: resume/autoplay
d: download / cancel d: download / cancel
q: quit q: quit
@@ -2480,7 +2510,7 @@ fn autoplay_tick(app: &mut App) {
let url = if let Some(p) = local { let url = if let Some(p) = local {
p.to_string_lossy().to_string() p.to_string_lossy().to_string()
} else { } else {
build_jellyfin_play_url(&cfg, &next_id) build_jellyfin_play_url(&cfg, &next_id, app.transcoding)
}; };
if let Err(e) = mpv_load_url(&cfg, mpv, &url) { if let Err(e) = mpv_load_url(&cfg, mpv, &url) {
@@ -3229,9 +3259,22 @@ fn clamp_mbps_to_bps(mbps: u32) -> u32 {
mbps.saturating_mul(1_000_000) // Mb/s -> bits/s mbps.saturating_mul(1_000_000) // Mb/s -> bits/s
} }
fn build_jellyfin_play_url(cfg: &core::config::Config, item_id: &str) -> String { fn build_jellyfin_play_url(cfg: &core::config::Config, item_id: &str, transcoding_on: bool) -> String {
let base = cfg.base_url.trim_end_matches('/'); let base = cfg.base_url.trim_end_matches('/');
format!("{}/Items/{}/Download", base, item_id)
// Transcoding OFF => keep existing behavior exactly.
if !transcoding_on {
return format!("{}/Items/{}/Download", base, item_id);
}
// Transcoding ON => use HLS "main" (does NOT require mediaSourceId)
// videoBitRate expects bits/sec, e.g. 5_000_000
let max_bps: u64 = (cfg.transcode_mbps.max(0.01) * 1_000_000.0) as u64;
format!(
"{}/Videos/{}/main.m3u8?videoBitRate={}&static=false",
base, item_id, max_bps
)
} }
fn mpv_cmd_reply(socket: &PathBuf, cmd: &str) -> Result<Value, String> { fn mpv_cmd_reply(socket: &PathBuf, cmd: &str) -> Result<Value, String> {