1pub mod core;
4pub mod error;
5pub mod filter;
6pub mod format;
7pub mod frame;
8pub mod graph;
9pub mod log;
10pub mod metadata;
11pub mod plugin_abi;
12pub mod plugin_host;
13pub mod render;
14pub mod scheduler;
15pub mod source;
16pub mod y4m;
17
18pub use core::{Core, CoreConfig};
19pub use error::{ErrorCategory, ErrorCode, PixelFlowError, Result};
20pub use filter::{
21 FilterDescriptor, FilterOptionValue, FilterOptions, FilterPlan, FilterPlanRequest,
22 FilterPlanner, FilterRegistry,
23};
24pub use format::{
25 ChromaSiting, ChromaSubsampling, ColorMatrix, ColorPrimaries, ColorRange, ColorTransfer,
26 FormatDescriptor, FormatFamily, PlaneDescriptor, PlaneRole, SampleType, format_with_bit_depth,
27 resolve_format_alias,
28};
29pub use frame::{
30 AllocatorConfig, Frame, FrameBuilder, Plane, PlaneMut, PlaneRows, RawPlane, RawPlaneMut, Sample,
31};
32pub use graph::{
33 Clip, ClipFormat, ClipMedia, ClipResolution, FilterChangeSet, FilterCompatibility, FrameCount,
34 FrameRate, Graph, GraphBuilder, GraphNode, NodeId, NodeKind, ValidatedGraph, ValidationPlan,
35 is_y4m_compatible_format,
36};
37pub use log::{LogLevel, LogRecord, LogSink, Logger, NoopLogSink};
38pub use metadata::{Metadata, MetadataKind, MetadataSchema, MetadataValue, Rational};
39pub use plugin_abi::{
40 PIXELFLOW_ABI_VERSION, PIXELFLOW_PLUGIN_ENTRY_SYMBOL, PixelflowErrorCategory,
41 PixelflowFilterDescriptorV1, PixelflowHostApiV1, PixelflowMetadataKind, PixelflowPluginApiV1,
42 PixelflowPluginEntryV1, PixelflowRegistrar, PixelflowStatus, PixelflowStringView,
43};
44pub use plugin_host::{
45 LoadedPlugin, is_dynamic_library, load_plugins_from_directories, platform_plugin_directories,
46};
47pub use render::{
48 FrameExecutor, FrameRequest, OrderedRender, RenderEngine, RenderExecutorMap, RenderOptions,
49 RenderRange,
50};
51pub use scheduler::{
52 ConcurrencyClass, DependencyPattern, DynamicDependencyBounds, FilterTiming, SourceCapabilities,
53 TimingReport, WorkerPoolConfig,
54};
55pub use source::{SourceOptionValue, SourceRequest};
56pub use y4m::{Y4mWriter, y4m_chroma_tag};
57
58#[must_use]
60pub const fn version() -> &'static str {
61 env!("CARGO_PKG_VERSION")
62}
63
64#[cfg(test)]
65mod tests {
66 #![expect(clippy::indexing_slicing, reason = "allow in tests")]
67
68 use std::sync::{Arc, Mutex};
69
70 use super::{
71 AllocatorConfig, ErrorCategory, ErrorCode, LogLevel, LogRecord, LogSink, Logger,
72 MetadataSchema, PixelFlowError, resolve_format_alias,
73 };
74
75 #[test]
76 fn version_matches_package_version() {
77 assert_eq!(super::version(), env!("CARGO_PKG_VERSION"));
78 }
79
80 #[test]
81 fn structured_error_preserves_category_code_and_message() {
82 let error = PixelFlowError::new(
83 ErrorCategory::Graph,
84 ErrorCode::new("graph.cycle"),
85 "graph contains a cycle",
86 );
87
88 assert_eq!(error.category(), ErrorCategory::Graph);
89 assert_eq!(error.code().as_str(), "graph.cycle");
90 assert_eq!(error.message(), "graph contains a cycle");
91 assert_eq!(
92 error.to_string(),
93 "graph error graph.cycle: graph contains a cycle"
94 );
95 }
96
97 #[test]
98 fn logger_forwards_records_to_sink() {
99 #[derive(Default)]
100 struct RecordingSink {
101 records: Mutex<Vec<LogRecord>>,
102 }
103
104 impl LogSink for RecordingSink {
105 fn log(&self, record: &LogRecord) {
106 self.records
107 .lock()
108 .expect("record lock poisoned")
109 .push(record.clone());
110 }
111 }
112
113 let sink = Arc::new(RecordingSink::default());
114 let logger = Logger::new(sink.clone());
115
116 logger.log(LogLevel::Warn, "pixelflow_core::tests", "cache disabled");
117
118 let records = sink.records.lock().expect("record lock poisoned");
119 assert_eq!(records.len(), 1);
120 assert_eq!(records[0].level(), LogLevel::Warn);
121 assert_eq!(records[0].target(), "pixelflow_core::tests");
122 assert_eq!(records[0].message(), "cache disabled");
123 }
124
125 #[test]
126 fn default_logger_discards_records() {
127 let logger = Logger::default();
128
129 logger.log(LogLevel::Info, "pixelflow_core::tests", "startup");
130 }
131
132 #[test]
133 fn public_modules_are_reachable() {
134 let format = resolve_format_alias("yuv420p10").expect("format alias should resolve");
135 let schema = MetadataSchema::core();
136 let allocator = AllocatorConfig::default();
137 let media = super::ClipMedia::fixed(
138 format.clone(),
139 1920,
140 1080,
141 24,
142 super::Rational {
143 numerator: 24_000,
144 denominator: 1_001,
145 },
146 );
147
148 assert_eq!(format.name(), "yuv420p10");
149 assert!(schema.contains_key("core:matrix"));
150 assert!(allocator.actual_alignment() >= 64);
151 assert!(super::is_y4m_compatible_format(&format));
152 assert!(matches!(media.frame_count(), super::FrameCount::Finite(24)));
153 assert_eq!(super::ColorMatrix::Bt709.as_str(), "bt709");
154 assert_eq!(super::ColorTransfer::Pq.as_str(), "pq");
155 assert_eq!(super::ColorPrimaries::DisplayP3.as_str(), "display_p3");
156 assert_eq!(super::ChromaSiting::Center.offsets(), (0.5, 0.5));
157 }
158}