facelessvideos 0.1.0

Rust helpers for drafting faceless YouTube short script outlines, paired with the FacelessVideos web app.
Documentation
//! Rust helpers for drafting faceless YouTube short script outlines.
//!
//! Pair this crate with the [FacelessVideos](https://facelessvideos.app) web app:
//! sketch the script outline locally, then paste into the editor at
//! <https://facelessvideos.app> to generate narration, B-roll, captions, and the MP4.
//!
//! # Example
//!
//! ```
//! use facelessvideos::ScriptTemplate;
//!
//! let t = ScriptTemplate::new("compound interest", "casual", 45);
//! let outline = t.render();
//! assert!(outline.contains("facelessvideos.app"));
//! ```

/// Canonical URL of the FacelessVideos web app.
pub const HOMEPAGE_URL: &str = "https://facelessvideos.app";

/// A faceless YouTube short script outline.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScriptTemplate {
    /// Video topic, e.g. "compound interest".
    pub topic: String,
    /// Narration tone, e.g. "casual", "informative".
    pub tone: String,
    /// Total video duration in seconds.
    pub duration: u32,
}

impl ScriptTemplate {
    /// Creates a new script template.
    pub fn new(topic: impl Into<String>, tone: impl Into<String>, duration: u32) -> Self {
        Self {
            topic: topic.into(),
            tone: tone.into(),
            duration,
        }
    }

    /// Renders the three-act script outline.
    pub fn render(&self) -> String {
        let body_end = self.duration.saturating_sub(5);
        format!(
            "[Hook  0-3s] {topic} — grab attention.\n\
             [Body 3-{body_end}s] Cover {topic} in {tone} tone.\n\
             [CTA  {body_end}-{duration}s] Subscribe / link in description.\n\
             \n\
             Generate the full video at {url}",
            topic = self.topic,
            body_end = body_end,
            tone = self.tone,
            duration = self.duration,
            url = HOMEPAGE_URL,
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn render_contains_homepage() {
        let t = ScriptTemplate::new("AI", "casual", 60);
        assert!(t.render().contains("facelessvideos.app"));
    }

    #[test]
    fn render_has_three_acts() {
        let t = ScriptTemplate::new("topic", "tone", 30);
        let out = t.render();
        assert!(out.contains("[Hook"));
        assert!(out.contains("[Body"));
        assert!(out.contains("[CTA"));
    }
}