Skip to content

Commit

Permalink
Add Capture Control For Python ⭐
Browse files Browse the repository at this point in the history
	modified:   Cargo.lock
	modified:   src/capture.rs
	modified:   windows-capture-python/Cargo.toml
	modified:   windows-capture-python/src/lib.rs
  • Loading branch information
NiiightmareXD committed Nov 15, 2023
1 parent 35c31f5 commit 93826a2
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 44 deletions.
19 changes: 0 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions src/capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ impl CaptureControl {
}
}

/// Check If Capture Thread Is Finished
#[must_use]
pub fn is_finished(&self) -> bool {
self.thread_handle
.as_ref()
.map_or(true, |thread_handle| thread_handle.is_finished())
}

/// Wait Until The Thread Stops
pub fn wait(mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
if let Some(thread_handle) = self.thread_handle.take() {
Expand Down
2 changes: 0 additions & 2 deletions windows-capture-python/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ name = "windows_capture"
crate-type = ["cdylib"]

[dependencies]
log = "0.4.20"
pyo3 = { version = "0.20.0", features = ["extension-module"] }
pyo3-log = "0.9.0"
windows = { version = "0.52.0", features = [
"Win32_UI_WindowsAndMessaging",
"Win32_Foundation",
Expand Down
145 changes: 122 additions & 23 deletions windows-capture-python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,77 @@
use std::{error::Error, sync::Arc};

use ::windows_capture::{
capture::WindowsCaptureHandler,
capture::{CaptureControl, WindowsCaptureHandler},
frame::Frame,
graphics_capture_api::InternalCaptureControl,
monitor::Monitor,
settings::{ColorFormat, WindowsCaptureSettings},
window::Window,
};
use log::{error, info};
use pyo3::{exceptions::PyException, prelude::*, types::PyList};

/// Fastest Windows Screen Capture Library For Python 🔥.
#[pymodule]
fn windows_capture(_py: Python, m: &PyModule) -> PyResult<()> {
pyo3_log::init();

m.add_class::<NativeCaptureControl>()?;
m.add_class::<NativeWindowsCapture>()?;
Ok(())
}

/// Internal Struct Used To Handle Free Threaded Start
#[pyclass]
pub struct NativeCaptureControl {
capture_control: Option<CaptureControl>,
}

impl NativeCaptureControl {
const fn new(capture_control: CaptureControl) -> Self {
Self {
capture_control: Some(capture_control),
}
}
}

#[pymethods]
impl NativeCaptureControl {
#[must_use]
pub fn is_finished(&self) -> bool {
self.capture_control
.as_ref()
.map_or(true, |capture_control| capture_control.is_finished())
}

pub fn wait(&mut self) -> PyResult<()> {
if let Some(capture_control) = self.capture_control.take() {
match capture_control.wait() {
Ok(_) => (),
Err(e) => {
return Err(PyException::new_err(format!(
"Failed To Join The Capture Thread -> {e}"
)));
}
};
}

Ok(())
}

pub fn stop(&mut self) -> PyResult<()> {
if let Some(capture_control) = self.capture_control.take() {
match capture_control.stop() {
Ok(_) => (),
Err(e) => {
return Err(PyException::new_err(format!(
"Failed To Stop The Capture Thread -> {e}"
)));
}
};
}

Ok(())
}
}

/// Internal Struct Used For Windows Capture
#[pyclass]
pub struct NativeWindowsCapture {
Expand All @@ -42,7 +94,6 @@ pub struct NativeWindowsCapture {

#[pymethods]
impl NativeWindowsCapture {
/// Create A New Windows Capture Struct
#[new]
pub fn new(
on_frame_arrived_callback: PyObject,
Expand Down Expand Up @@ -143,6 +194,70 @@ impl NativeWindowsCapture {

Ok(())
}

/// Start Capture On A Dedicated Thread
pub fn start_free_thread(&mut self) -> PyResult<NativeCaptureControl> {
let settings = if self.window_name.is_some() {
let window = match Window::from_contains_name(self.window_name.as_ref().unwrap()) {
Ok(window) => window,
Err(e) => {
return Err(PyException::new_err(format!(
"Failed To Find Window -> {e}"
)));
}
};

match WindowsCaptureSettings::new(
window,
Some(self.capture_cursor),
Some(self.draw_border),
ColorFormat::Bgra8,
(
self.on_frame_arrived_callback.clone(),
self.on_closed.clone(),
),
) {
Ok(settings) => settings,
Err(e) => {
return Err(PyException::new_err(format!(
"Failed To Create Windows Capture Settings -> {e}"
)));
}
}
} else {
let monitor = match Monitor::from_index(self.monitor_index.unwrap()) {
Ok(monitor) => monitor,
Err(e) => {
return Err(PyException::new_err(format!(
"Failed To Get Monitor From Index -> {e}"
)));
}
};

match WindowsCaptureSettings::new(
monitor,
Some(self.capture_cursor),
Some(self.draw_border),
ColorFormat::Bgra8,
(
self.on_frame_arrived_callback.clone(),
self.on_closed.clone(),
),
) {
Ok(settings) => settings,
Err(e) => {
return Err(PyException::new_err(format!(
"Failed To Create Windows Capture Settings -> {e}"
)));
}
}
};

let capture_control = InnerNativeWindowsCapture::start_free_threaded(settings);
let capture_control = NativeCaptureControl::new(capture_control);

Ok(capture_control)
}
}

struct InnerNativeWindowsCapture {
Expand All @@ -169,28 +284,12 @@ impl WindowsCaptureHandler for InnerNativeWindowsCapture {
) -> Result<(), Box<(dyn Error + Send + Sync)>> {
let width = frame.width();
let height = frame.height();
let buf = match frame.buffer() {
Ok(buf) => buf,
Err(e) => {
error!(
"Failed To Get Frame Buffer -> {e} -> Gracefully Stopping The Capture Thread"
);
capture_control.stop();
return Ok(());
}
};
let buf = frame.buffer()?;

let buf = buf.as_raw_buffer();

Python::with_gil(|py| -> PyResult<()> {
match py.check_signals() {
Ok(_) => (),
Err(_) => {
info!("KeyboardInterrupt Detected -> Gracefully Stopping The Capture Thread");
capture_control.stop();
return Ok(());
}
}
py.check_signals()?;

let stop_list = PyList::new(py, [false]);
self.on_frame_arrived_callback.call1(
Expand Down

0 comments on commit 93826a2

Please sign in to comment.