Allow toggling subtitles, fixes #151

This commit is contained in:
Jeremy Soller 2025-10-06 11:39:02 -06:00
parent c2ecc9cd6b
commit f2a48870f5
No known key found for this signature in database
GPG key ID: 670FDFB5428E05CA
2 changed files with 81 additions and 47 deletions

View file

@ -1,6 +1,7 @@
album = Album: {$album} album = Album: {$album}
audio = Audio audio = Audio
no-video-or-audio-file-open = No video or audio file open no-video-or-audio-file-open = No video or audio file open
off = Off
open-file = Open file open-file = Open file
open-folder = Open folder open-folder = Open folder
subtitles = Subtitles subtitles = Subtitles

View file

@ -233,6 +233,18 @@ pub enum MprisEvent {
State(MprisState), State(MprisState),
} }
#[derive(Clone, Debug)]
pub struct TextCode {
pub id: Option<i32>,
pub name: String,
}
impl AsRef<str> for TextCode {
fn as_ref(&self) -> &str {
self.name.as_str()
}
}
/// Messages that are used specifically by our [`App`]. /// Messages that are used specifically by our [`App`].
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Message { pub enum Message {
@ -295,8 +307,8 @@ pub struct App {
audio_codes: Vec<String>, audio_codes: Vec<String>,
audio_tags: Vec<gst::TagList>, audio_tags: Vec<gst::TagList>,
current_audio: i32, current_audio: i32,
text_codes: Vec<String>, text_codes: Vec<TextCode>,
current_text: i32, current_text: Option<i32>,
} }
impl App { impl App {
@ -320,7 +332,7 @@ impl App {
self.audio_tags.clear(); self.audio_tags.clear();
self.current_audio = -1; self.current_audio = -1;
self.text_codes.clear(); self.text_codes.clear();
self.current_text = -1; self.current_text = None;
self.update_mpris_meta(); self.update_mpris_meta();
self.update_nav_bar_active(); self.update_nav_bar_active();
was_open was_open
@ -441,46 +453,27 @@ impl App {
self.current_audio = pipeline.property::<i32>("current-audio"); self.current_audio = pipeline.property::<i32>("current-audio");
let n_text = pipeline.property::<i32>("n-text"); let n_text = pipeline.property::<i32>("n-text");
self.text_codes = Vec::with_capacity(n_text as usize); self.text_codes = Vec::with_capacity(n_text as usize + 1);
self.text_codes.push(TextCode {
id: None,
name: fl!("off"),
});
for i in 0..n_text { for i in 0..n_text {
let tags: gst::TagList = pipeline.emit_by_name("get-text-tags", &[&i]); let tags: gst::TagList = pipeline.emit_by_name("get-text-tags", &[&i]);
log::info!("text stream {i}: {tags:#?}"); log::info!("text stream {i}: {tags:#?}");
self.text_codes let name = if let Some(title) = tags.get::<gst::tags::Title>() {
.push(if let Some(title) = tags.get::<gst::tags::Title>() { title.get().to_string()
title.get().to_string() } else if let Some(language_code) = tags.get::<gst::tags::LanguageCode>() {
} else if let Some(language_code) = tags.get::<gst::tags::LanguageCode>() { let language_code = language_code.get();
let language_code = language_code.get(); language_name(language_code).unwrap_or_else(|| language_code.to_string())
language_name(language_code).unwrap_or_else(|| language_code.to_string()) } else {
} else { format!("Subtitle #{i}")
format!("Subtitle #{i}") };
}); self.text_codes.push(TextCode { id: Some(i), name });
} }
self.current_text = pipeline.property::<i32>("current-text"); self.current_text = Some(pipeline.property::<i32>("current-text"));
//TODO: Flags can be used to enable/disable subtitles
let flags_value = pipeline.property_value("flags");
println!("original flags {:?}", flags_value);
match flags_value.transform::<i32>() {
Ok(flags_transform) => match flags_transform.get::<i32>() {
Ok(mut flags) => {
flags |= GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO | GST_PLAY_FLAG_TEXT;
match gst::glib::Value::from(flags).transform_with_type(flags_value.type_()) {
Ok(value) => pipeline.set_property("flags", value),
Err(err) => {
log::warn!("failed to transform int to flags: {err}");
}
}
}
Err(err) => {
log::warn!("failed to get flags as int: {err}");
}
},
Err(err) => {
log::warn!("failed to transform flags to int: {err}");
}
}
println!("updated flags {:?}", pipeline.property_value("flags"));
self.update_flags();
self.update_mpris_meta(); self.update_mpris_meta();
self.update_title() self.update_title()
} }
@ -660,6 +653,40 @@ impl App {
cosmic::app::command::set_theme(self.flags.config.app_theme.theme()) cosmic::app::command::set_theme(self.flags.config.app_theme.theme())
} }
fn update_flags(&mut self) {
let Some(video) = &mut self.video_opt else {
return;
};
let pipeline = video.pipeline();
let flags_value = pipeline.property_value("flags");
println!("original flags {:?}", flags_value);
match flags_value.transform::<i32>() {
Ok(flags_transform) => match flags_transform.get::<i32>() {
Ok(mut flags) => {
flags |= GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO;
if self.current_text.is_some() {
flags |= GST_PLAY_FLAG_TEXT;
} else {
flags &= !GST_PLAY_FLAG_TEXT;
}
match gst::glib::Value::from(flags).transform_with_type(flags_value.type_()) {
Ok(value) => pipeline.set_property("flags", value),
Err(err) => {
log::warn!("failed to transform int to flags: {err}");
}
}
}
Err(err) => {
log::warn!("failed to get flags as int: {err}");
}
},
Err(err) => {
log::warn!("failed to transform flags to int: {err}");
}
}
println!("updated flags {:?}", pipeline.property_value("flags"));
}
fn update_mpris_meta(&mut self) { fn update_mpris_meta(&mut self) {
let mut new = MprisMeta { let mut new = MprisMeta {
//TODO: clear url_opt when file is closed //TODO: clear url_opt when file is closed
@ -870,7 +897,7 @@ impl Application for App {
audio_tags: Vec::new(), audio_tags: Vec::new(),
current_audio: -1, current_audio: -1,
text_codes: Vec::new(), text_codes: Vec::new(),
current_text: -1, current_text: None,
}; };
// Do not show nav bar by default. Will be opened by open_project if needed // Do not show nav bar by default. Will be opened by open_project if needed
@ -1176,13 +1203,18 @@ impl Application for App {
} }
} }
} }
Message::TextCode(code) => { Message::TextCode(index) => {
if let Ok(code) = i32::try_from(code) { if let Some(text_code) = self.text_codes.get(index) {
if let Some(video) = &self.video_opt { if let Some(id) = text_code.id {
let pipeline = video.pipeline(); if let Some(video) = &self.video_opt {
pipeline.set_property("current-text", code); let pipeline = video.pipeline();
self.current_text = pipeline.property("current-text"); pipeline.set_property("current-text", id);
self.current_text = Some(pipeline.property("current-text"));
}
} else {
self.current_text = None;
} }
self.update_flags();
} }
} }
Message::Pause | Message::Play | Message::PlayPause => { Message::Pause | Message::Play | Message::PlayPause => {
@ -1548,12 +1580,13 @@ impl Application for App {
); );
} }
if !self.text_codes.is_empty() { if !self.text_codes.is_empty() {
//TODO: allow toggling subtitles
items.push(widget::text::heading(fl!("subtitles")).into()); items.push(widget::text::heading(fl!("subtitles")).into());
items.push( items.push(
widget::dropdown( widget::dropdown(
&self.text_codes, &self.text_codes,
usize::try_from(self.current_text).ok(), self.text_codes
.iter()
.position(|x| x.id == self.current_text),
Message::TextCode, Message::TextCode,
) )
.into(), .into(),