Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
287 changes: 152 additions & 135 deletions Cargo.lock

Large diffs are not rendered by default.

57 changes: 23 additions & 34 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,27 @@ license = "Unlicense"
keywords = ["asimov-module", "asimov", "ai"]
categories = ["command-line-utilities", "text-processing"] # TODO
publish = false
build = "build.rs"

[features]
# Supported today: CLI + std + ffmpeg desktop backend.
default = ["cli", "std", "ffmpeg"]

# Experimental backends (compile-gated, may be stubbed at runtime).
experimental = ["android", "avf", "dshow", "v4l2"]

# Backward-compatible alias. "native" currently means "experimental native backends".
native = ["experimental"]

# "all" means: everything we can compile & wire up today (not necessarily fully implemented).
all = ["ffmpeg", "pretty", "tracing", "experimental"]

cli = ["asimov-module/cli", "std", "dep:clap", "dep:clientele"]
default = ["cli", "std", "ffmpeg", "android", "avf"]
cli = [
"asimov-module/cli",
"std",
"dep:clap",
"dep:clientele",
"dep:image",
"dep:image_hasher",
"dep:serde_json"
]
std = ["asimov-module/std", "clap?/std", "clientele?/std"]
unstable = []

pretty = []
tracing = ["asimov-module/tracing", "clientele?/tracing"]

mobile-preview = []
ffmpeg = []
android = ["dep:ndk-sys"]
android = ["mobile-preview", "dep:ndk-sys"]
avf = [
"mobile-preview",
"dep:dispatch2",
"dep:objc2",
"dep:objc2-av-foundation",
Expand All @@ -47,29 +44,21 @@ avf = [
"dep:objc2-core-video",
"dep:objc2-foundation",
]
dshow = []
v4l2 = []

[dependencies]
# IMPORTANT: keep std enabled for asimov-module; it currently uses std in its implementation.
asimov-module = { version = "25", default-features = false, features = ["std"] }

ctrlc = "3.5"
derive_more = { version = "2", features = ["display", "error", "from"] }
dogma = { version = "0.1", features = ["traits"] }
image = "0.25"
image_hasher = { version = "3", features = ["fast_image_resize"] }
know = { version = "0.2", features = ["serde"] }
#nokhwa = { version = "0.10", features = ["input-native"] }
scopeguard = { version = "1.2", default-features = false }
serde_json = "1"
thiserror = "2"
bytes = "1"
cfg-if = "1"
crossbeam-channel = "0.5"
know = { version = "0.2", features = ["serde"] }

# Optional integrations:
# Optional CLI-only dependencies:
clap = { version = "4.5", default-features = false, features = ["std"], optional = true }
clientele = { version = "0.3.8", default-features = false, features = ["clap", "std"], optional = true }
serde_json = { version = "1.0.145", optional = true }
image = { version = "0.25", optional = true }
image_hasher = { version = "3", features = ["fast_image_resize"], optional = true }

[target.'cfg(unix)'.dependencies]
libc = "0.2"
Expand All @@ -79,7 +68,7 @@ ndk-sys = { version = "0.6", optional = true }

[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
dispatch2 = { version = "0.3", optional = true }
objc2 = { version = "0.6", optional = true }
objc2 = { version = "0.6", optional = true, features = ["exception"] }
objc2-av-foundation = { version = "0.3", features = ["objc2-core-media"], optional = true }
objc2-core-foundation = { version = "0.3", optional = true }
objc2-core-media = { version = "0.3", optional = true }
Expand All @@ -93,10 +82,10 @@ lto = "thin"

[[bin]]
name = "asimov-camera-reader"
path = "src/reader/main.rs"
path = "src/bin/reader.rs"
required-features = ["cli"]

[[bin]]
name = "asimov-camera-cataloger"
path = "src/cataloger/main.rs"
path = "src/bin/cataloger.rs"
required-features = ["cli"]
9 changes: 9 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
fn main() {
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("android") {
println!("cargo:rustc-link-lib=camera2ndk");
println!("cargo:rustc-link-lib=mediandk");

println!("cargo:rustc-link-lib=android");
println!("cargo:rustc-link-lib=log");
}
}
8 changes: 8 additions & 0 deletions src/api/backend.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// This is free and unencumbered software released into the public domain.

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CameraBackend {
Android,
Avf,
Ffmpeg,
}
198 changes: 198 additions & 0 deletions src/api/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// This is free and unencumbered software released into the public domain.

#[cfg(all(feature = "mobile-preview", feature = "android", target_os = "android"))]
use crate::api::AndroidPreviewTarget;

use crate::{CameraError, DeviceInfo, FrameRef};

use crossbeam_channel as ch;

#[derive(Clone, Debug)]
pub struct CameraConfig {
pub device: Option<DeviceInfo>,
pub width: u32,
pub height: u32,
pub fps: f64,
pub buffer_raw: usize,
pub buffer_frames: usize,
pub throttle_fps: Option<f64>,
pub diagnostics: bool,
pub frame_tx: Option<ch::Sender<FrameRef>>,

#[cfg(all(feature = "mobile-preview", feature = "android", target_os = "android"))]
pub android_preview: AndroidPreviewTarget,
}

impl CameraConfig {
pub fn builder() -> CameraConfigBuilder {
CameraConfigBuilder::new()
}

pub fn normalized(mut self) -> Self {
self.width = self.width.max(1);
self.height = self.height.max(1);

self.fps = if self.fps.is_finite() && self.fps > 0.0 {
self.fps
} else {
30.0
};

self.buffer_raw = self.buffer_raw.max(1);
self.buffer_frames = self.buffer_frames.max(1);

self.throttle_fps = self.throttle_fps.filter(|x| x.is_finite() && *x > 0.0);

self
}

pub fn validate(&self) -> Result<(), CameraError> {
if self.width == 0 || self.height == 0 {
return Err(CameraError::invalid_config("width/height must be > 0"));
}
if !self.fps.is_finite() || self.fps <= 0.0 {
return Err(CameraError::invalid_config("fps must be finite and > 0"));
}
if self.buffer_raw == 0 {
return Err(CameraError::invalid_config("buffer_raw must be >= 1"));
}
if self.buffer_frames == 0 {
return Err(CameraError::invalid_config("buffer_frames must be >= 1"));
}

Ok(())
}
}

#[derive(Clone, Debug)]
pub struct CameraConfigBuilder {
device: Option<DeviceInfo>,
width: u32,
height: u32,
fps: f64,
buffer_raw: usize,
buffer_frames: usize,
throttle_fps: Option<f64>,
diagnostics: bool,
frame_tx: Option<ch::Sender<FrameRef>>,

#[cfg(all(feature = "mobile-preview", feature = "android", target_os = "android"))]
android_preview: Option<AndroidPreviewTarget>,
}

impl CameraConfigBuilder {
fn new() -> Self {
Self {
device: None,
width: 1280,
height: 720,
fps: 30.0,
buffer_raw: 2,
buffer_frames: 1,
throttle_fps: None,
diagnostics: false,
frame_tx: None,

#[cfg(all(feature = "mobile-preview", feature = "android", target_os = "android"))]
android_preview: None,
}
}

pub fn device(mut self, device: DeviceInfo) -> Self {
self.device = Some(device);
self
}

pub fn width(mut self, width: u32) -> Self {
self.width = width;
self
}

pub fn height(mut self, height: u32) -> Self {
self.height = height;
self
}

pub fn fps(mut self, fps: f64) -> Self {
self.fps = fps;
self
}

pub fn buffer_raw(mut self, n: usize) -> Self {
self.buffer_raw = n;
self
}

pub fn buffer_frames(mut self, n: usize) -> Self {
self.buffer_frames = n;
self
}

pub fn throttle_fps(mut self, fps: Option<f64>) -> Self {
self.throttle_fps = fps;
self
}

pub fn diagnostics(mut self, enabled: bool) -> Self {
self.diagnostics = enabled;
self
}

pub fn frame_tx(mut self, tx: ch::Sender<FrameRef>) -> Self {
self.frame_tx = Some(tx);
self
}

#[cfg(all(feature = "mobile-preview", feature = "android", target_os = "android"))]
pub fn android_preview(mut self, target: AndroidPreviewTarget) -> Self {
self.android_preview = Some(target);
self
}

pub fn build(self) -> Result<CameraConfig, CameraError> {
#[cfg(all(feature = "mobile-preview", feature = "android", target_os = "android"))]
{
let android_preview = self.android_preview.ok_or_else(|| {
CameraError::invalid_config(
"android_preview is required when building with mobile-preview on Android",
)
})?;

let cfg = CameraConfig {
device: self.device,
width: self.width,
height: self.height,
fps: self.fps,
buffer_raw: self.buffer_raw,
buffer_frames: self.buffer_frames,
throttle_fps: self.throttle_fps,
diagnostics: self.diagnostics,
frame_tx: self.frame_tx,
android_preview,
};

let cfg = cfg.normalized();
cfg.validate()?;
return Ok(cfg);
}

#[cfg(not(all(feature = "mobile-preview", feature = "android", target_os = "android")))]
{
let cfg = CameraConfig {
device: self.device,
width: self.width,
height: self.height,
fps: self.fps,
buffer_raw: self.buffer_raw,
buffer_frames: self.buffer_frames,
throttle_fps: self.throttle_fps,
diagnostics: self.diagnostics,
frame_tx: self.frame_tx,
};

let cfg = cfg.normalized();
cfg.validate()?;
Ok(cfg)
}
}
}
Loading