Skip to main content

pixelflow_core/
lib.rs

1//! Core abstractions shared by PixelFlow crates.
2
3pub 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/// Returns the `pixelflow-core` crate version.
59#[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}