parent
2e8829027a
commit
1c8a0bf450
8 changed files with 92 additions and 132 deletions
|
|
@ -369,14 +369,6 @@ impl ServiceTrait for Song {
|
|||
}
|
||||
}
|
||||
|
||||
const VERSE_KEYWORDS: [&str; 24] = [
|
||||
"Verse 1", "Verse 2", "Verse 3", "Verse 4", "Verse 5", "Verse 6",
|
||||
"Verse 7", "Verse 8", "Chorus 1", "Chorus 2", "Chorus 3",
|
||||
"Chorus 4", "Bridge 1", "Bridge 2", "Bridge 3", "Bridge 4",
|
||||
"Intro 1", "Intro 2", "Ending 1", "Ending 2", "Other 1",
|
||||
"Other 2", "Other 3", "Other 4",
|
||||
];
|
||||
|
||||
impl FromRow<'_, SqliteRow> for Song {
|
||||
fn from_row(row: &SqliteRow) -> sqlx::Result<Self> {
|
||||
let lyrics: &str = row.try_get("lyrics")?;
|
||||
|
|
@ -518,72 +510,6 @@ impl From<Value> for Song {
|
|||
}
|
||||
}
|
||||
|
||||
fn lyrics_to_verse(
|
||||
lyrics: String,
|
||||
) -> Result<(Vec<VerseName>, HashMap<VerseName, String>)> {
|
||||
let mut verse_list = Vec::new();
|
||||
if lyrics.is_empty() {
|
||||
return Err(miette!("There is no lyrics here"));
|
||||
}
|
||||
|
||||
let raw_lyrics = lyrics.as_str();
|
||||
|
||||
let mut lyric_map = HashMap::new();
|
||||
let mut verse_title = String::new();
|
||||
let mut lyric = String::new();
|
||||
for (i, line) in raw_lyrics.split('\n').enumerate() {
|
||||
if VERSE_KEYWORDS.contains(&line) {
|
||||
if i != 0 {
|
||||
lyric_map.insert(verse_title, lyric);
|
||||
lyric = String::new();
|
||||
verse_title = line.to_string();
|
||||
} else {
|
||||
verse_title = line.to_string();
|
||||
}
|
||||
} else {
|
||||
lyric.push_str(line);
|
||||
lyric.push('\n');
|
||||
}
|
||||
}
|
||||
lyric_map.insert(verse_title, lyric);
|
||||
let mut verse_map = HashMap::new();
|
||||
for (verse_name, lyric) in lyric_map {
|
||||
let mut verse_elements = verse_name.split_whitespace();
|
||||
let verse_keyword = verse_elements.next();
|
||||
let Some(keyword) = verse_keyword else {
|
||||
return Err(miette!(
|
||||
"Can't parse a proper verse keyword from lyrics"
|
||||
));
|
||||
};
|
||||
let verse_index = verse_elements.next();
|
||||
let Some(index) = verse_index else {
|
||||
return Err(miette!(
|
||||
"Can't parse a proper verse index from lyrics"
|
||||
));
|
||||
};
|
||||
let index = index.parse::<usize>().into_diagnostic()?;
|
||||
let verse = match keyword {
|
||||
"Verse" => VerseName::Verse { number: index },
|
||||
"Pre-Chorus" => VerseName::PreChorus { number: index },
|
||||
"Chorus" => VerseName::Chorus { number: index },
|
||||
"Post-Chorus" => VerseName::PostChorus { number: index },
|
||||
"Bridge" => VerseName::Bridge { number: index },
|
||||
"Intro" => VerseName::Intro { number: index },
|
||||
"Outro" => VerseName::Outro { number: index },
|
||||
"Instrumental" => {
|
||||
VerseName::Instrumental { number: index }
|
||||
}
|
||||
"Other" => VerseName::Other { number: index },
|
||||
_ => VerseName::Other { number: 99 },
|
||||
};
|
||||
verse_list.push(verse);
|
||||
let lyric = lyric.trim().to_string();
|
||||
verse_map.insert(verse, lyric);
|
||||
}
|
||||
|
||||
Ok((verse_list, verse_map))
|
||||
}
|
||||
|
||||
pub fn lisp_to_song(list: Vec<Value>) -> Song {
|
||||
const DEFAULT_SONG_ID: i32 = 0;
|
||||
// const DEFAULT_SONG_LOCATION: usize = 0;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::core::model::LibraryKind;
|
||||
|
||||
pub mod double_ended_slider;
|
||||
// pub mod double_ended_slider;
|
||||
pub mod image_editor;
|
||||
pub mod library;
|
||||
pub mod presentation_editor;
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ pub(crate) struct Presenter {
|
|||
pub current_slide: Slide,
|
||||
pub current_item: usize,
|
||||
pub current_slide_index: usize,
|
||||
pub absolute_slide_index: usize,
|
||||
pub total_slides: usize,
|
||||
pub video: Option<Video>,
|
||||
pub video_position: f32,
|
||||
|
|
@ -75,6 +74,7 @@ pub(crate) enum Action {
|
|||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub(crate) enum Message {
|
||||
NextSlide,
|
||||
PrevSlide,
|
||||
|
|
@ -168,6 +168,7 @@ enum MenuAction {
|
|||
ObsStopStream,
|
||||
ObsStartRecord,
|
||||
ObsStopRecord,
|
||||
MidiAction,
|
||||
}
|
||||
|
||||
impl menu::Action for MenuAction {
|
||||
|
|
@ -190,12 +191,13 @@ impl menu::Action for MenuAction {
|
|||
),
|
||||
Self::ObsStartRecord => todo!(),
|
||||
Self::ObsStopRecord => todo!(),
|
||||
Self::MidiAction => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Presenter {
|
||||
fn create_video(url: Url) -> Result<Video> {
|
||||
fn create_video(url: &Url) -> Result<Video> {
|
||||
// Based on `iced_video_player::Video::new`,
|
||||
// but without a text sink so that the built-in subtitle functionality triggers.
|
||||
use gstreamer as gst;
|
||||
|
|
@ -218,16 +220,35 @@ impl Presenter {
|
|||
|
||||
let video_sink: gst::Element =
|
||||
pipeline.property("video-sink");
|
||||
let pad = video_sink.pads().first().cloned().unwrap();
|
||||
let pad = pad.dynamic_cast::<gst::GhostPad>().unwrap();
|
||||
let pad =
|
||||
video_sink.pads().first().cloned().expect("first pad");
|
||||
let pad = pad
|
||||
.dynamic_cast::<gst::GhostPad>()
|
||||
.map_err(|_| iced_video_player::Error::Cast)
|
||||
.into_diagnostic()?;
|
||||
let bin = pad
|
||||
.parent_element()
|
||||
.unwrap()
|
||||
.ok_or_else(|| {
|
||||
iced_video_player::Error::AppSink(String::from(
|
||||
"Should have a parent element here",
|
||||
))
|
||||
})
|
||||
.into_diagnostic()?
|
||||
.downcast::<gst::Bin>()
|
||||
.unwrap();
|
||||
let video_sink = bin.by_name("lumina_video").unwrap();
|
||||
let video_sink =
|
||||
video_sink.downcast::<gst_app::AppSink>().unwrap();
|
||||
.map_err(|_| iced_video_player::Error::Cast)
|
||||
.into_diagnostic()?;
|
||||
let video_sink = bin
|
||||
.by_name("lumina_video")
|
||||
.ok_or_else(|| {
|
||||
iced_video_player::Error::AppSink(String::from(
|
||||
"Can't find element lumina_video",
|
||||
))
|
||||
})
|
||||
.into_diagnostic()?;
|
||||
let video_sink = video_sink
|
||||
.downcast::<gst_app::AppSink>()
|
||||
.map_err(|_| iced_video_player::Error::Cast)
|
||||
.into_diagnostic()?;
|
||||
let result =
|
||||
Video::from_gst_pipeline(pipeline, video_sink, None);
|
||||
result.into_diagnostic()
|
||||
|
|
@ -239,7 +260,9 @@ impl Presenter {
|
|||
if let Some(slide) = item.slides.first() {
|
||||
let path = slide.background().path.clone();
|
||||
if path.exists() {
|
||||
let url = Url::from_file_path(path).unwrap();
|
||||
let url = Url::from_file_path(path).expect(
|
||||
"There should be a video file here",
|
||||
);
|
||||
let result = Video::new(&url);
|
||||
match result {
|
||||
Ok(mut v) => {
|
||||
|
|
@ -281,7 +304,6 @@ impl Presenter {
|
|||
current_slide: slide.unwrap_or(&DEFAULT_SLIDE).clone(),
|
||||
current_item: 0,
|
||||
current_slide_index: 0,
|
||||
absolute_slide_index: 0,
|
||||
total_slides,
|
||||
video,
|
||||
audio,
|
||||
|
|
@ -308,6 +330,7 @@ impl Presenter {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn update(&mut self, message: Message) -> Action {
|
||||
match message {
|
||||
Message::AddObsClient(client) => {
|
||||
|
|
@ -347,17 +370,16 @@ impl Presenter {
|
|||
self.obs_scenes = Some(scenes);
|
||||
}
|
||||
Message::AssignObsScene(scene_index) => {
|
||||
let slide_id = self.context_menu_id.expect("In this match we should always already have a context menu id");
|
||||
let Some(scenes) = &self.obs_scenes else {
|
||||
return Action::None;
|
||||
};
|
||||
let new_scene = &scenes[scene_index];
|
||||
debug!(?scenes, ?new_scene, "updating obs actions");
|
||||
if let Some(map) = self.slide_action_map.as_mut() {
|
||||
if let Some(actions) = map.get_mut(
|
||||
&self.context_menu_id.unwrap_or_default(),
|
||||
) {
|
||||
if let Some(actions) = map.get_mut(&slide_id) {
|
||||
let mut altered_actions = vec![];
|
||||
actions.iter_mut().for_each(|action| {
|
||||
for action in actions.iter_mut() {
|
||||
match action {
|
||||
slide_actions::Action::Obs {
|
||||
action: ObsAction::Scene { .. },
|
||||
|
|
@ -371,7 +393,7 @@ impl Presenter {
|
|||
_ => altered_actions
|
||||
.push(action.to_owned()),
|
||||
}
|
||||
});
|
||||
}
|
||||
*actions = altered_actions;
|
||||
debug!(
|
||||
"updating the obs scene {:?}",
|
||||
|
|
@ -379,7 +401,7 @@ impl Presenter {
|
|||
);
|
||||
} else if map
|
||||
.insert(
|
||||
self.context_menu_id.unwrap(),
|
||||
slide_id,
|
||||
vec![slide_actions::Action::Obs {
|
||||
action: ObsAction::Scene {
|
||||
scene: new_scene.clone(),
|
||||
|
|
@ -401,7 +423,7 @@ impl Presenter {
|
|||
} else {
|
||||
let mut map = HashMap::new();
|
||||
map.insert(
|
||||
self.context_menu_id.unwrap(),
|
||||
slide_id,
|
||||
vec![slide_actions::Action::Obs {
|
||||
action: ObsAction::Scene {
|
||||
scene: new_scene.clone(),
|
||||
|
|
@ -412,23 +434,16 @@ impl Presenter {
|
|||
}
|
||||
}
|
||||
Message::AssignSlideAction(action) => {
|
||||
let slide_id = self.context_menu_id.expect("In this match we should always already have a context menu id");
|
||||
if let Some(map) = self.slide_action_map.as_mut() {
|
||||
if let Some(actions) =
|
||||
map.get_mut(&self.context_menu_id.unwrap())
|
||||
{
|
||||
if let Some(actions) = map.get_mut(&slide_id) {
|
||||
actions.push(action);
|
||||
} else {
|
||||
map.insert(
|
||||
self.context_menu_id.unwrap(),
|
||||
vec![action],
|
||||
);
|
||||
map.insert(slide_id, vec![action]);
|
||||
}
|
||||
} else {
|
||||
let mut map = HashMap::new();
|
||||
map.insert(
|
||||
self.context_menu_id.unwrap(),
|
||||
vec![action],
|
||||
);
|
||||
map.insert(slide_id, vec![action]);
|
||||
self.slide_action_map = Some(map);
|
||||
}
|
||||
}
|
||||
|
|
@ -519,7 +534,7 @@ impl Presenter {
|
|||
{
|
||||
if let Some(stripped_audio) = new_audio
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.expect("Should be no problem")
|
||||
.to_string()
|
||||
.strip_prefix(r"file://")
|
||||
{
|
||||
|
|
@ -654,7 +669,7 @@ impl Presenter {
|
|||
Message::None
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.expect("Spawning a task shouldn't fail")
|
||||
},
|
||||
|x| x,
|
||||
));
|
||||
|
|
@ -677,13 +692,24 @@ impl Presenter {
|
|||
}
|
||||
|
||||
pub fn view(&self) -> Element<Message> {
|
||||
slide_view(&self.current_slide, &self.video, false, true)
|
||||
slide_view(
|
||||
&self.current_slide,
|
||||
self.video.as_ref(),
|
||||
false,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn view_preview(&self) -> Element<Message> {
|
||||
slide_view(&self.current_slide, &self.video, false, false)
|
||||
slide_view(
|
||||
&self.current_slide,
|
||||
self.video.as_ref(),
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn preview_bar(&self) -> Element<Message> {
|
||||
let mut items = vec![];
|
||||
self.service.iter().enumerate().for_each(
|
||||
|
|
@ -700,7 +726,7 @@ impl Presenter {
|
|||
|
||||
let container = slide_view(
|
||||
slide,
|
||||
&self.video,
|
||||
self.video.as_ref(),
|
||||
true,
|
||||
false,
|
||||
);
|
||||
|
|
@ -739,18 +765,18 @@ impl Presenter {
|
|||
style.shadow = Shadow {
|
||||
color: Color::BLACK,
|
||||
offset: {
|
||||
if is_current_slide {
|
||||
Vector::new(5.0, 5.0)
|
||||
} else if hovered {
|
||||
if is_current_slide
|
||||
|| hovered
|
||||
{
|
||||
Vector::new(5.0, 5.0)
|
||||
} else {
|
||||
Vector::new(0.0, 0.0)
|
||||
}
|
||||
},
|
||||
blur_radius: {
|
||||
if is_current_slide {
|
||||
10.0
|
||||
} else if hovered {
|
||||
if is_current_slide
|
||||
|| hovered
|
||||
{
|
||||
10.0
|
||||
} else {
|
||||
0.0
|
||||
|
|
@ -938,8 +964,9 @@ impl Presenter {
|
|||
BackgroundKind::Video => {
|
||||
let path = &self.current_slide.background().path;
|
||||
if path.exists() {
|
||||
let url = Url::from_file_path(path).unwrap();
|
||||
let result = Self::create_video(url);
|
||||
let url = Url::from_file_path(path)
|
||||
.expect("There should be a video file here");
|
||||
let result = Self::create_video(&url);
|
||||
match result {
|
||||
Ok(mut v) => {
|
||||
v.set_looping(
|
||||
|
|
@ -1028,9 +1055,13 @@ async fn obs_scene_switch(client: Arc<Client>, scene: Scene) {
|
|||
#[allow(clippy::unused_async)]
|
||||
async fn start_audio(sink: Arc<Sink>, audio: PathBuf) {
|
||||
debug!(?audio);
|
||||
let file = BufReader::new(File::open(audio).unwrap());
|
||||
let file = BufReader::new(
|
||||
File::open(audio)
|
||||
.expect("There should be an audio file here"),
|
||||
);
|
||||
debug!(?file);
|
||||
let source = Decoder::new(file).unwrap();
|
||||
let source = Decoder::new(file)
|
||||
.expect("There should be an audio decoder here");
|
||||
sink.append(source);
|
||||
let empty = sink.empty();
|
||||
let paused = sink.is_paused();
|
||||
|
|
@ -1050,7 +1081,7 @@ fn scale_font(font_size: f32, width: f32) -> f32 {
|
|||
|
||||
pub(crate) fn slide_view<'a>(
|
||||
slide: &'a Slide,
|
||||
video: &'a Option<Video>,
|
||||
video: Option<&'a Video>,
|
||||
delegate: bool,
|
||||
hide_mouse: bool,
|
||||
) -> Element<'a, Message> {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ pub const fn service<Message: Clone + 'static>(
|
|||
}
|
||||
|
||||
pub struct Service<'a, Message> {
|
||||
service: &'a Vec<ServiceItem>,
|
||||
items: &'a Vec<ServiceItem>,
|
||||
on_start: Option<Message>,
|
||||
on_cancelled: Option<Message>,
|
||||
on_finish: Option<Message>,
|
||||
|
|
@ -36,9 +36,9 @@ pub struct Service<'a, Message> {
|
|||
|
||||
impl<'a, Message: Clone + 'static> Service<'a, Message> {
|
||||
#[must_use]
|
||||
pub const fn new(service: &'a Vec<ServiceItem>) -> Self {
|
||||
pub const fn new(service_items: &'a Vec<ServiceItem>) -> Self {
|
||||
Self {
|
||||
service,
|
||||
items: service_items,
|
||||
drag_threshold: 8.0,
|
||||
on_start: None,
|
||||
on_cancelled: None,
|
||||
|
|
@ -93,11 +93,13 @@ impl<'a, Message: Clone + 'static> Service<'a, Message> {
|
|||
// );
|
||||
// }
|
||||
|
||||
#[must_use]
|
||||
pub fn on_start(mut self, on_start: Option<Message>) -> Self {
|
||||
self.on_start = on_start;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn on_cancel(
|
||||
mut self,
|
||||
on_cancelled: Option<Message>,
|
||||
|
|
@ -106,6 +108,7 @@ impl<'a, Message: Clone + 'static> Service<'a, Message> {
|
|||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn on_finish(mut self, on_finish: Option<Message>) -> Self {
|
||||
self.on_finish = on_finish;
|
||||
self
|
||||
|
|
@ -289,7 +292,7 @@ impl<Message: Clone + 'static>
|
|||
_viewport: &Rectangle,
|
||||
) {
|
||||
// let state = tree.state.downcast_mut::<State>();
|
||||
for _item in self.service {}
|
||||
for _item in self.items {}
|
||||
}
|
||||
|
||||
// fn overlay<'b>(
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ impl SlideEditor {
|
|||
|
||||
/// Ensure to use the `cosmic::Theme and cosmic::Renderer` here
|
||||
/// or else it will not compile
|
||||
#[allow(clippy::extra_unused_lifetimes)]
|
||||
impl<'a> Program<SlideWidget, cosmic::Theme, cosmic::Renderer>
|
||||
for EditorProgram
|
||||
{
|
||||
|
|
|
|||
|
|
@ -33,8 +33,7 @@ use cosmic::{
|
|||
dropdown,
|
||||
grid::{self},
|
||||
horizontal_space, icon, mouse_area, popover, progress_bar,
|
||||
scrollable, spin_button, text, text_editor, text_input,
|
||||
tooltip,
|
||||
scrollable, text, text_editor, text_input, tooltip,
|
||||
},
|
||||
};
|
||||
use derive_more::Debug;
|
||||
|
|
@ -107,6 +106,7 @@ pub struct SongEditor {
|
|||
importing: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Action {
|
||||
Task(Task<Message>),
|
||||
UpdateSong(Song),
|
||||
|
|
@ -189,7 +189,7 @@ impl Display for Face {
|
|||
};
|
||||
// need to figure out how to parse this out and then back into something
|
||||
|
||||
f.write_str(&format!("{name}{weight}{style}"))
|
||||
write!(f, "{name}{weight}{style}")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -204,7 +204,6 @@ impl SongEditor {
|
|||
})
|
||||
.map(|f| Face(f.clone()))
|
||||
.collect();
|
||||
let stroke_sizes = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
let font_sizes = vec![
|
||||
"5".to_string(),
|
||||
"6".to_string(),
|
||||
|
|
@ -928,9 +927,9 @@ impl SongEditor {
|
|||
slide_view(
|
||||
slide,
|
||||
if index == 0 {
|
||||
&self.video
|
||||
self.video.as_ref()
|
||||
} else {
|
||||
&None
|
||||
None
|
||||
},
|
||||
false,
|
||||
false,
|
||||
|
|
|
|||
|
|
@ -306,7 +306,7 @@ impl TextSvg {
|
|||
return self;
|
||||
};
|
||||
path.push(PathBuf::from("lumina"));
|
||||
path.push(PathBuf::from("temp"));
|
||||
path.push(PathBuf::from("text_svg_cache"));
|
||||
let _ = fs::create_dir_all(&path);
|
||||
|
||||
let mut final_svg = String::with_capacity(1024);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use cosmic::{
|
||||
Element, Task,
|
||||
cosmic_theme::palette::WithAlpha,
|
||||
iced::{Background, Border, Point},
|
||||
iced::{Background, Border},
|
||||
iced_widget::{column, row},
|
||||
theme,
|
||||
widget::{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue