needle/lib.rs
1#![deny(missing_docs)]
2
3//! # needle
4//!
5//! needle detects openings/intros and endings/credits across video files. needle can be used standalone
6//! via a dedicated CLI, or as a library to implement higher-level tools or plugins (e.g., for intro skipping).
7//!
8//! The library exposes two central structs:
9//!
10//! 1. [Analyzer](crate::audio::Analyzer): Decodes one or more videos and converts them into a set of [FrameHashes](crate::audio::FrameHashes).
11//! 2. [Comparator](crate::audio::Comparator): Searches for openings and endings across two or more videos.
12//!
13//! ## Basic Usage
14//!
15//! First, you need to create and run an [Analyzer](crate::audio::Analyzer).
16//!
17//! This will decode the audio streams for all provided video files and return a list of [FrameHashes](audio::FrameHashes), one per video. The structure
18//! stores a compressed representation of the audio stream that contains _only_ the data we need to search for openings and endings.
19//!
20//! ```
21//! use std::path::PathBuf;
22//! use needle::audio::Analyzer;
23//! # fn get_sample_paths() -> Vec<PathBuf> {
24//! # let resources = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources");
25//! # vec![
26//! # resources.join("sample-5s.mp4"),
27//! # resources.join("sample-shifted-4s.mp4"),
28//! # ]
29//! # }
30//!
31//! let video_paths: Vec<PathBuf> = get_sample_paths();
32//! let analyzer = Analyzer::from_files(video_paths, false, false);
33//!
34//! // Use a `hash_period` of 1.0, `hash_duration` of 3.0, do not `persist` frame hash data
35//! // and enable `threading`.
36//! let frame_hashes = analyzer.run(1.0, 3.0, false, true).unwrap();
37//! ```
38//!
39//! Now you need to create and run a [Comparator](crate::audio::Comparator) using the output [FrameHashes](audio::FrameHashes). You can re-use
40//! the videos by constructing an instance from the [Analyzer](crate::audio::Analyzer).
41//!
42//! ```
43//! use std::path::PathBuf;
44//! use needle::audio::Comparator;
45//! # use needle::audio::Analyzer;
46//! # fn get_sample_paths() -> Vec<PathBuf> {
47//! # let resources = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources");
48//! # vec![
49//! # resources.join("sample-5s.mp4"),
50//! # resources.join("sample-shifted-4s.mp4"),
51//! # ]
52//! # }
53//! # let video_paths: Vec<PathBuf> = get_sample_paths();
54//! # let analyzer = Analyzer::from_files(video_paths, false, false);
55//! # let frame_hashes = analyzer.run(1.0, 3.0, false, true).unwrap();
56//!
57//! let comparator: Comparator = analyzer.into();
58//! let results = comparator.run_with_frame_hashes(frame_hashes, true, false, false, true).unwrap();
59//!
60//! dbg!(results);
61//! // {
62//! // "/tmp/land-of-lustrous-ep1.mkv": SearchResult {
63//! // opening: None,
64//! // ending: Some(
65//! // (
66//! // 1331.664387072s,
67//! // 1419.024930474s,
68//! // ),
69//! // ),
70//! // },
71//! // "/tmp/land-of-lustrous-ep2.mkv": SearchResult {
72//! // opening: Some(
73//! // (
74//! // 44.718820458s,
75//! // 131.995463634s,
76//! // ),
77//! // ),
78//! // ending: Some(
79//! // (
80//! // 1331.664387072s,
81//! // 1436.560077708s,
82//! // ),
83//! // ),
84//! // },
85//! // "/tmp/land-of-lustrous-ep3.mkv": SearchResult {
86//! // opening: Some(
87//! // (
88//! // 41.11111074s,
89//! // 127.800452334s,
90//! // ),
91//! // ),
92//! // ending: Some(
93//! // (
94//! // 1331.664387072s,
95//! // 1436.560077708s,
96//! // ),
97//! // ),
98//! // },
99//! // },
100//! ```
101//!
102//! [Comparator::run_with_frame_hashes](crate::audio::Comparator::run_with_frame_hashes) runs a search for openings and endings
103//! using the provided frame hash data. Note that there is an equivalent method that can read existing frame hash data files from disk
104//! ([Comparator::run](crate::audio::Comparator::run)).
105//!
106//! The output of this method is a map from each video file to a [SearchResult](crate::audio::SearchResult). This
107//! structure contains the actual times for any detected openings and endings.
108
109use std::path::PathBuf;
110
111/// Detects opening and endings across videos using just audio streams.
112pub mod audio;
113#[cfg(feature = "video")]
114/// Detects opening and endings across videos using just video streams.
115pub mod video;
116/// Common utility functions.
117pub mod util;
118
119/// Common error type.
120#[derive(thiserror::Error, Debug)]
121pub enum Error {
122 /// Frame hash data was not found on disk.
123 #[error("frame hash data not found at: {0:?}")]
124 FrameHashDataNotFound(PathBuf),
125 /// No paths were provided to the [crate::audio::Analyzer].
126 #[error("no paths provided to analyzer")]
127 AnalyzerMissingPaths,
128 /// Invalid path.
129 #[error("path does not exist: {0:?}")]
130 PathNotFound(PathBuf),
131 /// Wraps [ffmpeg_next::Error].
132 #[error("FFmpeg error: {0}")]
133 FFmpegError(#[from] ffmpeg_next::Error),
134 /// Wraps [bincode::Error].
135 #[error("bincode error: {0}")]
136 BincodeError(#[from] bincode::Error),
137 /// Wraps [serde_json::Error].
138 #[error("serde_json error: {0}")]
139 SerdeJSONError(#[from] serde_json::Error),
140 /// Wraps [std::io::Error].
141 #[error("IO error: {0}")]
142 IOError(#[from] std::io::Error),
143}
144
145/// Common result type.
146pub type Result<T> = std::result::Result<T, Error>;