ensuring that slides can be created by different things

This commit is contained in:
Chris Cochrun 2024-10-08 06:18:26 -05:00
parent 887fc733e7
commit 8f065380aa
7 changed files with 270 additions and 50 deletions

23
Cargo.lock generated
View file

@ -499,6 +499,7 @@ dependencies = [
"dirs", "dirs",
"fastrand 2.1.1", "fastrand 2.1.1",
"obws", "obws",
"pretty_assertions",
"quote", "quote",
"reqwest", "reqwest",
"rfd", "rfd",
@ -781,6 +782,12 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@ -2054,6 +2061,16 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "pretty_assertions"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
]
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "1.3.1" version = "1.3.1"
@ -3646,6 +3663,12 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]] [[package]]
name = "youtube_dl" name = "youtube_dl"
version = "0.8.1" version = "0.8.1"

View file

@ -42,6 +42,7 @@ time = { version = "0.3.29", features = ["formatting", "macros"] }
obws = "0.13.0" obws = "0.13.0"
reqwest = "0.11.23" reqwest = "0.11.23"
color-eyre = "0.6.3" color-eyre = "0.6.3"
pretty_assertions = "1.4.1"
# ffmpeg-next = "6.0.0" # ffmpeg-next = "6.0.0"
# cxx-qt-build generates C++ code from the `#[cxx_qt::bridge]` module # cxx-qt-build generates C++ code from the `#[cxx_qt::bridge]` module

View file

@ -1,5 +1,5 @@
pub struct Image { pub struct Image {
title: String, _title: String,
} }
#[cfg(test)] #[cfg(test)]

View file

@ -91,5 +91,4 @@ pub trait Modeling {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*;
} }

View file

@ -1,6 +1,7 @@
use std::path::PathBuf; use std::{error::Error, fmt::Display, path::PathBuf};
use color_eyre::eyre::Result; use color_eyre::eyre::{eyre, Result};
use tracing::debug;
use crate::{ use crate::{
images::Image, kinds::ServiceItemKind, images::Image, kinds::ServiceItemKind,
@ -21,19 +22,81 @@ pub enum TextAlignment {
BottomRight, BottomRight,
} }
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Background {
path: PathBuf,
kind: BackgroundKind,
}
impl TryFrom<String> for Background {
type Error = ParseError;
fn try_from(value: String) -> Result<Self, Self::Error> {
let extension = value.rsplit_once('.').unwrap_or_default();
match extension.0 {
"jpg" | "png" | "webp" => Ok(Self {
path: PathBuf::from(value),
kind: BackgroundKind::Image,
}),
"mp4" | "mkv" | "webm" => Ok(Self {
path: PathBuf::from(value),
kind: BackgroundKind::Video,
}),
_ => Err(ParseError::NonBackgroundFile)
}
}
}
impl TryFrom<PathBuf> for Background {
type Error = ParseError;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
let extension = value.extension().unwrap_or_default().to_str().unwrap_or_default();
match extension {
"jpg" | "png" | "webp" => Ok(Self {
path: value,
kind: BackgroundKind::Image,
}),
"mp4" | "mkv" | "webm" => Ok(Self {
path: value,
kind: BackgroundKind::Video,
}),
_ => Err(ParseError::NonBackgroundFile)
}
}
}
#[derive(Debug)]
pub enum ParseError {
NonBackgroundFile,
}
impl Error for ParseError {}
impl Display for ParseError {
fn fmt(
&self,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
let message = match self {
Self::NonBackgroundFile => "The file is not a recognized image or video type",
};
write!(f, "Error: {message}")
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Background { pub enum BackgroundKind {
#[default] #[default]
Image, Image,
Video, Video,
} }
impl From<String> for Background { impl From<String> for BackgroundKind {
fn from(value: String) -> Self { fn from(value: String) -> Self {
if value == "image" { if value == "image" {
Background::Image BackgroundKind::Image
} else { } else {
Background::Video BackgroundKind::Video
} }
} }
} }
@ -42,7 +105,7 @@ impl From<String> for Background {
struct Slide { struct Slide {
id: i32, id: i32,
database_id: i32, database_id: i32,
background: PathBuf, background: Background,
text: String, text: String,
font: String, font: String,
font_size: i32, font_size: i32,
@ -75,9 +138,13 @@ impl From<Song> for Slide {
} }
impl From<Video> for Slide { impl From<Video> for Slide {
fn from(_video: Video) -> Self { fn from(video: Video) -> Self {
Self { Self {
kind: ServiceItemKind::Video, kind: ServiceItemKind::Video,
background: Background::try_from(video.path).unwrap_or_default(),
video_loop: video.looping,
video_start_time: video.start_time.unwrap_or_default(),
video_end_time: video.end_time.unwrap_or_default(),
..Default::default() ..Default::default()
} }
} }
@ -103,34 +170,158 @@ impl From<Presentation> for Slide {
} }
impl SlideModel { impl SlideModel {
pub fn add_song_to_end(&mut self, song: Song) -> Result<()> { pub fn add_song(&mut self, song: Song, index: i32) -> Result<()> {
let lyrics = song.get_lyrics()?; let lyrics = song.get_lyrics()?;
let mut slides: Vec<Slide> = lyrics let current_length = self.slides.len();
let slides: Vec<Slide> = lyrics
.iter() .iter()
.map(|lyric| Slide { .map(|lyric| Slide {
background: song.background.clone(), background: song.background.clone().unwrap_or_default(),
text: lyric.to_owned(), text: lyric.to_owned(),
font: song.font.clone(), font: song.font.clone().unwrap_or_default(),
font_size: song.font_size, font_size: song.font_size.unwrap_or_default(),
kind: ServiceItemKind::Song, kind: ServiceItemKind::Song,
text_alignment: song.text_alignment, text_alignment: song.text_alignment.unwrap_or_default(),
..Default::default() ..Default::default()
}) })
.collect(); .collect();
self.slides.append(&mut slides); for (location, slide) in slides.iter().enumerate() {
self.slides.insert(location + index as usize, slide.clone());
debug!("Here is the current slide_model: {:?}", self);
}
if self.slides.len() > current_length {
Ok(())
} else {
Err(eyre!("The size of the slides vector didn't change"))
}
}
pub fn get_slide(&self, index: i32) -> Option<&Slide> {
self.slides.get(index as usize)
}
pub fn add_video(&mut self, video: Video, index: i32) -> Result<()> {
self.slides.insert(index as usize, Slide::from(video));
Ok(()) Ok(())
} }
pub fn add_video_to_end(&mut self, video: Video) -> Result<()> { pub fn add_image(&mut self, image: Image, index: i32) -> Result<()> {
self.slides.push(Slide::from(video)); self.slides.insert(index as usize, Slide::from(image));
Ok(())
}
pub fn add_presentation(&mut self, presentation: Presentation, index: i32) -> Result<()> {
self.slides.insert(index as usize, Slide::from(presentation));
Ok(()) Ok(())
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::songs::Song;
use pretty_assertions::{assert_eq, assert_ne};
use super::*;
fn test_song() -> Song {
Song {
title: "From The Day".to_string(),
lyrics: Some("Verse 1
When You found me,
I was so blind
My sin was before me,
I was swallowed by pride
Chorus 1
But out of the darkness,
You brought me to Your light
You showed me new mercy
And opened up my eyes
Chorus 2
From the day
You saved my soul
'Til the very moment
When I come home
I'll sing, I'll dance,
My heart will overflow
From the day
You saved my soul
Verse 2
Where brilliant light
Is all around
And endless joy
Is the only sound
Chorus 3
Oh, rest my heart
Forever now
Oh, in Your arms
I'll always be found
Bridge 1
My love is Yours
My heart is Yours
My life is Yours
Forever
My love is Yours
My heart is Yours
My life is Yours
Forever
Other 1
From the Day
I Am They
Other 2
Ending 1
Oh Oh Oh
From the day
You saved my soul".to_string()),
verse_order: Some("O1 V1 C1 C2 O2 V2 C3 C2 O2 B1 C2 C2 E1 O2"
.to_string()
.split(' ')
.map(|s| s.to_string())
.collect()),
..Default::default()
}
}
fn test_song_verse_one() -> String {
"When You found me,\nI was so blind\nMy sin was before me,\nI was swallowed by pride".to_string()
}
fn test_song_last_verse() -> String {
"Oh Oh Oh\nFrom the day\nYou saved my soul\n".to_string()
}
#[test] #[test]
pub fn test_slides() { pub fn test_add_song_to_slides() {
assert_eq!(true, true) let song = test_song();
let mut slide_model = SlideModel::default();
let result = slide_model.add_song(song, 0);
match result {
Ok(_) => {
let slide = slide_model.get_slide(15).unwrap();
let new_slide = Slide {
text: test_song_last_verse(),
..Default::default()
};
assert_eq!(slide, &new_slide);
let slide = slide_model.get_slide(1).unwrap();
let new_slide = Slide {
text: test_song_verse_one(),
..Default::default()
};
assert_eq!(slide, &new_slide);
},
Err(e) => {
panic!("There was a problem adding the slide: {:?}", e);
}
}
} }
} }

View file

@ -1,7 +1,7 @@
use std::{collections::HashMap, mem::replace, path::PathBuf}; use std::{collections::HashMap, mem::replace, path::PathBuf};
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::{eyre, Result};
use sqlx::{query, Connection, SqliteConnection}; use sqlx::query;
use tracing::{debug, error}; use tracing::{debug, error};
use crate::{ use crate::{
@ -13,15 +13,14 @@ use crate::{
pub struct Song { pub struct Song {
pub title: String, pub title: String,
pub lyrics: Option<String>, pub lyrics: Option<String>,
pub author: String, pub author: Option<String>,
pub ccli: String, pub ccli: Option<String>,
pub audio: PathBuf, pub audio: Option<PathBuf>,
pub verse_order: Vec<String>, pub verse_order: Option<Vec<String>>,
pub background: PathBuf, pub background: Option<Background>,
pub background_type: Background, pub text_alignment: Option<TextAlignment>,
pub text_alignment: TextAlignment, pub font: Option<String>,
pub font: String, pub font_size: Option<i32>,
pub font_size: i32,
} }
const VERSE_KEYWORDS: [&'static str; 24] = [ const VERSE_KEYWORDS: [&'static str; 24] = [
@ -39,7 +38,7 @@ impl Modeling for Model<Song> {
Ok(()) Ok(())
} }
fn add_to_db(&mut self, item: Self::Item) -> Result<()> { fn add_to_db(&mut self, _item: Self::Item) -> Result<()> {
todo!() todo!()
} }
@ -91,21 +90,20 @@ impl Model<Song> {
let _ = self.add_item(Song { let _ = self.add_item(Song {
title: song.title, title: song.title,
lyrics: Some(song.lyrics), lyrics: Some(song.lyrics),
author: song.author, author: Some(song.author),
ccli: song.ccli, ccli: Some(song.ccli),
audio: song.audio.into(), audio: Some(song.audio.into()),
verse_order: song.verse_order.split(" ").map(|s| s.to_string()).collect(), verse_order: Some(song.verse_order.split(" ").map(|s| s.to_string()).collect()),
background: song.background.into(), background: Some(song.background.try_into().unwrap_or_default()),
background_type: song.background_type.into(),
text_alignment: { text_alignment: {
if song.horizontal_text_alignment == "center" && song.vertical_text_alignment == "center" { if song.horizontal_text_alignment == "center" && song.vertical_text_alignment == "center" {
TextAlignment::MiddleCenter Some(TextAlignment::MiddleCenter)
} else { } else {
TextAlignment::TopCenter Some(TextAlignment::TopCenter)
} }
}, },
font: song.font, font: Some(song.font),
font_size: song.font_size, font_size: Some(song.font_size),
}); });
} }
}, },
@ -120,6 +118,13 @@ impl Model<Song> {
impl Song { impl Song {
pub fn get_lyrics(&self) -> Result<Vec<String>> { pub fn get_lyrics(&self) -> Result<Vec<String>> {
let mut lyric_list = Vec::new(); let mut lyric_list = Vec::new();
if self.lyrics.is_none() {
return Err(eyre!("There is no lyrics here"))
} else if self.verse_order.is_none() {
return Err(eyre!("There is no verse_order here"))
} else if self.verse_order.clone().is_some_and(|v| v.is_empty()) {
return Err(eyre!("There is no verse_order here"))
}
if let Some(raw_lyrics) = self.lyrics.clone() { if let Some(raw_lyrics) = self.lyrics.clone() {
let raw_lyrics = raw_lyrics.as_str(); let raw_lyrics = raw_lyrics.as_str();
let verse_order = self.verse_order.clone(); let verse_order = self.verse_order.clone();
@ -143,7 +148,7 @@ impl Song {
} }
lyric_map.insert(verse_title, lyric); lyric_map.insert(verse_title, lyric);
for verse in verse_order { for verse in verse_order.unwrap_or_default() {
let mut verse_name = ""; let mut verse_name = "";
debug!(verse = verse); debug!(verse = verse);
for word in VERSE_KEYWORDS.clone() { for word in VERSE_KEYWORDS.clone() {
@ -188,6 +193,7 @@ impl Song {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use pretty_assertions::{assert_eq, assert_ne};
#[test] #[test]
pub fn test_song_lyrics() { pub fn test_song_lyrics() {
@ -256,7 +262,7 @@ You saved my soul"
"O1 V1 C1 C2 O2 V2 C3 C2 O2 B1 C2 C2 E1 O2" "O1 V1 C1 C2 O2 V2 C3 C2 O2 B1 C2 C2 E1 O2"
.to_string() .to_string()
.split(' ') .split(' ')
.map(|s| s.to_string()) .map(|s| Some(s.to_string()))
.collect(); .collect();
let lyrics = song.get_lyrics(); let lyrics = song.get_lyrics();
match lyrics { match lyrics {

View file

@ -7,11 +7,11 @@ use std::path::PathBuf;
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, PartialEq)]
pub struct Video { pub struct Video {
title: String, pub title: String,
path: PathBuf, pub path: PathBuf,
start_time: f32, pub start_time: Option<f32>,
end_time: f32, pub end_time: Option<f32>,
looping: bool, pub looping: bool,
} }
impl Model<Video> {} impl Model<Video> {}
@ -24,7 +24,7 @@ impl Modeling for Model<Video> {
Ok(()) Ok(())
} }
fn add_to_db(&mut self, item: Self::Item) -> Result<()> { fn add_to_db(&mut self, _item: Self::Item) -> Result<()> {
todo!() todo!()
} }