use crate::ffwrappers::errors::Errors;
use core::{f64, str};
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::{path::Path, process::Command};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("The verse `{verse:?}` was not found")]
VerseNotFound { verse: String },
#[error("The prefix was not found")]
PrefixNotMatch,
#[error("Unable to parse verse number from {item:?} to i32")]
VerseFromTitle { item: String },
}
#[derive(PartialEq, Debug)]
enum VerseKind {
SingleVerse,
RangeVerse,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Root {
pub chapters: Vec<Chapter>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Chapter {
pub id: i64,
#[serde(rename = "time_base")]
pub time_base: String,
pub start: i64,
#[serde(rename = "start_time")]
pub start_time: String,
pub end: i64,
#[serde(rename = "end_time")]
pub end_time: String,
pub tags: Tags,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Tags {
pub title: String,
}
impl Root {
pub fn new(path: &Path) -> Result<Root, Errors> {
let probe = Command::new("ffprobe")
.arg("-v")
.arg("quiet")
.arg("-print_format")
.arg("json")
.arg("-show_chapters")
.arg("-i")
.arg(path)
.output()
.unwrap();
if !probe.status.success() {
eprint!(
"The file, {}, was not found by ffprobe. Check path and try again. ",
path.to_str().unwrap()
);
return Err(Errors::FileError);
}
let c: Root =
serde_json::from_slice(&probe.stdout).expect("Error during JSON parsing of file.");
Ok(c)
}
pub fn verse(&self, verse: &str) -> Result<(f64, f64), Error> {
let kind: VerseKind = verse_kind(verse);
match kind {
VerseKind::SingleVerse => Ok(self.return_single_verse(verse)?),
VerseKind::RangeVerse => Ok(self.return_range_verse(verse)?),
}
}
fn return_single_verse(&self, verse: &str) -> Result<(f64, f64), Error> {
Ok(self
.get_times(self.find_verse_id(format!("{}{}", self.get_prefix()?, verse).as_str())?)?)
}
fn return_range_verse(&self, verse: &str) -> Result<(f64, f64), Error> {
let range: (&str, &str) = range_split(verse)?;
let start_time: f64 = self.return_single_verse(range.0).unwrap().0;
let end_time: f64 = self.return_single_verse(range.1).unwrap().1;
Ok((start_time, end_time))
}
fn find_verse_id(&self, verse: &str) -> Result<i64, Error> {
for i in self.chapters.iter() {
if i.tags.title == verse {
return Ok(i.id);
}
}
Err(Error::VerseNotFound {
verse: verse.to_string(),
})
}
fn get_times(&self, id: i64) -> Result<(f64, f64), Error> {
for i in self.chapters.iter() {
if i.id == id {
return Ok((i.start_time.parse().unwrap(), i.end_time.parse().unwrap()));
}
}
unreachable!()
}
fn get_last_chapter(&self) -> Result<&Chapter, Error> {
Ok(self.chapters.last().unwrap())
}
fn get_prefix(&self) -> Result<&str, Error> {
let title = self.get_last_chapter()?.tags.title.as_str();
let pattern = Regex::new(r"(^[\s\S]*:)").unwrap(); let prefix = pattern.captures(title);
match prefix {
Some(prefix) => Ok(prefix.get(1).unwrap().as_str()),
None => Err(Error::PrefixNotMatch),
}
}
pub fn get_all_verses(&self) -> Result<Vec<(f64, f64)>, Error> {
let last_verse: i32 = get_verse_from_title(self.get_last_chapter()?.tags.title.as_str())?;
let mut all_verses: Vec<(f64, f64)> = Vec::new();
for i in 1..last_verse {
all_verses.push(self.verse(&i.to_string())?);
}
Ok(all_verses)
}
}
fn get_verse_from_title(title: &str) -> Result<i32, Error> {
let v: Vec<&str> = title.split(':').collect();
match v[1].parse::<i32>() {
Ok(p) => Ok(p),
Err(_) => Err(Error::VerseFromTitle {
item: String::from(v[1]),
}),
}
}
fn verse_kind(verse: &str) -> VerseKind {
if verse.contains('-') {
VerseKind::RangeVerse
} else {
VerseKind::SingleVerse
}
}
fn range_split(verse: &str) -> Result<(&str, &str), Error> {
let s: Vec<&str> = verse.split('-').collect();
Ok((s[0], s[1]))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_verse_from_title() {
let title_0 = "Joel 5:1";
assert_eq!(get_verse_from_title(title_0).unwrap(), 1);
let title_0 = "John 3:16";
assert_eq!(get_verse_from_title(title_0).unwrap(), 16);
let title_0 = "Ps. 18:32";
assert_eq!(get_verse_from_title(title_0).unwrap(), 32);
}
#[test]
fn test_verse() {
let root = init_struct_1();
assert_eq!(root.verse("16").unwrap(), (197.597, 226.259));
assert_eq!(root.verse("17").unwrap(), (226.259, 241.908));
assert_eq!(root.verse("25").unwrap(), (358.658, 374.741));
assert_eq!(root.verse("26").unwrap(), (374.741, 394.561));
}
#[test]
#[should_panic]
fn test_verse_not_found() {
let root = init_struct_1();
assert_eq!(root.verse("27").unwrap(), (197.597, 226.259));
}
#[test]
fn test_range_split_1() {
assert_eq!(range_split("5-7").unwrap(), ("5", "7"));
}
#[test]
fn test_range_split_2() {
assert_eq!(range_split("5-17").unwrap(), ("5", "17"));
}
#[test]
fn test_range_split_3() {
assert_eq!(range_split("15-17").unwrap(), ("15", "17"));
}
#[test]
fn test_get_prefix() {
let r: Root = init_struct_1();
assert_eq!(r.get_prefix().unwrap(), "John 3:");
}
#[test]
fn test_find_verse_id() {
let r: Root = init_struct_1();
let id = r.find_verse_id("John 3:16").unwrap();
assert_eq!(id, 16);
}
#[test]
#[should_panic]
fn test_find_verse_id_fail() {
let r: Root = init_struct_1();
let _id = r.find_verse_id("Robert 3:16").unwrap();
}
#[test]
fn test_find_times() {
let r: Root = init_struct_1();
assert_eq!(r.get_times(16).unwrap(), (197.597, 226.259));
}
#[test]
fn test_return_range_verse() {
let r: Root = init_struct_1();
assert_eq!(r.return_range_verse("16-17").unwrap(), (197.597, 241.908))
}
#[test]
fn test_return_single_verse() {
let r: Root = init_struct_1();
assert_eq!(r.return_single_verse("16").unwrap(), (197.597, 226.259))
}
#[test]
fn test_verse_single() {
let r: Root = init_struct_1();
assert_eq!(r.verse("16").unwrap(), (197.597, 226.259))
}
#[test]
fn test_verse_range() {
let r: Root = init_struct_1();
assert_eq!(r.verse("16-17").unwrap(), (197.597, 241.908))
}
#[test]
fn test_verse_kind() {
assert_eq!(verse_kind("1"), VerseKind::SingleVerse)
}
#[test]
fn test_last_chapter() {
let r: Root = init_struct_1();
let chapter: &Chapter = r.get_last_chapter().unwrap();
assert_eq!(chapter.id, 26);
}
#[test]
#[ignore = "Need to finish writing the test."]
fn test_get_all_verses() {
let f = init_struct_1();
assert_eq!(f.get_all_verses().unwrap(), vec![(197.597000, 4226.259000)]);
}
fn init_struct_1() -> Root {
let root_struct: Root = Root {
chapters: {
vec![
Chapter {
id: 16,
time_base: String::from("1/1000"),
start: 197597,
start_time: String::from("197.597000"),
end: 226259,
end_time: String::from("226.259000"),
tags: Tags {
title: String::from("John 3:16"),
},
},
Chapter {
id: 17,
time_base: String::from("1/1000"),
start: 197597,
start_time: String::from("226.259000"),
end: 241908,
end_time: String::from("241.908000"),
tags: Tags {
title: String::from("John 3:17"),
},
},
Chapter {
id: 25,
time_base: String::from("1/1000"),
start: 358658,
start_time: String::from("358.658000"),
end: 374741,
end_time: String::from("374.741000"),
tags: Tags {
title: String::from("John 3:25"),
},
},
Chapter {
id: 26,
time_base: String::from("1/1000"),
start: 374741,
start_time: String::from("374.741000"),
end: 394561,
end_time: String::from("394.561000"),
tags: Tags {
title: String::from("John 3:26"),
},
},
]
},
};
root_struct
}
}