327 lines
8 KiB
Rust
327 lines
8 KiB
Rust
use std::{error::Error, fmt::Display, path::PathBuf};
|
|
|
|
use color_eyre::eyre::{eyre, Result};
|
|
use tracing::debug;
|
|
|
|
use crate::{
|
|
images::Image, kinds::ServiceItemKind,
|
|
presentations::Presentation, songs::Song, videos::Video,
|
|
};
|
|
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
pub enum TextAlignment {
|
|
TopLeft,
|
|
TopCenter,
|
|
TopRight,
|
|
MiddleLeft,
|
|
#[default]
|
|
MiddleCenter,
|
|
MiddleRight,
|
|
BottomLeft,
|
|
BottomCenter,
|
|
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)]
|
|
pub enum BackgroundKind {
|
|
#[default]
|
|
Image,
|
|
Video,
|
|
}
|
|
|
|
impl From<String> for BackgroundKind {
|
|
fn from(value: String) -> Self {
|
|
if value == "image" {
|
|
BackgroundKind::Image
|
|
} else {
|
|
BackgroundKind::Video
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, PartialEq)]
|
|
struct Slide {
|
|
id: i32,
|
|
database_id: i32,
|
|
background: Background,
|
|
text: String,
|
|
font: String,
|
|
font_size: i32,
|
|
kind: ServiceItemKind,
|
|
text_alignment: TextAlignment,
|
|
service_item_id: i32,
|
|
active: bool,
|
|
selected: bool,
|
|
video_loop: bool,
|
|
video_start_time: f32,
|
|
video_end_time: f32,
|
|
obs_scene: ObsScene,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
struct ObsScene;
|
|
|
|
#[derive(Clone, Debug, Default, PartialEq)]
|
|
struct SlideModel {
|
|
slides: Vec<Slide>,
|
|
}
|
|
|
|
impl From<Song> for Slide {
|
|
fn from(_song: Song) -> Self {
|
|
Self {
|
|
kind: ServiceItemKind::Song,
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Video> for Slide {
|
|
fn from(video: Video) -> Self {
|
|
Self {
|
|
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()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Image> for Slide {
|
|
fn from(_image: Image) -> Self {
|
|
Self {
|
|
kind: ServiceItemKind::Image,
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Presentation> for Slide {
|
|
fn from(presentation: Presentation) -> Self {
|
|
let preskind = presentation.get_kind();
|
|
Self {
|
|
kind: ServiceItemKind::Presentation(preskind.clone()),
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SlideModel {
|
|
pub fn add_song(&mut self, song: Song, index: i32) -> Result<()> {
|
|
let lyrics = song.get_lyrics()?;
|
|
let current_length = self.slides.len();
|
|
let slides: Vec<Slide> = lyrics
|
|
.iter()
|
|
.map(|lyric| Slide {
|
|
background: song.background.clone().unwrap_or_default(),
|
|
text: lyric.to_owned(),
|
|
font: song.font.clone().unwrap_or_default(),
|
|
font_size: song.font_size.unwrap_or_default(),
|
|
kind: ServiceItemKind::Song,
|
|
text_alignment: song.text_alignment.unwrap_or_default(),
|
|
..Default::default()
|
|
})
|
|
.collect();
|
|
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(())
|
|
}
|
|
|
|
pub fn add_image(&mut self, image: Image, index: i32) -> Result<()> {
|
|
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(())
|
|
}
|
|
}
|
|
|
|
#[cfg(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]
|
|
pub fn test_add_song_to_slides() {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|