Skip to main content

rskit_media/probe/
analysis.rs

1//! Extended probe analysis types — keyframes, silence, chapters.
2//!
3//! These types represent structural information about media that goes beyond basic metadata.
4//! They power chunking strategies, timeline UIs, and intelligent splitting.
5
6use std::time::Duration;
7
8use serde::{Deserialize, Serialize};
9
10use crate::time::{TimeRange, Timestamp};
11
12/// A keyframe (I-frame) position within a video stream.
13///
14/// Keyframes are the only safe points to split video without re-encoding.
15/// Used by [`ChunkStrategy`](crate::chunking::ChunkStrategy) implementations to plan efficient parallel processing boundaries.
16#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
17pub struct KeyframeInfo {
18    /// Position in the stream.
19    pub timestamp: Timestamp,
20    /// Frame number (zero-based).
21    pub frame_number: u64,
22    /// Picture type from the codec (I, IDR, etc.).
23    pub picture_type: PictureType,
24    /// Packet size in bytes (useful for bitrate analysis).
25    pub size_bytes: Option<u64>,
26}
27
28/// Video picture type from the codec.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
30#[non_exhaustive]
31pub enum PictureType {
32    /// Intra-coded frame (full frame, no references).
33    I,
34    /// IDR frame (instantaneous decoder refresh — resets reference list).
35    Idr,
36    /// Predicted frame (references past frames).
37    P,
38    /// Bi-predicted frame (references past and future frames).
39    B,
40    /// Unknown or unrecognized type.
41    Unknown,
42}
43
44impl PictureType {
45    /// Parse from ffprobe's `pict_type` field.
46    #[must_use]
47    pub fn from_ffprobe(s: &str) -> Self {
48        match s.trim() {
49            "I" => Self::I,
50            "IDR" | "idr" => Self::Idr,
51            "P" => Self::P,
52            "B" => Self::B,
53            _ => Self::Unknown,
54        }
55    }
56
57    /// Whether this picture type is a keyframe (safe split point).
58    #[must_use]
59    pub fn is_keyframe(&self) -> bool {
60        matches!(self, Self::I | Self::Idr)
61    }
62}
63
64/// A detected silence interval within an audio stream.
65///
66/// Silence intervals are optimal split points for audio-centric operations like transcription —
67/// splitting at silence avoids cutting words.
68#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
69pub struct SilenceInterval {
70    /// Start of the silence.
71    pub start: Timestamp,
72    /// End of the silence.
73    pub end: Timestamp,
74    /// Duration of the silence.
75    pub duration: Duration,
76}
77
78impl SilenceInterval {
79    /// The midpoint of this silence interval — ideal split point.
80    #[must_use]
81    pub fn midpoint(&self) -> Timestamp {
82        let mid_us = (self.start.as_micros() + self.end.as_micros()) / 2;
83        Timestamp::from_micros(mid_us)
84    }
85
86    /// Convert to a [`TimeRange`].
87    #[must_use]
88    pub fn as_range(&self) -> TimeRange {
89        TimeRange::new(self.start, self.end)
90    }
91}
92
93/// A chapter marker embedded in the media container.
94///
95/// Chapters provide semantic structure (intro, verse, chorus, etc.) and are common in podcasts,
96/// audiobooks, and long-form video.
97#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
98pub struct Chapter {
99    /// Chapter index (zero-based).
100    pub index: usize,
101    /// Time range of this chapter.
102    pub range: TimeRange,
103    /// Chapter title (from container metadata).
104    pub title: Option<String>,
105}
106
107impl Chapter {
108    /// Duration of this chapter.
109    #[must_use]
110    pub fn duration(&self) -> Duration {
111        self.range.duration()
112    }
113}