Skip to main content

omni_dev/cli/
transcript.rs

1//! Transcript and caption fetching from media platforms.
2//!
3//! Provider-namespaced: each source (YouTube today; Vimeo, podcast feeds, …
4//! later) lives under its own subcommand so per-source argument shapes and
5//! help text stay clean.
6
7pub mod fetch;
8pub mod format;
9pub mod youtube;
10
11use anyhow::Result;
12use clap::{Parser, Subcommand};
13
14/// Transcript and caption fetching from media platforms.
15#[derive(Parser)]
16pub struct TranscriptCommand {
17    /// The transcript subcommand to execute.
18    #[command(subcommand)]
19    pub command: TranscriptSubcommands,
20}
21
22/// Transcript subcommands: a provider-less auto-detecting `fetch`, plus one
23/// namespace per media platform.
24#[derive(Subcommand)]
25pub enum TranscriptSubcommands {
26    /// Fetch a transcript, auto-detecting the source from the locator.
27    Fetch(fetch::FetchCommand),
28    /// YouTube: fetch captions, list available languages, and inspect video metadata.
29    Youtube(youtube::YoutubeCommand),
30}
31
32impl TranscriptCommand {
33    /// Dispatches to the selected provider.
34    pub async fn execute(self) -> Result<()> {
35        match self.command {
36            TranscriptSubcommands::Fetch(cmd) => cmd.execute().await,
37            TranscriptSubcommands::Youtube(cmd) => cmd.execute().await,
38        }
39    }
40}
41
42#[cfg(test)]
43#[allow(clippy::unwrap_used, clippy::expect_used)]
44mod tests {
45    use super::*;
46    use crate::cli::transcript::format::CliFormat;
47
48    #[test]
49    fn transcript_subcommands_fetch_variant() {
50        let cmd = TranscriptCommand {
51            command: TranscriptSubcommands::Fetch(fetch::FetchCommand {
52                url: "https://youtu.be/dQw4w9WgXcQ".to_string(),
53                lang: "en".to_string(),
54                format: CliFormat::Srt,
55                auto: false,
56                translate: None,
57                output: None,
58            }),
59        };
60        assert!(matches!(cmd.command, TranscriptSubcommands::Fetch(_)));
61    }
62
63    /// Drives the `Fetch` dispatch arm end-to-end: an unrecognised locator
64    /// fails fast with the typed `InvalidLocator` error before any network
65    /// call — exercising `TranscriptCommand::execute` → `FetchCommand::execute`
66    /// → `detect`.
67    #[tokio::test]
68    async fn fetch_dispatch_surfaces_unrecognised_locator() {
69        let cmd = TranscriptCommand {
70            command: TranscriptSubcommands::Fetch(fetch::FetchCommand {
71                url: "https://vimeo.com/76979871".to_string(),
72                lang: "en".to_string(),
73                format: CliFormat::Srt,
74                auto: false,
75                translate: None,
76                output: None,
77            }),
78        };
79        let err = cmd.execute().await.unwrap_err();
80        assert!(err.to_string().contains("invalid transcript locator"));
81    }
82
83    #[test]
84    fn transcript_subcommands_youtube_variant() {
85        let cmd = TranscriptCommand {
86            command: TranscriptSubcommands::Youtube(youtube::YoutubeCommand {
87                command: youtube::YoutubeSubcommands::Info(youtube::info::InfoCommand {
88                    url: "https://youtu.be/dQw4w9WgXcQ".to_string(),
89                    output: youtube::info::InfoOutput::Table,
90                }),
91            }),
92        };
93        assert!(matches!(cmd.command, TranscriptSubcommands::Youtube(_)));
94    }
95}