use crate::clip::ClipId;
use crate::logging::Rating;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TakeId(Uuid);
impl TakeId {
#[must_use]
pub fn new() -> Self {
Self(Uuid::new_v4())
}
#[must_use]
pub const fn from_uuid(uuid: Uuid) -> Self {
Self(uuid)
}
#[must_use]
pub const fn as_uuid(&self) -> &Uuid {
&self.0
}
}
impl Default for TakeId {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for TakeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Take {
pub id: TakeId,
pub clip_id: ClipId,
pub take_number: u32,
pub scene: String,
pub name: String,
pub rating: Rating,
pub is_selected: bool,
pub is_good: bool,
pub notes: Option<String>,
pub created_at: DateTime<Utc>,
}
impl Take {
#[must_use]
pub fn new(clip_id: ClipId, scene: impl Into<String>, take_number: u32) -> Self {
Self {
id: TakeId::new(),
clip_id,
take_number,
scene: scene.into(),
name: format!("Take {take_number}"),
rating: Rating::Unrated,
is_selected: false,
is_good: false,
notes: None,
created_at: Utc::now(),
}
}
pub fn set_selected(&mut self, selected: bool) {
self.is_selected = selected;
}
pub fn set_good(&mut self, good: bool) {
self.is_good = good;
}
pub fn set_rating(&mut self, rating: Rating) {
self.rating = rating;
}
pub fn set_notes(&mut self, notes: impl Into<String>) {
self.notes = Some(notes.into());
}
pub fn set_name(&mut self, name: impl Into<String>) {
self.name = name.into();
}
}
#[derive(Debug, Clone, Copy)]
pub enum TakeSelector {
HighestRated,
MostRecent,
FirstGood,
LastGood,
ManuallySelected,
}
impl TakeSelector {
#[must_use]
pub fn select<'a>(&self, takes: &'a [Take]) -> Option<&'a Take> {
if takes.is_empty() {
return None;
}
match self {
Self::HighestRated => takes.iter().max_by_key(|t| t.rating),
Self::MostRecent => takes.iter().max_by_key(|t| t.created_at),
Self::FirstGood => takes.iter().find(|t| t.is_good),
Self::LastGood => takes.iter().rfind(|t| t.is_good),
Self::ManuallySelected => takes.iter().find(|t| t.is_selected),
}
}
#[must_use]
pub fn select_good(takes: &[Take]) -> Vec<&Take> {
takes.iter().filter(|t| t.is_good).collect()
}
#[must_use]
pub fn select_by_rating(takes: &[Take], min_rating: Rating) -> Vec<&Take> {
takes.iter().filter(|t| t.rating >= min_rating).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_take_creation() {
let clip_id = ClipId::new();
let take = Take::new(clip_id, "Scene 1", 1);
assert_eq!(take.take_number, 1);
assert_eq!(take.scene, "Scene 1");
assert!(!take.is_selected);
assert!(!take.is_good);
}
#[test]
fn test_take_selector() {
let clip_id = ClipId::new();
let mut take1 = Take::new(clip_id, "Scene 1", 1);
let mut take2 = Take::new(clip_id, "Scene 1", 2);
let mut take3 = Take::new(clip_id, "Scene 1", 3);
take1.set_rating(Rating::ThreeStars);
take2.set_rating(Rating::FiveStars);
take3.set_rating(Rating::FourStars);
take2.set_good(true);
let takes = vec![take1.clone(), take2.clone(), take3.clone()];
let best = TakeSelector::HighestRated
.select(&takes)
.expect("select should succeed");
assert_eq!(best.take_number, 2);
let first_good = TakeSelector::FirstGood
.select(&takes)
.expect("select should succeed");
assert_eq!(first_good.take_number, 2);
let good_takes = TakeSelector::select_good(&takes);
assert_eq!(good_takes.len(), 1);
}
}