swap-cli 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
swap_cli/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """swap-cli — desktop deepfake CLI built on Decart Lucy 2."""
2
+
3
+ from .version import __version__
4
+
5
+ __all__ = ["__version__"]
swap_cli/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Entrypoint for `python -m swap_cli`."""
2
+
3
+ from .cli import app
4
+
5
+ if __name__ == "__main__":
6
+ app()
swap_cli/camera.py ADDED
@@ -0,0 +1,83 @@
1
+ """OpenCV webcam capture exposed as an aiortc VideoStreamTrack."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import fractions
7
+ import sys
8
+ import time
9
+ from typing import Final
10
+
11
+ import av
12
+ import cv2
13
+ from aiortc import VideoStreamTrack
14
+
15
+ VIDEO_CLOCK_RATE: Final = 90_000 # standard for video tracks
16
+
17
+
18
+ def _platform_backend() -> int:
19
+ """Pick the most stable cv2 capture backend for the current OS."""
20
+ if sys.platform == "win32":
21
+ return cv2.CAP_DSHOW
22
+ if sys.platform == "darwin":
23
+ return cv2.CAP_AVFOUNDATION
24
+ return cv2.CAP_V4L2
25
+
26
+
27
+ class CameraTrack(VideoStreamTrack):
28
+ """Wraps a `cv2.VideoCapture` device as a WebRTC video track.
29
+
30
+ Designed to be passed to `RealtimeClient.connect(local_track=...)`.
31
+ Frames are pulled lazily as Decart consumes them, paced to the model's fps.
32
+ """
33
+
34
+ kind = "video"
35
+
36
+ def __init__(self, *, device: int = 0, width: int, height: int, fps: int) -> None:
37
+ super().__init__()
38
+ self._cap = cv2.VideoCapture(device, _platform_backend())
39
+ if not self._cap.isOpened():
40
+ raise RuntimeError(f"Could not open camera device {device}")
41
+
42
+ self._cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
43
+ self._cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
44
+ self._cap.set(cv2.CAP_PROP_FPS, fps)
45
+
46
+ self._width = width
47
+ self._height = height
48
+ self._fps = fps
49
+ self._frame_interval = 1.0 / fps
50
+ self._next_frame_at = time.monotonic()
51
+ self._frame_count = 0
52
+ self._stopped = False
53
+
54
+ async def recv(self) -> av.VideoFrame: # noqa: D401 — aiortc protocol
55
+ # Pace at the model's fps so we don't spam frames faster than Decart
56
+ # negotiates them on the wire.
57
+ now = time.monotonic()
58
+ wait = self._next_frame_at - now
59
+ if wait > 0:
60
+ await asyncio.sleep(wait)
61
+ self._next_frame_at = max(now, self._next_frame_at) + self._frame_interval
62
+
63
+ ok, frame = await asyncio.to_thread(self._cap.read)
64
+ if not ok or frame is None:
65
+ raise RuntimeError("Camera read failed — is another app using the webcam?")
66
+
67
+ # OpenCV is BGR; aiortc/PyAV want a known pixel format.
68
+ rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
69
+ video_frame = av.VideoFrame.from_ndarray(rgb, format="rgb24")
70
+ video_frame.pts = self._frame_count
71
+ video_frame.time_base = fractions.Fraction(1, self._fps)
72
+ self._frame_count += 1
73
+ return video_frame
74
+
75
+ def stop(self) -> None:
76
+ if self._stopped:
77
+ return
78
+ self._stopped = True
79
+ try:
80
+ self._cap.release()
81
+ except Exception:
82
+ pass
83
+ super().stop()