Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
112 changes: 56 additions & 56 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -1,58 +1,58 @@
{
"permissions": {
"allow": [
"Bash(pnpm typecheck:*)",
"Bash(pnpm lint:*)",
"Bash(pnpm build:*)",
"Bash(cargo check:*)",
"Bash(cargo fmt:*)",
"Bash(pnpm format:*)",
"Bash(pnpm exec biome check:*)",
"Bash(grep:*)",
"Bash(cargo metadata:*)",
"Bash(ffprobe:*)",
"Bash(ls:*)",
"Bash(find:*)",
"Bash(cat:*)",
"WebFetch(domain:raw.githubusercontent.com)",
"WebFetch(domain:api.github.com)",
"Bash(cargo doc:*)",
"Bash(cargo clippy:*)",
"Bash(python3:*)",
"Bash(cargo run:*)",
"WebSearch",
"Bash(xargs ls:*)",
"WebFetch(domain:ffmpeg.org)",
"Bash(git log:*)",
"Bash(tree:*)",
"Bash(tail:*)",
"Bash(pnpm typecheck:desktop:*)",
"Bash(pnpm exec tsc:*)",
"Bash(pnpm biome check:*)",
"Bash(pnpm --dir apps/desktop exec tsc:*)",
"Bash(xxd:*)",
"Bash(git checkout:*)",
"WebFetch(domain:www.npmjs.com)",
"Bash(pnpm install:*)",
"Bash(pnpm --dir apps/desktop exec biome check:*)",
"Bash(pnpm --dir apps/desktop exec biome format:*)",
"Bash(echo:*)",
"Bash(pnpm exec biome:*)",
"Bash(rustfmt:*)",
"Bash(cargo tree:*)",
"WebFetch(domain:github.com)",
"WebFetch(domain:docs.rs)",
"WebFetch(domain:gix.github.io)",
"Bash(cargo clean:*)",
"Bash(cargo test:*)",
"Bash(powershell -Command \"[System.Environment]::OSVersion.Version.ToString()\")",
"Bash(cargo build:*)",
"Bash(gh api:*)",
"Bash(curl:*)",
"Bash(node -e:*)",
"Bash(findstr:*)"
],
"deny": [],
"ask": []
}
"permissions": {
"allow": [
"Bash(pnpm typecheck:*)",
"Bash(pnpm lint:*)",
"Bash(pnpm build:*)",
"Bash(cargo check:*)",
"Bash(cargo fmt:*)",
"Bash(pnpm format:*)",
"Bash(pnpm exec biome check:*)",
"Bash(grep:*)",
"Bash(cargo metadata:*)",
"Bash(ffprobe:*)",
"Bash(ls:*)",
"Bash(find:*)",
"Bash(cat:*)",
"WebFetch(domain:raw.githubusercontent.com)",
"WebFetch(domain:api.github.com)",
"Bash(cargo doc:*)",
"Bash(cargo clippy:*)",
"Bash(python3:*)",
"Bash(cargo run:*)",
"WebSearch",
"Bash(xargs ls:*)",
"WebFetch(domain:ffmpeg.org)",
"Bash(git log:*)",
"Bash(tree:*)",
"Bash(tail:*)",
"Bash(pnpm typecheck:desktop:*)",
"Bash(pnpm exec tsc:*)",
"Bash(pnpm biome check:*)",
"Bash(pnpm --dir apps/desktop exec tsc:*)",
"Bash(xxd:*)",
"Bash(git checkout:*)",
"WebFetch(domain:www.npmjs.com)",
"Bash(pnpm install:*)",
"Bash(pnpm --dir apps/desktop exec biome check:*)",
"Bash(pnpm --dir apps/desktop exec biome format:*)",
"Bash(echo:*)",
"Bash(pnpm exec biome:*)",
"Bash(rustfmt:*)",
"Bash(cargo tree:*)",
"WebFetch(domain:github.com)",
"WebFetch(domain:docs.rs)",
"WebFetch(domain:gix.github.io)",
"Bash(ffmpeg:*)",
"Bash(DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH ./target/release/examples/memory-leak-detector:*)",
"Bash(ln:*)",
"Bash(./target/release/examples/memory-leak-detector:*)",
"Bash(cargo build:*)",
"Bash(footprint:*)",
"Bash(RUST_LOG=info,cap_recording=debug ./target/release/examples/memory-leak-detector:*)",
"Bash(git rm:*)"
],
"deny": [],
"ask": []
}
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ tauri.windows.conf.json
.cursor
.env*.local
.docs/
.claude/
7 changes: 2 additions & 5 deletions apps/desktop/src/utils/tauri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,6 @@ uploadProgressEvent: "upload-progress-event"

/** user-defined types **/

export type AllGpusInfo = { gpus: GpuInfoDiag[]; primaryGpuIndex: number | null; isMultiGpuSystem: boolean; hasDiscreteGpu: boolean }
export type Annotation = { id: string; type: AnnotationType; x: number; y: number; width: number; height: number; strokeColor: string; strokeWidth: number; fillColor: string; opacity: number; rotation: number; text: string | null; maskType?: MaskType | null; maskLevel?: number | null }
export type AnnotationType = "arrow" | "circle" | "rectangle" | "text" | "mask"
export type AppTheme = "system" | "light" | "dark"
Expand Down Expand Up @@ -424,7 +423,6 @@ quality: number | null;
* Whether to prioritize speed over quality (default: false)
*/
fast: boolean | null }
export type GpuInfoDiag = { vendor: string; description: string; dedicatedVideoMemoryMb: number; adapterIndex: number; isSoftwareAdapter: boolean; isBasicRenderDriver: boolean; supportsHardwareEncoding: boolean }
export type HapticPattern = "alignment" | "levelChange" | "generic"
export type HapticPerformanceTime = "default" | "now" | "drawCompleted"
export type Hotkey = { code: string; meta: boolean; ctrl: boolean; alt: boolean; shift: boolean }
Expand All @@ -437,6 +435,7 @@ export type JsonValue<T> = [T]
export type LogicalBounds = { position: LogicalPosition; size: LogicalSize }
export type LogicalPosition = { x: number; y: number }
export type LogicalSize = { width: number; height: number }
export type MacOSVersionInfo = { displayName: string }
export type MainWindowRecordingStartBehaviour = "close" | "minimise"
export type MaskKeyframes = { position?: MaskVectorKeyframe[]; size?: MaskVectorKeyframe[]; intensity?: MaskScalarKeyframe[] }
export type MaskKind = "sensitive" | "highlight"
Expand Down Expand Up @@ -479,7 +478,6 @@ export type RecordingStatus = "pending" | "recording"
export type RecordingStopped = null
export type RecordingTargetMode = "display" | "window" | "area"
export type RenderFrameEvent = { frame_number: number; fps: number; resolution_base: XY<number> }
export type RenderingStatus = { isUsingSoftwareRendering: boolean; isUsingBasicRenderDriver: boolean; hardwareEncodingAvailable: boolean; warningMessage: string | null }
export type RequestOpenRecordingPicker = { target_mode: RecordingTargetMode | null }
export type RequestOpenSettings = { page: string }
export type RequestScreenCapturePrewarm = { force?: boolean }
Expand All @@ -500,7 +498,7 @@ export type StartRecordingInputs = { capture_target: ScreenCaptureTarget; captur
export type StereoMode = "stereo" | "monoL" | "monoR"
export type StudioRecordingMeta = { segment: SingleSegment } | { inner: MultipleSegments }
export type StudioRecordingStatus = { status: "InProgress" } | { status: "NeedsRemux" } | { status: "Failed"; error: string } | { status: "Complete" }
export type SystemDiagnostics = { windowsVersion: WindowsVersionInfo | null; gpuInfo: GpuInfoDiag | null; allGpus: AllGpusInfo | null; renderingStatus: RenderingStatus; availableEncoders: string[]; graphicsCaptureSupported: boolean; d3D11VideoProcessorAvailable: boolean }
export type SystemDiagnostics = { macosVersion: MacOSVersionInfo | null; availableEncoders: string[]; screenCaptureSupported: boolean }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Breaking change in SystemDiagnostics type requires verification.

The SystemDiagnostics type has been completely restructured from Windows/GPU-centric diagnostics to macOS-centric diagnostics. This breaking change removes several fields (e.g., windowsVersion, gpuInfo, allGpus, renderingStatus, graphicsCaptureSupported, d3D11VideoProcessorAvailable) and introduces new macOS-specific fields (macosVersion, screenCaptureSupported).

Ensure that all TypeScript consumers of this type have been updated to reference the new field names and structure.

#!/bin/bash

# Search for usages of removed SystemDiagnostics fields
echo "=== Searching for removed SystemDiagnostics fields ==="
rg -n --type=ts --type=tsx -C3 '\b(windowsVersion|gpuInfo|allGpus|renderingStatus|graphicsCaptureSupported|d3D11VideoProcessorAvailable)\b'

echo ""
echo "=== Searching for SystemDiagnostics type usage ==="
rg -n --type=ts --type=tsx -C3 'SystemDiagnostics'
🤖 Prompt for AI Agents
In apps/desktop/src/utils/tauri.ts around line 501, the SystemDiagnostics type
was changed from a Windows/GPU-centric shape to a macOS-centric shape (removed
fields: windowsVersion, gpuInfo, allGpus, renderingStatus,
graphicsCaptureSupported, d3D11VideoProcessorAvailable; added macosVersion,
screenCaptureSupported), which is a breaking change; locate all TypeScript
usages (run the provided ripgrep commands), update each consumer to use the new
field names or accessors, or restore a compatibility union/interface or mapping
function that populates the old fields from the new diagnostics where
appropriate, and add unit/type tests to confirm no remaining references to
removed fields remain and compilation succeeds.

export type TargetUnderCursor = { display_id: DisplayId | null; window: WindowUnderCursor | null }
export type TextSegment = { start: number; end: number; enabled?: boolean; content?: string; center?: XY<number>; size?: XY<number>; fontFamily?: string; fontSize?: number; fontWeight?: number; italic?: boolean; color?: string; fadeDuration?: number }
export type TimelineConfiguration = { segments: TimelineSegment[]; zoomSegments: ZoomSegment[]; sceneSegments?: SceneSegment[]; maskSegments?: MaskSegment[]; textSegments?: TextSegment[] }
Expand All @@ -517,7 +515,6 @@ export type VideoUploadInfo = { id: string; link: string; config: S3UploadMeta }
export type WindowExclusion = { bundleIdentifier?: string | null; ownerName?: string | null; windowTitle?: string | null }
export type WindowId = string
export type WindowUnderCursor = { id: WindowId; app_name: string; bounds: LogicalBounds }
export type WindowsVersionInfo = { major: number; minor: number; build: number; displayName: string; meetsRequirements: boolean; isWindows11: boolean }
export type XY<T> = { x: T; y: T }
export type ZoomMode = "auto" | { manual: { x: number; y: number } }
export type ZoomSegment = { start: number; end: number; amount: number; mode: ZoomMode }
Expand Down
69 changes: 45 additions & 24 deletions crates/camera-ffmpeg/src/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub enum AsFFmpegError {
SwscaleFallbackFailed { format: String, reason: String },
#[error("{0}")]
Native(#[from] cidre::os::Error),
#[error("No image buffer available")]
NoImageBuffer,
}

struct FourccInfo {
Expand Down Expand Up @@ -53,17 +55,39 @@ static FALLBACK_WARNING_LOGGED: AtomicBool = AtomicBool::new(false);

impl CapturedFrameExt for CapturedFrame {
fn as_ffmpeg(&self) -> Result<ffmpeg::frame::Video, AsFFmpegError> {
let native = self.native().clone();

let width = native.image_buf().width();
let height = native.image_buf().height();
let native = self.native();

let mut image_buf = native.image_buf().ok_or(AsFFmpegError::NoImageBuffer)?;

let width = image_buf.width();
let height = image_buf.height();
let plane0_stride = image_buf.plane_bytes_per_row(0);
let plane1_stride = image_buf.plane_bytes_per_row(1);
let plane_count = image_buf.plane_count();
let plane_info: [(usize, usize, usize); 3] = [
(
image_buf.plane_bytes_per_row(0),
image_buf.plane_height(0),
image_buf.plane_width(0),
),
(
image_buf.plane_bytes_per_row(1),
image_buf.plane_height(1),
image_buf.plane_width(1),
),
(
image_buf.plane_bytes_per_row(2),
image_buf.plane_height(2),
image_buf.plane_width(2),
),
];

let format_desc = native.sample_buf().format_desc().unwrap();

let mut this = native.image_buf().clone();

let bytes_lock =
ImageBufExt::base_addr_lock(this.as_mut(), cv::pixel_buffer::LockFlags::READ_ONLY)?;
let bytes_lock = ImageBufExt::base_addr_lock(
image_buf.as_mut(),
cv::pixel_buffer::LockFlags::READ_ONLY,
)?;

let res = match cidre::four_cc_to_str(&mut format_desc.media_sub_type().to_be_bytes()) {
"2vuy" => {
Expand All @@ -73,7 +97,7 @@ impl CapturedFrameExt for CapturedFrame {
height as u32,
);

let src_stride = native.image_buf().plane_bytes_per_row(0);
let src_stride = plane0_stride;
let dest_stride = ff_frame.stride(0);

let src_bytes = bytes_lock.plane_data(0);
Expand All @@ -96,7 +120,7 @@ impl CapturedFrameExt for CapturedFrame {
height as u32,
);

let src_stride = native.image_buf().plane_bytes_per_row(0);
let src_stride = plane0_stride;
let dest_stride = ff_frame.stride(0);

let src_bytes = bytes_lock.plane_data(0);
Expand All @@ -110,7 +134,7 @@ impl CapturedFrameExt for CapturedFrame {
dest_row.copy_from_slice(src_row);
}

let src_stride = native.image_buf().plane_bytes_per_row(1);
let src_stride = plane1_stride;
let dest_stride = ff_frame.stride(1);

let src_bytes = bytes_lock.plane_data(1);
Expand All @@ -133,7 +157,7 @@ impl CapturedFrameExt for CapturedFrame {
height as u32,
);

let src_stride = native.image_buf().plane_bytes_per_row(0);
let src_stride = plane0_stride;
let dest_stride = ff_frame.stride(0);

let src_bytes = bytes_lock.plane_data(0);
Expand All @@ -156,7 +180,7 @@ impl CapturedFrameExt for CapturedFrame {
height as u32,
);

let src_stride = native.image_buf().plane_bytes_per_row(0);
let src_stride = plane0_stride;
let dest_stride = ff_frame.stride(0);

let src_bytes = bytes_lock.plane_data(0);
Expand All @@ -179,7 +203,7 @@ impl CapturedFrameExt for CapturedFrame {
height as u32,
);

let src_stride = native.image_buf().plane_bytes_per_row(0);
let src_stride = plane0_stride;
let dest_stride = ff_frame.stride(0);

let src_bytes = bytes_lock.plane_data(0);
Expand All @@ -202,7 +226,7 @@ impl CapturedFrameExt for CapturedFrame {
height as u32,
);

let src_stride = native.image_buf().plane_bytes_per_row(0);
let src_stride = plane0_stride;
let dest_stride = ff_frame.stride(0);

let src_bytes = bytes_lock.plane_data(0);
Expand All @@ -225,7 +249,7 @@ impl CapturedFrameExt for CapturedFrame {
height as u32,
);

let src_stride = native.image_buf().plane_bytes_per_row(0);
let src_stride = plane0_stride;
let dest_stride = ff_frame.stride(0);

let src_bytes = bytes_lock.plane_data(0);
Expand All @@ -248,7 +272,7 @@ impl CapturedFrameExt for CapturedFrame {
height as u32,
);

let src_stride = native.image_buf().plane_bytes_per_row(0);
let src_stride = plane0_stride;
let dest_stride = ff_frame.stride(0);

let src_bytes = bytes_lock.plane_data(0);
Expand All @@ -265,7 +289,6 @@ impl CapturedFrameExt for CapturedFrame {
ff_frame
}
"y420" => {
let plane_count = native.image_buf().plane_count();
if plane_count < 3 {
return Err(AsFFmpegError::InsufficientPlaneCount {
format: "y420".to_string(),
Expand All @@ -280,15 +303,13 @@ impl CapturedFrameExt for CapturedFrame {
height as u32,
);

for plane in 0..3 {
let src_stride = native.image_buf().plane_bytes_per_row(plane);
for (plane, &(src_stride, plane_height, row_width)) in plane_info.iter().enumerate()
{
let dest_stride = ff_frame.stride(plane);
let plane_height = native.image_buf().plane_height(plane);

let src_bytes = bytes_lock.plane_data(plane);
let dest_bytes = &mut ff_frame.data_mut(plane);

let row_width = native.image_buf().plane_width(plane);
for y in 0..plane_height {
let src_row = &src_bytes[y * src_stride..y * src_stride + row_width];
let dest_row =
Expand All @@ -306,7 +327,7 @@ impl CapturedFrameExt for CapturedFrame {
height as u32,
);

let src_stride = native.image_buf().plane_bytes_per_row(0);
let src_stride = plane0_stride;
let dest_stride = ff_frame.stride(0);

let src_bytes = bytes_lock.plane_data(0);
Expand Down Expand Up @@ -334,7 +355,7 @@ impl CapturedFrameExt for CapturedFrame {
let mut src_frame =
ffmpeg::frame::Video::new(info.pixel, width as u32, height as u32);

let src_stride = native.image_buf().plane_bytes_per_row(0);
let src_stride = plane0_stride;
let dest_stride = src_frame.stride(0);
let src_bytes = bytes_lock.plane_data(0);
let dest_bytes = &mut src_frame.data_mut(0);
Expand Down
14 changes: 6 additions & 8 deletions crates/camera/src/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,13 @@ pub(super) fn start_capturing_impl(
let queue = dispatch::Queue::new();
let delegate =
CallbackOutputDelegate::with(CallbackOutputDelegateInner::new(Box::new(move |data| {
let Some(image_buf) = data.sample_buf.image_buf() else {
if data.sample_buf.image_buf().is_none() {
return;
};

callback(CapturedFrame {
native: NativeCapturedFrame(image_buf.retained(), data.sample_buf.retained()),
// reference_time: Instant::now(),
native: NativeCapturedFrame(data.sample_buf.retained()),
timestamp: data.timestamp,
// capture_begin_time: Some(data.capture_begin_time),
});
})));

Expand Down Expand Up @@ -202,14 +200,14 @@ impl Debug for AVFoundationError {
}

#[derive(Debug, Clone)]
pub struct NativeCapturedFrame(arc::R<cv::ImageBuf>, arc::R<cm::SampleBuf>);
pub struct NativeCapturedFrame(arc::R<cm::SampleBuf>);

impl NativeCapturedFrame {
pub fn image_buf(&self) -> &arc::R<cv::ImageBuf> {
&self.0
pub fn image_buf(&self) -> Option<arc::R<cv::ImageBuf>> {
self.0.image_buf().map(|b| b.retained())
}

pub fn sample_buf(&self) -> &arc::R<cm::SampleBuf> {
&self.1
&self.0
}
}
Loading
Loading