Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ default-members = ["crates/*"]

[workspace.package]
edition = "2024"
version = "0.17.0"
version = "0.18.0"
license = "MIT"

[profile.release]
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ Videos take a few seconds to start, since yt-dlp has to ask YouTube for them fir
Flex starts on whichever video you have highlighted, so it's usually ready by the time you
press OK. Hold OK on a video to mark it watched, see related videos or jump to its
channel. Settings →
YouTube also has the quality limit (1080p unless you change it), whether to show Shorts
YouTube also has the quality limit (1080p unless you change it), an H.264 option for
machines that struggle to decode VP9, whether to show Shorts
(hidden by default) and which SponsorBlock categories to skip (sponsors, self-promotion
and "like and subscribe" reminders by default).

Expand Down
6 changes: 3 additions & 3 deletions crates/flex-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ anyhow = "1.0.104"
chrono = { version = "0.4.45", default-features = false, features = ["clock"] }
eframe = { version = "0.36", default-features = false, features = ["glow", "default_fonts"] }
env_logger = "0.11.11"
flex-core = { version = "0.17.0", path = "../flex-core" }
flex-plugin = { version = "0.17.0", path = "../flex-plugin" }
flex-web = { version = "0.17.0", path = "../flex-web" }
flex-core = { version = "0.18.0", path = "../flex-core" }
flex-plugin = { version = "0.18.0", path = "../flex-plugin" }
flex-web = { version = "0.18.0", path = "../flex-web" }
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "webp"] }
libloading = "0.9"
log = "0.4.34"
Expand Down
2 changes: 1 addition & 1 deletion crates/flex-app/src/mpv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ mod tests {
let _ = env_logger::builder().is_test(true).try_init();
let tools = flex_core::youtube::Tools::find().expect("yt-dlp in the tools folder or on the PATH");
let started = Instant::now();
let video = tools.resolve("aqz-KE-bpKQ", 1080).unwrap();
let video = tools.resolve("aqz-KE-bpKQ", 1080, false).unwrap();
eprintln!("resolved in {:?}: {} ({:?}p)", started.elapsed(), video.title, video.height);

let mpv = Mpv::headless(&[("vo", "null"), ("ao", "null"), ("hwdec", "no")]).unwrap();
Expand Down
13 changes: 10 additions & 3 deletions crates/flex-app/src/screens/youtube.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,7 @@ impl Screen for YouTubeSettings {
if press.is(Action::Back) {
return Nav::Pop;
}
match self.list.handle(press, 7) {
match self.list.handle(press, 8) {
ListInput::AtTop => Nav::FocusTopBar,
ListInput::Activate(0) => {
let on = !sh.youtube_enabled();
Expand All @@ -815,17 +815,22 @@ impl Screen for YouTubeSettings {
Nav::None
}
ListInput::Activate(3) => {
let on = !sh.youtube_h264();
sh.set_youtube_h264(on);
Nav::None
}
ListInput::Activate(4) => {
let on = !sh.youtube_shorts();
sh.set_youtube_shorts(on);
Nav::None
}
ListInput::Activate(4) => {
ListInput::Activate(5) => {
let current = sh.youtube_continue_limit();
let i = CONTINUE_LIMITS.iter().position(|n| *n == current).unwrap_or(3);
sh.set_youtube_continue_limit(CONTINUE_LIMITS[(i + 1) % CONTINUE_LIMITS.len()]);
Nav::None
}
ListInput::Activate(5) => Nav::push(SponsorBlockSettings::default()),
ListInput::Activate(6) => Nav::push(SponsorBlockSettings::default()),
ListInput::Activate(_) => Nav::push(ChannelList::default()),
ListInput::None => Nav::None,
}
Expand All @@ -847,6 +852,8 @@ impl Screen for YouTubeSettings {
Row::new(Icon::None, format!("YouTube: {}", if on { "on" } else { "off" })).detail("Adds YouTube to Home"),
Row::new(Icon::Refresh, tools).detail("OK to update now (they also update daily)"),
Row::new(Icon::None, format!("Quality: up to {}p", sh.youtube_quality())).detail("OK to change"),
Row::new(Icon::None, format!("Codec: {}", if sh.youtube_h264() { "H.264 when available" } else { "any (VP9 or H.264)" }))
.detail("H.264 is easier on slow machines · OK to change"),
Row::new(Icon::None, format!("Shorts: {}", if sh.youtube_shorts() { "shown" } else { "hidden" })).detail("OK to change"),
Row::new(Icon::None, match sh.youtube_continue_limit() {
0 => "Continue Watching: no YouTube videos".to_owned(),
Expand Down
15 changes: 13 additions & 2 deletions crates/flex-app/src/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1641,6 +1641,17 @@ impl Shared {

pub fn set_youtube_quality(&mut self, height: u32) {
self.set_setting(crate::youtube::QUALITY_SETTING, &height.to_string());
self.youtube.forget_streams();
}

/// Whether to prefer H.264 videos (easier to decode than VP9).
pub fn youtube_h264(&self) -> bool {
self.setting_bool(crate::youtube::H264_SETTING)
}

pub fn set_youtube_h264(&mut self, on: bool) {
self.set_setting_bool(crate::youtube::H264_SETTING, on);
self.youtube.forget_streams();
}

pub fn youtube_shorts(&self) -> bool {
Expand Down Expand Up @@ -1671,8 +1682,8 @@ impl Shared {

/// Starts finding a video's streams (and what to skip) ahead of playing it.
pub fn prepare_youtube(&mut self, id: &str) {
let (quality, categories) = (self.youtube_quality(), self.sponsorblock_categories());
self.youtube.prepare(id, quality, categories);
let (quality, h264, categories) = (self.youtube_quality(), self.youtube_h264(), self.sponsorblock_categories());
self.youtube.prepare(id, quality, h264, categories);
}

pub fn yt_channels(&self) -> Vec<flex_core::youtube::store::Channel> {
Expand Down
13 changes: 10 additions & 3 deletions crates/flex-app/src/youtube.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use flex_core::youtube::{Tools, Video};

pub const ENABLED_SETTING: &str = "youtube";
pub const QUALITY_SETTING: &str = "youtube_quality";
/// Whether to prefer H.264, which slow machines decode more easily than VP9.
pub const H264_SETTING: &str = "youtube_h264";
pub const SHORTS_SETTING: &str = "youtube_shorts";
pub const SPONSORBLOCK_SETTING: &str = "youtube_sponsorblock";
pub const CONTINUE_SETTING: &str = "youtube_continue";
Expand Down Expand Up @@ -115,7 +117,7 @@ impl YouTube {

/// Starts finding a video's streams and SponsorBlock segments, unless that's done or
/// under way. Called when a video is highlighted, so it's usually ready by OK.
pub fn prepare(&mut self, id: &str, max_height: u32, categories: Vec<String>) {
pub fn prepare(&mut self, id: &str, max_height: u32, h264: bool, categories: Vec<String>) {
let fresh = self.resolved.get(id).is_some_and(|(at, r)| r.is_ok() && at.elapsed() < RESOLVED_FOR);
if fresh || self.resolving.contains(id) {
return;
Expand All @@ -125,15 +127,15 @@ impl YouTube {
self.resolving.insert(id.to_owned());
let video_id = id.to_owned();
self.spawn(move |tx| {
let mut result = tools.resolve(&video_id, max_height);
let mut result = tools.resolve(&video_id, max_height, h264);
if let Err(e) = &result {
// YouTube may have changed something that a newer yt-dlp handles.
log::warn!("youtube: {video_id}: {e:#}; checking for a newer yt-dlp");
if let Ok(report) = tools::update(true, crate::shared::now_unix())
&& report.updated.iter().any(|(t, _)| *t == Tool::YtDlp)
&& let Some(tools) = Tools::find()
{
result = tools.resolve(&video_id, max_height);
result = tools.resolve(&video_id, max_height, h264);
}
}
let _ = tx.send(Msg::Resolved(video_id, result.map_err(|e| format!("{e:#}"))));
Expand All @@ -155,6 +157,11 @@ impl YouTube {
self.resolved.get(id).map(|(_, r)| r)
}

/// Forgets every video's streams, after the quality or codec setting changes.
pub fn forget_streams(&mut self) {
self.resolved.clear();
}

/// Forgets a failed attempt so playing again tries again.
pub fn forget(&mut self, id: &str) {
if self.resolved.get(id).is_some_and(|(_, r)| r.is_err()) {
Expand Down
2 changes: 1 addition & 1 deletion crates/flex-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ publish = false
[dependencies]
anyhow = "1.0.104"
directories = "6.0.0"
flex-plugin = { version = "0.17.0", path = "../flex-plugin" }
flex-plugin = { version = "0.18.0", path = "../flex-plugin" }
hunch = "2"
lofty = "0.25.4"
log = "0.4.34"
Expand Down
17 changes: 10 additions & 7 deletions crates/flex-core/src/youtube/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,16 @@ impl Tools {
}

/// Finds the streams for a video (a URL or an 11-character id), at most `max_height`
/// pixels tall. Takes a few seconds (it asks YouTube).
pub fn resolve(&self, video: &str, max_height: u32) -> Result<Video> {
/// pixels tall, and H.264 when `h264` and YouTube has it. Takes a few seconds (it asks
/// YouTube).
pub fn resolve(&self, video: &str, max_height: u32, h264: bool) -> Result<Video> {
let url = match video_id(video) {
Some(id) => format!("https://www.youtube.com/watch?v={id}"),
None => video.to_owned(),
};
let out = self
.command()
.args(["--dump-single-json", "--format", &format_choice(max_height), &url])
.args(["--dump-single-json", "--format", &format_choice(max_height, h264), &url])
.output()
.with_context(|| format!("running {}", self.yt_dlp.display()))?;
if !out.status.success() {
Expand All @@ -94,9 +95,11 @@ impl Tools {

/// yt-dlp's format choice: the best video up to `max_height` without AV1 (which many
/// GPUs can't decode, e.g. the TV PC's GTX 1070 Ti), plus the best audio; else the best
/// single file.
fn format_choice(max_height: u32) -> String {
format!("bv*[vcodec!^=av01][height<={max_height}]+ba/b[height<={max_height}]/b")
/// single file. With `h264`, H.264 comes first: it's the cheapest to decode, for slow
/// machines that struggle with VP9.
fn format_choice(max_height: u32, h264: bool) -> String {
let any = format!("bv*[vcodec!^=av01][height<={max_height}]+ba/b[height<={max_height}]/b");
if h264 { format!("bv*[vcodec^=avc1][height<={max_height}]+ba/{any}") } else { any }
}

/// A video ready to play.
Expand Down Expand Up @@ -294,7 +297,7 @@ mod tests {
#[ignore]
fn resolves_a_real_video() {
let tools = Tools::find().expect("yt-dlp in the tools folder or on the PATH");
let v = tools.resolve("https://www.youtube.com/watch?v=aqz-KE-bpKQ", 1080).unwrap();
let v = tools.resolve("https://www.youtube.com/watch?v=aqz-KE-bpKQ", 1080, false).unwrap();
assert_eq!(v.id, "aqz-KE-bpKQ");
assert!(v.title.contains("Big Buck Bunny"), "{}", v.title);
assert!(v.audio_url.is_some() && v.height.is_some_and(|h| h <= 1080));
Expand Down
2 changes: 1 addition & 1 deletion crates/flex-core/src/youtube/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ mod tests {
#[ignore]
fn relays_youtube_fast() {
let tools = super::super::Tools::find().expect("yt-dlp");
let video = tools.resolve("aqz-KE-bpKQ", 1080).unwrap();
let video = tools.resolve("aqz-KE-bpKQ", 1080, false).unwrap();
let local = global().unwrap().add(&video.video_url, video.video_size);
let started = std::time::Instant::now();
let response = ureq::get(&local).header("Range", "bytes=0-").call().unwrap();
Expand Down
2 changes: 1 addition & 1 deletion crates/flex-web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ publish = false
[dependencies]
anyhow = "1.0.104"
axum = "0.8.9"
flex-core = { version = "0.17.0", path = "../flex-core" }
flex-core = { version = "0.18.0", path = "../flex-core" }
log = "0.4.34"
serde = { version = "1.0.229", features = ["derive", "rc"] }
serde_json = "1.0.151"
Expand Down
2 changes: 1 addition & 1 deletion plugins/anilist/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ license.workspace = true
publish = false

[dependencies]
flex-plugin = { version = "0.17.0", path = "../../crates/flex-plugin" }
flex-plugin = { version = "0.18.0", path = "../../crates/flex-plugin" }
ureq = { version = "3", features = ["json"] }
2 changes: 1 addition & 1 deletion plugins/anilist/plugin.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "AniList"
description = "Updates your AniList progress as you watch anime in Flex"
version = "0.17.0"
version = "0.18.0"
command = "flex-anilist"
subscribe = ["playback"]

Expand Down
2 changes: 1 addition & 1 deletion plugins/lotus-lamp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ license.workspace = true
publish = false

[dependencies]
flex-plugin = { version = "0.17.0", path = "../../crates/flex-plugin" }
flex-plugin = { version = "0.18.0", path = "../../crates/flex-plugin" }
lotus-lamp = { git = "https://github.com/bombpersons/lotus-lamp-rs", rev = "59cf5ccaa0007f006f1812fd02cc5991aff11e72" }
tokio = { version = "1.53.2", features = ["rt", "time", "sync", "macros"] }
2 changes: 1 addition & 1 deletion plugins/lotus-lamp/plugin.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "LED strip (Lotus Lamp)"
description = "Lights a Bluetooth LED strip behind the TV with the colours on screen"
version = "0.17.0"
version = "0.18.0"
command = "flex-lotus-lamp"
subscribe = ["light"]

Expand Down
2 changes: 1 addition & 1 deletion plugins/trakt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ license.workspace = true
publish = false

[dependencies]
flex-plugin = { version = "0.17.0", path = "../../crates/flex-plugin" }
flex-plugin = { version = "0.18.0", path = "../../crates/flex-plugin" }
ureq = { version = "3", features = ["json"] }
2 changes: 1 addition & 1 deletion plugins/trakt/plugin.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "Trakt"
description = "Keeps your Trakt history up to date with what you watch in Flex"
version = "0.17.0"
version = "0.18.0"
command = "flex-trakt"
subscribe = ["playback"]

Expand Down
Loading