use crate::ffwrappers::errors::Errors;
use core::{f64, str};
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::{path::Path, process::Command};
#[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) -> (f64, f64) {
let kind: VerseKind = verse_kind(verse);
match kind {
VerseKind::SingleVerse => self.return_single_verse(verse),
VerseKind::RangeVerse => self.return_range_verse(verse),
}
}
fn return_single_verse(&self, verse: &str) -> (f64, f64) {
self.get_times(self.find_verse_id(format!("{}{}", self.get_prefix(), verse).as_str()))
}
fn return_range_verse(&self, verse: &str) -> (f64, f64) {
let range: (&str, &str) = range_split(verse);
let start_time: f64 = self.return_single_verse(range.0).0;
let end_time: f64 = self.return_single_verse(range.1).1;
(start_time, end_time)
}
fn find_verse_id(&self, verse: &str) -> i64 {
for i in self.chapters.iter() {
if i.tags.title == verse {
return i.id;
}
}
panic!("The verse was not found.");
}
fn get_times(&self, id: i64) -> (f64, f64) {
for i in self.chapters.iter() {
if i.id == id {
return (i.start_time.parse().unwrap(), i.end_time.parse().unwrap());
}
}
unreachable!()
}
fn get_last_chapter(&self) -> &Chapter {
return self.chapters.last().unwrap();
}
fn get_prefix(&self) -> &str {
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) => {
return prefix.get(1).unwrap().as_str();
}
None => panic!("The prefix pattern was not found."),
};
}
pub fn get_all_verses(&self) -> Vec<(f64, f64)> {
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()));
}
all_verses
}
}
fn get_verse_from_title(title: &str) -> i32 {
let v: Vec<&str> = title.split(':').collect();
v[1].parse()
.expect("Unable to parse verse number from &str to i32.")
}
fn verse_kind(verse: &str) -> VerseKind {
if verse.contains('-') {
VerseKind::RangeVerse
} else {
VerseKind::SingleVerse
}
}
fn range_split(verse: &str) -> (&str, &str) {
let s: Vec<&str> = verse.split('-').collect();
(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), 1);
let title_0 = "John 3:16";
assert_eq!(get_verse_from_title(title_0), 16);
let title_0 = "Ps. 18:32";
assert_eq!(get_verse_from_title(title_0), 32);
}
#[test]
fn test_verse() {
let root = init_struct_1();
assert_eq!(root.verse("16"), (197.597, 226.259));
assert_eq!(root.verse("17"), (226.259, 241.908));
assert_eq!(root.verse("25"), (358.658, 374.741));
assert_eq!(root.verse("26"), (374.741, 394.561));
}
#[test]
#[should_panic]
fn test_verse_not_found() {
let root = init_struct_1();
assert_eq!(root.verse("27"), (197.597, 226.259));
}
#[test]
fn test_range_split_1() {
assert_eq!(range_split("5-7"), ("5", "7"));
}
#[test]
fn test_range_split_2() {
assert_eq!(range_split("5-17"), ("5", "17"));
}
#[test]
fn test_range_split_3() {
assert_eq!(range_split("15-17"), ("15", "17"));
}
#[test]
fn test_get_prefix() {
let r: Root = init_struct_1();
assert_eq!(r.get_prefix(), "John 3:");
}
#[test]
fn test_find_verse_id() {
let r: Root = init_struct_1();
let id = r.find_verse_id("John 3:16");
assert_eq!(id, 16);
}
#[test]
fn test_find_times() {
let r: Root = init_struct_1();
assert_eq!(r.get_times(16), (197.597, 226.259));
}
#[test]
fn test_return_range_verse() {
let r: Root = init_struct_1();
assert_eq!(r.return_range_verse("16-17"), (197.597, 241.908))
}
#[test]
fn test_return_single_verse() {
let r: Root = init_struct_1();
assert_eq!(r.return_single_verse("16"), (197.597, 226.259))
}
#[test]
fn test_verse_single() {
let r: Root = init_struct_1();
assert_eq!(r.verse("16"), (197.597, 226.259))
}
#[test]
fn test_verse_range() {
let r: Root = init_struct_1();
assert_eq!(r.verse("16-17"), (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();
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(), 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
}
}