diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 6d64c0c..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..a036c24 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: [l1npengtul] +# patreon: # Replace with a single Patreon username +# open_collective: # Replace with a single Open Collective username +# ko_fi: # Replace with a single Ko-fi username +# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +# liberapay: # Replace with a single Liberapay username +# issuehunt: # Replace with a single IssueHunt username +# otechie: # Replace with a single Otechie username +# lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +# custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.gitignore b/.gitignore index b5226e7..d45a211 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,6 @@ nokhwa.iml /pkg -target \ No newline at end of file +target + +.DS_STORE diff --git a/.run/Clippy Main.run.xml b/.run/Clippy Main.run.xml new file mode 100644 index 0000000..bd75d7a --- /dev/null +++ b/.run/Clippy Main.run.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 326ed34..428a9a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,69 +1,70 @@ [package] name = "nokhwa" -version = "0.9.4" +version = "0.10.0" authors = ["l1npengtul "] -edition = "2018" +edition = "2021" description = "A Simple-to-use, cross-platform Rust Webcam Capture Library" keywords = ["camera", "webcam", "capture", "cross-platform"] -resolver = "2" license = "Apache-2.0" repository = "https://github.com/l1npengtul/nokhwa" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace] +members = ["nokhwa-bindings-macos", "nokhwa-bindings-windows", "nokhwa-core", "examples/*"] +exclude = ["examples/jscam"] + [lib] crate-type = ["cdylib", "rlib"] [features] -default = ["decoding", "flume"] -decoding = ["mozjpeg"] +default = ["flume", "decoding"] +serialize = ["serde", "nokhwa-core/serialize"] +decoding = ["nokhwa-core/mjpeg"] input-v4l = ["v4l", "v4l2-sys-mit"] input-msmf = ["nokhwa-bindings-windows"] input-avfoundation = ["nokhwa-bindings-macos"] -input-uvc = ["uvc", "uvc/vendor", "ouroboros", "usb_enumeration"] -input-opencv = ["opencv", "opencv/clang-runtime"] +# Re-enable it once soundness has been proven + mozjpeg is updated to 0.9.x +# input-uvc = ["uvc", "uvc/vendor", "usb_enumeration", "lazy_static"] +input-opencv = ["opencv", "opencv/rgb", "rgb"] input-ipcam = ["input-opencv"] -input-gst = ["gstreamer", "glib", "gstreamer-app", "gstreamer-video", "regex"] -input-jscam = ["web-sys", "js-sys", "wasm-bindgen-futures", "wasm-bindgen"] -output-wgpu = ["wgpu"] +input-gst = ["gstreamer", "glib", "gstreamer-app", "gstreamer-video", "regex", "parking_lot"] +input-jscam = ["web-sys", "js-sys", "wasm-bindgen-futures", "wasm-bindgen", "wasm-rs-async-executor"] +output-wgpu = ["wgpu", "nokhwa-core/wgpu-types"] output-wasm = ["input-jscam"] -output-threaded = ["parking_lot"] -small-wasm = ["wee_alloc"] -docs-only = ["input-uvc", "input-v4l", "input-opencv", "input-ipcam", "input-gst", "input-msmf", "input-avfoundation", "input-jscam","output-wgpu", "output-wasm", "output-threaded"] +output-threaded = [] +small-wasm = [] +docs-only = ["input-v4l", "input-opencv", "input-ipcam", "input-gst", "input-msmf", "input-avfoundation", "input-jscam","output-wgpu", "output-wasm", "output-threaded"] docs-nolink = ["glib/dox", "gstreamer-app/dox", "gstreamer/dox", "gstreamer-video/dox", "opencv/docs-only"] docs-features = [] test-fail-warning = [] [dependencies] -thiserror = "1.0.26" -paste = "1.0.5" +thiserror = "1.0" +paste = "1.0" -[dependencies.flume] -version = "0.10.8" +[dependencies.nokhwa-core] +version = "0.1.0" +path = "nokhwa-core" + +[dependencies.serde] +version = "1.0" optional = true -[target.'cfg(not(target_family = "wasm"))'.dependencies.mozjpeg] -version = "0.8.24" +[dependencies.flume] +version = "0.10" optional = true [dependencies.image] -version = "^0.23" +version = "0.24" default-features = false -[target.'cfg(target_os = "linux")'.dependencies.v4l] -version = "0.12.1" +[dependencies.v4l] +version = "0.13" optional = true -[target.'cfg(target_os = "linux")'.dependencies.v4l2-sys-mit] -version = "0.2.0" -optional = true - -[dependencies.ouroboros] -version = "^0.13" -optional = true - -[dependencies.uvc] -version = "0.2.0" +[dependencies.v4l2-sys-mit] +version = "0.2" optional = true [dependencies.usb_enumeration] @@ -71,36 +72,41 @@ version = "0.1.2" optional = true [dependencies.wgpu] -version = "^0.11" +version = "0.13" optional = true [dependencies.opencv] -version = "0.60.0" -features = ["clang-runtime"] +version = "0.67" +optional = true + +[dependencies.rgb] +version = "0.8" optional = true [dependencies.nokhwa-bindings-windows] -version = "0.3.4" +version = "0.4" +path = "nokhwa-bindings-windows" optional = true [dependencies.nokhwa-bindings-macos] -version = "0.1.1" +version = "0.2" +path = "nokhwa-bindings-macos" optional = true [dependencies.gstreamer] -version = "0.17.0" +version = "0.18" optional = true [dependencies.gstreamer-app] -version = "0.17.0" +version = "0.18" optional = true [dependencies.gstreamer-video] -version = "0.17.0" +version = "0.18" optional = true [dependencies.glib] -version = "0.14.0" +version = "0.15" optional = true [dependencies.regex] @@ -108,8 +114,7 @@ version = "1.4.6" optional = true [dependencies.web-sys] -version = "^0.3" -# why +version = "0.3" features = [ "console", "CanvasRenderingContext2d", @@ -129,23 +134,27 @@ features = [ optional = true [dependencies.js-sys] -version = "^0.3" +version = "0.3" optional = true [dependencies.wasm-bindgen] -version = "^0.2" +version = "0.2" optional = true [dependencies.wasm-bindgen-futures] -version = "^0.4" +version = "0.4" optional = true -[dependencies.wee_alloc] -version = "0.4.5" +[dependencies.wasm-rs-async-executor] +version = "0.9" optional = true [dependencies.parking_lot] -version = "^0.11" +version = "0.12" +optional = true + +[dependencies.lazy_static] +version = "1.4" optional = true [profile.release] diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index ac1bcf0..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,127 +0,0 @@ -pipeline { - agent { - node { - label 'ci_linux' - } - - } - stages { - stage('Sanity Check') { - steps { - echo '$BUILD_TAG' - scmSkip(deleteBuild: true, skipPattern: '.*\\[ci skip\\].*') - } - } - - stage('Cargo RustFMT') { - agent { - node { - label 'ci_linux' - } - - } - steps { - sh 'rustup update stable' - sh 'cargo fmt --all -- --check' - } - } - - stage('Build, Clippy') { - parallel { - stage('V4L2') { - agent { - node { - label 'ci_linux' - } - - } - steps { - sh 'rustup update stable' - sh 'cargo clippy --features "input-v4l, output-wgpu, test-fail-warning"' - } - } - - stage('Media Foundation') { - agent { - node { - label 'ci_windows' - } - - } - steps { - pwsh(script: 'rustup update stable', returnStatus: true) - pwsh(script: 'cargo clippy --features "input-msmf, output-wgpu, test-fail-warning"', returnStatus: true, returnStdout: true) - } - } - - stage('AVFoundation') { - steps { - sh 'echo TODO' - } - } - - stage('libUVC') { - agent { - node { - label 'ci_linux' - } - - } - steps { - sh 'rustup update stable' - sh 'cargo clippy --features "input-uvc, output-wgpu, test-fail-warning"' - } - } - - stage('OpenCV IPCamera') { - agent { - node { - label 'ci_linux' - } - - } - steps { - sh 'rustup update stable' - sh 'cargo +nightly clippy --features "input-opencv, input-ipcam, output-wgpu, test-fail-warning"' - } - } - - stage('GStreamer') { - agent { - node { - label 'ci_linux' - } - - } - steps { - sh 'rustup update nightly' - sh 'cargo clippy --features "input-gst, output-wgpu, test-fail-warning"' - } - } - - stage('JSCamera/WASM') { - steps { - sh 'rustup update stable' - sh 'wasm-pack build --release -- --features "input-jscam, output-wasm, test-fail-warning" --no-default-features' - sh 'cargo clippy --features "input-jscam, output-wasm, test-fail-warning" --no-default-features' - } - } - - } - } - - stage('RustDOC') { - agent { - node { - label 'ci_linux' - } - - } - steps { - sh 'rustup update nightly' - sh 'cargo +nightly doc --features "docs-only, docs-nolink, docs-features, test-fail-warning" --no-deps --release' - } - } - - } -} \ No newline at end of file diff --git a/README.md b/README.md index 7ce8107..19e3a6c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![cargo version](https://img.shields.io/crates/v/nokhwa.svg)](https://crates.io/crates/nokhwa) [![docs.rs version](https://img.shields.io/docsrs/nokhwa)](https://docs.rs/nokhwa/latest/nokhwa/) # nokhwa Nokhwa(녹화): Korean word meaning "to record". @@ -5,11 +6,11 @@ A Simple-to-use, cross-platform Rust Webcam Capture Library ## Using nokhwa Nokhwa can be added to your crate by adding it to your `Cargo.toml`: -```.ignore +```toml [dependencies.nokhwa] -// TODO: replace the "*" with the latest version of `nokhwa` +# TODO: replace the "*" with the latest version of `nokhwa` version = "*" -// TODO: add some features +# TODO: add some features features = [""] ``` @@ -17,7 +18,7 @@ Most likely, you will only use functionality provided by the `Camera` struct. If ## Example -```.ignore +```rust // set up the Camera let mut camera = Camera::new( 0, // index @@ -27,7 +28,7 @@ let mut camera = Camera::new( // open stream camera.open_stream().unwrap(); loop { - let frame = camera.get_frame().unwrap(); + let frame = camera.frame().unwrap(); println!("{}, {}", frame.width(), frame.height()); } ``` @@ -41,16 +42,16 @@ The table below lists current Nokhwa API support. - The `Query-Device` column signifies reading device capabilities - The `Platform` column signifies what Platform this is availible on. - | Backend | Input | Query | Query-Device | Platform | - |-------------------------------------|--------------------|-------------------|--------------------|---------------------| - | Video4Linux(`input-v4l`) | ✅ | ✅ | ✅ | Linux | - | MSMF(`input-msmf`) | ✅ | ✅ | ✅ | Windows | - | AVFoundation(`input-avfoundatuin`)^^| ✅ | ✅ | ✅ | Mac | - | libuvc(`input-uvc`)^^^ | ❌ | ✅ | ❌ | Linux, Windows, Mac | - | OpenCV(`input-opencv`)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac | - | IPCamera(`input-ipcam`/OpenCV)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac | - | GStreamer(`input-gst`) | ✅ | ✅ | ✅ | Linux, Windows, Mac | - | JS/WASM(`input-wasm`) | ✅ | ✅ | ✅ | Browser(Web) | + | Backend | Input | Query | Query-Device | Platform | + |----------------------------------------|--------------------|-------------------|--------------------|---------------------| + | Video4Linux(`input-v4l`) | ✅ | ✅ | ✅ | Linux | + | MSMF(`input-msmf`) | ✅ | ✅ | ✅ | Windows | + | AVFoundation(`input-avfoundation`)^^ | ✅ | ✅ | ✅ | Mac | + | libuvc(`input-uvc`) (**DEPRECATED**)^^^| ❌ | ✅ | ❌ | Linux, Windows, Mac | + | OpenCV(`input-opencv`)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac | + | IPCamera(`input-ipcam`/OpenCV)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac | + | GStreamer(`input-gst`)(**DEPRECATED**) | ✅ | ✅ | ✅ | Linux, Windows, Mac | + | JS/WASM(`input-wasm`) | ✅ | ✅ | ✅ | Browser(Web) | ✅: Working, 🔮 : Experimental, ❌ : Not Supported, 🚧: Planned/WIP @@ -63,14 +64,16 @@ The table below lists current Nokhwa API support. The default feature includes nothing. Anything starting with `input-*` is a feature that enables the specific backend. As a general rule of thumb, you would want to keep at least `input-uvc` or other backend that has querying enabled so you can get device information from `nokhwa`. +### ***NOTE: It is safe to have `input-v4l`, `input-msmf`, and `input-avfoundation` all enabled at the same time since it is platform gated as well!*** + `input-*` features: - `input-v4l`: Enables the `Video4Linux` backend. (linux) - `input-msmf`: Enables the `MediaFoundation` backennd. (Windows 7 or newer) - `input-avfoundation`: Enables the `AVFoundation` backend. (MacOSX 10.7) - - `input-uvc`: Enables the `libuvc` backend. (cross-platform, libuvc statically-linked) + - `input-uvc`: **[DEPRECATED]** Enables the `libuvc` backend. (cross-platform, libuvc statically-linked) - `input-opencv`: Enables the `opencv` backend. (cross-platform) - `input-ipcam`: Enables the use of IP Cameras, please see the `NetworkCamera` struct. Note that this relies on `opencv`, so it will automatically enable the `input-opencv` feature. - - `input-gst`: Enables the `gstreamer` backend. (cross-platform) + - `input-gst`: **[DEPRECATED]** Enables the `gstreamer` backend. - `input-jscam`: Enables the use of the `JSCamera` struct, which uses browser APIs. (Web) Conversely, anything that starts with `output-*` controls a feature that controls the output of something (usually a frame from the camera) @@ -107,3 +110,7 @@ Contributions are welcome! ## Minimum Service Rust Version `nokhwa` may build on older versions of `rustc`, but there is no guarantee except for the latest stable rust. + +## Sponsors +- $5/mo sponsors: + - [remifluff](https://github.com/remifluff) diff --git a/examples/capture/Cargo.toml b/examples/capture/Cargo.toml index f2b4593..77438ce 100644 --- a/examples/capture/Cargo.toml +++ b/examples/capture/Cargo.toml @@ -10,14 +10,13 @@ edition = "2018" default = ["nokhwa/default"] input-msmf = ["nokhwa/input-msmf"] input-v4l = ["nokhwa/input-v4l"] -input-uvc = ["nokhwa/input-uvc"] input-opencv = ["nokhwa/input-opencv"] input-gst = ["nokhwa/input-gst"] input-avfoundation = ["nokhwa/input-avfoundation"] [dependencies] clap = "2.33.3" -glium = "0.30.0" +glium = "0.31.0" glutin = "0.27.0" flume = "0.10.9" diff --git a/examples/capture/example-capture/example-capture.xcodeproj/project.xcworkspace/xcuserdata/pengg.xcuserdatad/UserInterfaceState.xcuserstate b/examples/capture/example-capture/example-capture.xcodeproj/project.xcworkspace/xcuserdata/pengg.xcuserdatad/UserInterfaceState.xcuserstate index 1ffedf4..bc42ece 100644 Binary files a/examples/capture/example-capture/example-capture.xcodeproj/project.xcworkspace/xcuserdata/pengg.xcuserdatad/UserInterfaceState.xcuserstate and b/examples/capture/example-capture/example-capture.xcodeproj/project.xcworkspace/xcuserdata/pengg.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/examples/capture/src/main.rs b/examples/capture/src/main.rs index cf906a9..3f6d453 100644 --- a/examples/capture/src/main.rs +++ b/examples/capture/src/main.rs @@ -1,5 +1,5 @@ /* - * Copyright 2021 l1npengtul / The Nokhwa Contributors + * Copyright 2022 l1npengtul / The Nokhwa Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,13 @@ use glium::{ implement_vertex, index::PrimitiveType, program, texture::RawImage2d, uniform, Display, IndexBuffer, Surface, Texture2d, VertexBuffer, }; -use glutin::{event_loop::EventLoop, window::WindowBuilder, ContextBuilder}; -use nokhwa::{nokhwa_initialize, query_devices, Camera, CaptureAPIBackend, FrameFormat}; +use glutin::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, + ContextBuilder, +}; +use nokhwa::{nokhwa_initialize, query, ApiBackend, Camera, FrameFormat}; use std::time::Instant; #[derive(Copy, Clone)] @@ -111,33 +116,33 @@ fn main() { // Query example if matches.is_present("query") { let backend_value = matches.value_of("query").unwrap(); - let mut use_backend = CaptureAPIBackend::Auto; + let mut use_backend = ApiBackend::Auto; // AUTO if backend_value == "AUTO" { - use_backend = CaptureAPIBackend::Auto; + use_backend = ApiBackend::Auto; } else if backend_value == "UVC" { - use_backend = CaptureAPIBackend::UniversalVideoClass; + use_backend = ApiBackend::UniversalVideoClass; } else if backend_value == "GST" { - use_backend = CaptureAPIBackend::GStreamer; + use_backend = ApiBackend::GStreamer; } else if backend_value == "V4L" { - use_backend = CaptureAPIBackend::Video4Linux; + use_backend = ApiBackend::Video4Linux; } else if backend_value == "MSMF" { - use_backend = CaptureAPIBackend::MediaFoundation; + use_backend = ApiBackend::MediaFoundation; } else if backend_value == "AVF" { nokhwa_initialize(|x| { println!("{}", x); }); - use_backend = CaptureAPIBackend::AVFoundation; + use_backend = ApiBackend::AVFoundation; } - match query_devices(use_backend) { + match query(use_backend) { Ok(devs) => { for (idx, camera) in devs.iter().enumerate() { println!("Device at index {}: {}", idx, camera) } } Err(why) => { - println!("Failed to query: {}", why.to_string()) + println!("Failed to query: {why}") } } } @@ -145,13 +150,13 @@ fn main() { if matches.is_present("capture") { let backend_value = { match matches.value_of("capture-backend").unwrap() { - "UVC" => CaptureAPIBackend::UniversalVideoClass, - "GST" => CaptureAPIBackend::GStreamer, - "V4L" => CaptureAPIBackend::Video4Linux, - "OPENCV" => CaptureAPIBackend::OpenCv, - "MSMF" => CaptureAPIBackend::MediaFoundation, - "AVF" => CaptureAPIBackend::AVFoundation, - _ => CaptureAPIBackend::Auto, + "UVC" => ApiBackend::UniversalVideoClass, + "GST" => ApiBackend::GStreamer, + "V4L" => ApiBackend::Video4Linux, + "OPENCV" => ApiBackend::OpenCv, + "MSMF" => ApiBackend::MediaFoundation, + "AVF" => ApiBackend::AVFoundation, + _ => ApiBackend::Auto, } }; let width = matches @@ -206,13 +211,13 @@ fn main() { } } Err(why) => { - println!("Failed to get compatible resolution/FPS list for FrameFormat {}: {}", ff, why.to_string()) + println!("Failed to get compatible resolution/FPS list for FrameFormat {ff}: {why}") } } } } Err(why) => { - println!("Failed to get compatible FourCC: {}", why.to_string()) + println!("Failed to get compatible FourCC: {why}") } } } @@ -226,7 +231,7 @@ fn main() { } } Err(why) => { - println!("Failed to get camera controls: {}", why.to_string()) + println!("Failed to get camera controls: {why}") } } } @@ -237,7 +242,7 @@ fn main() { let supported = match camera.camera_controls_string() { Ok(cc) => cc, Err(why) => { - println!("Failed to get camera controls: {}", why.to_string()); + println!("Failed to get camera controls: {why}"); return; } }; @@ -330,7 +335,7 @@ fn main() { v_tex_coords = tex_coords; } ", - + outputs_srgb: true, fragment: " #version 140 uniform sampler2D tex; @@ -347,49 +352,49 @@ fn main() { // run the event loop gl_event_loop.run(move |event, _window, ctrl| { - let before_capture = Instant::now(); - let frame = recv.recv().unwrap(); - let after_capture = Instant::now(); + *ctrl = match event { + Event::MainEventsCleared => { + let instant = Instant::now(); + let frame = recv.recv().unwrap(); + let capture_elapsed = instant.elapsed().as_millis(); - let width = &frame.width(); - let height = &frame.height(); + let frame_size = (frame.width(), frame.height()); - let raw_data = RawImage2d::from_raw_rgb(frame.into_raw(), (*width, *height)); - let gl_texture = Texture2d::new(&gl_display, raw_data).unwrap(); + let raw_data = RawImage2d::from_raw_rgb(frame.into_raw(), frame_size); + let gl_texture = Texture2d::new(&gl_display, raw_data).unwrap(); - let uniforms = uniform! { - matrix: [ - [1.0, 0.0, 0.0, 0.0], - [0.0, -1.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0f32] - ], - tex: &gl_texture - }; + let uniforms = uniform! { + matrix: [ + [1.0, 0.0, 0.0, 0.0], + [0.0, -1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0f32] + ], + tex: &gl_texture + }; - let mut target = gl_display.draw(); - target.clear_color(0.0, 0.0, 0.0, 0.0); - target - .draw( - &vert_buffer, - &idx_buf, - &program, - &uniforms, - &Default::default(), - ) - .unwrap(); - target.finish().unwrap(); + let mut target = gl_display.draw(); + target.clear_color(0.0, 0.0, 0.0, 0.0); + target + .draw( + &vert_buffer, + &idx_buf, + &program, + &uniforms, + &Default::default(), + ) + .unwrap(); + target.finish().unwrap(); - if let glutin::event::Event::WindowEvent { event, .. } = event { - if event == glutin::event::WindowEvent::CloseRequested { - *ctrl = glutin::event_loop::ControlFlow::Exit; + println!("Took {capture_elapsed}ms to capture",); + ControlFlow::Poll } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => ControlFlow::Exit, + _ => ControlFlow::Poll, } - - println!( - "Took {}ms to capture", - after_capture.duration_since(before_capture).as_millis() - ) }) } // dont diff --git a/examples/jscam/setup.sh b/examples/jscam/setup.sh index 501ffa0..0178410 100644 --- a/examples/jscam/setup.sh +++ b/examples/jscam/setup.sh @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright 2021 l1npengtul / The Nokhwa Contributors +# Copyright 2022 l1npengtul / The Nokhwa Contributors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/examples/jscam/src/index.html b/examples/jscam/src/index.html index 0f6f3f0..e9a8a76 100644 --- a/examples/jscam/src/index.html +++ b/examples/jscam/src/index.html @@ -1,5 +1,5 @@