triggercam 0.1.0__tar.gz
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.
- triggercam-0.1.0/LICENSE +21 -0
- triggercam-0.1.0/PKG-INFO +100 -0
- triggercam-0.1.0/README.md +81 -0
- triggercam-0.1.0/pyproject.toml +27 -0
- triggercam-0.1.0/setup.cfg +4 -0
- triggercam-0.1.0/src/triggercam/__init__.py +4 -0
- triggercam-0.1.0/src/triggercam/camera.py +152 -0
- triggercam-0.1.0/src/triggercam.egg-info/PKG-INFO +100 -0
- triggercam-0.1.0/src/triggercam.egg-info/SOURCES.txt +10 -0
- triggercam-0.1.0/src/triggercam.egg-info/dependency_links.txt +1 -0
- triggercam-0.1.0/src/triggercam.egg-info/requires.txt +1 -0
- triggercam-0.1.0/src/triggercam.egg-info/top_level.txt +1 -0
triggercam-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tuo170
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: triggercam
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Thread-safe webcam capture with on-demand start/stop recording, built on OpenCV
|
|
5
|
+
Author: tuo170
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/tuo170/triggercam
|
|
8
|
+
Project-URL: Issues, https://github.com/tuo170/triggercam/issues
|
|
9
|
+
Keywords: opencv,webcam,camera,recording,threading
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Topic :: Multimedia :: Video :: Capture
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: opencv-python>=4.5
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# triggercam
|
|
21
|
+
|
|
22
|
+
> Thread-safe webcam capture for Python with on-demand start/stop recording, built on OpenCV.
|
|
23
|
+
|
|
24
|
+
OpenCVベースの、スレッドセーフなWebカメラキャプチャライブラリです。プレビューを表示し続けたまま、録画の開始・停止を好きなタイミングでトリガーできます。
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import time
|
|
28
|
+
from triggercam import Camera
|
|
29
|
+
|
|
30
|
+
with Camera() as cam:
|
|
31
|
+
time.sleep(10)
|
|
32
|
+
cam.start_recording("clip") # clip.mp4 に書き込み開始
|
|
33
|
+
time.sleep(10)
|
|
34
|
+
cam.stop_recording() # clip_20260101-120000.mp4 にリネーム
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## なぜtriggercamなのか
|
|
38
|
+
|
|
39
|
+
よくある録画系のサンプルは「プログラム起動と同時に録画開始、`q`キーで終了」という一発勝負の構成がほとんどです。
|
|
40
|
+
|
|
41
|
+
カメラのプレビューは動かし続けたまま、録画のON/OFFだけを自分のコードから好きなタイミング(センサーのトリガー、スケジュール、ボタン操作、検知イベントなど)で切り替えたい、というケース向けの例はあまり見かけませんでした。
|
|
42
|
+
|
|
43
|
+
triggercam(本プログラム)では、それができます。
|
|
44
|
+
|
|
45
|
+
現状、単純な録画ON/OFFのみの機能となっています。
|
|
46
|
+
複数カメラ対応・動体検知・NVR的なUIが欲しい場合は、他の本格的なアプリケーションについても検討してください。
|
|
47
|
+
|
|
48
|
+
## インストール
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install triggercam
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
(まだPyPIには公開していません。今のところはソースから `pip install -e .` でインストールしてください)
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
- `Camera(index=0, width=1280, height=720, fps=30, show_preview=True)` — カメラを開き、即座にバックグラウンドのキャプチャスレッドを開始します。
|
|
59
|
+
- `cam.start_recording(filename)` — `<filename>.mp4`への書き込みを開始します。Writerが開けなかった場合(非対応コーデックなど)は`RuntimeError`を送出します。すでに録画中の場合は何もしません。
|
|
60
|
+
- `cam.stop_recording()` — 録画を停止し、ファイル名を`<filename>_<タイムスタンプ>.mp4`にリネームします。最終的なパスを返します(録画していなかった場合は`None`)。
|
|
61
|
+
- `cam.running` — キャプチャスレッドが停止すると`False`になります(カメラが切断された場合、プレビューウィンドウで`q`が押された場合など)。
|
|
62
|
+
- `cam.close()` — スレッドを停止し、カメラ・Writerのリソースを解放します。プログラムが中断されても録画ファイルが壊れた状態で残らないよう、必ず呼び出してください(`with Camera() as cam:`を使えば自動的に呼ばれます)。
|
|
63
|
+
|
|
64
|
+
一通りの流れと、Ctrl+Cでの安全な終了方法は[`examples/basic_recording.py`](examples/basic_recording.py)を参照してください。
|
|
65
|
+
|
|
66
|
+
## 動作検証
|
|
67
|
+
|
|
68
|
+
### 検証環境(2026-08-21)
|
|
69
|
+
|
|
70
|
+
| 項目 | 内容 |
|
|
71
|
+
|---|---|
|
|
72
|
+
| OS | Windows 11 Pro |
|
|
73
|
+
| Python | 3.10.6(プロジェクト専用のvenv) |
|
|
74
|
+
| インストール方法 | `pip install -e .` |
|
|
75
|
+
| 依存パッケージ | opencv-python 5.0.0.93 |
|
|
76
|
+
| カメラ | Logi C270 HD WebCam(USB UVCカメラ) |
|
|
77
|
+
|
|
78
|
+
### 確認できたこと
|
|
79
|
+
|
|
80
|
+
- カメラが正しく認識され、プレビューウィンドウに実際の映像が表示されることを確認(起動してからプレビューウィンドウが表示されるまで少し時間がかかる)。
|
|
81
|
+
- `start_recording()` → `stop_recording()` の一連の流れが正常に動作し、`clip.mp4` が `clip_<タイムスタンプ>.mp4` に正しくリネームされることを確認。
|
|
82
|
+
- 録画サイクル(10秒待機→録画→待機→停止)が問題なく動作することを確認。
|
|
83
|
+
- 録画中に`Ctrl+C`で強制中断した際、そのファイルが正しく`stop_recording()`まで処理された状態で残ることを確認(未リネームの壊れたファイルが残らない)。
|
|
84
|
+
- 生成された動画ファイルはメディアプレイヤーで正常に再生可能なことを確認。
|
|
85
|
+
|
|
86
|
+
### 未検証
|
|
87
|
+
|
|
88
|
+
- Linux / macOSでの動作(カメラインデックスでのキャプチャのため動作するはずだが、実機未確認)
|
|
89
|
+
|
|
90
|
+
## ライセンス
|
|
91
|
+
|
|
92
|
+
MIT — [LICENSE](LICENSE)を参照してください。
|
|
93
|
+
|
|
94
|
+
## 背景
|
|
95
|
+
|
|
96
|
+
このライブラリは、[Qiitaに投稿した元のプログラム](https://qiita.com/tuo170/items/ca1933a88f3255792214)
|
|
97
|
+
( https://qiita.com/tuo170/items/ca1933a88f3255792214 )
|
|
98
|
+
を土台に、バグ修正・API設計・パッケージ化を行って作られています。
|
|
99
|
+
|
|
100
|
+
実装のコーディングやレビュー、設計の壁打ち相手としてClaude Codeを使用しました。
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# triggercam
|
|
2
|
+
|
|
3
|
+
> Thread-safe webcam capture for Python with on-demand start/stop recording, built on OpenCV.
|
|
4
|
+
|
|
5
|
+
OpenCVベースの、スレッドセーフなWebカメラキャプチャライブラリです。プレビューを表示し続けたまま、録画の開始・停止を好きなタイミングでトリガーできます。
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
import time
|
|
9
|
+
from triggercam import Camera
|
|
10
|
+
|
|
11
|
+
with Camera() as cam:
|
|
12
|
+
time.sleep(10)
|
|
13
|
+
cam.start_recording("clip") # clip.mp4 に書き込み開始
|
|
14
|
+
time.sleep(10)
|
|
15
|
+
cam.stop_recording() # clip_20260101-120000.mp4 にリネーム
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## なぜtriggercamなのか
|
|
19
|
+
|
|
20
|
+
よくある録画系のサンプルは「プログラム起動と同時に録画開始、`q`キーで終了」という一発勝負の構成がほとんどです。
|
|
21
|
+
|
|
22
|
+
カメラのプレビューは動かし続けたまま、録画のON/OFFだけを自分のコードから好きなタイミング(センサーのトリガー、スケジュール、ボタン操作、検知イベントなど)で切り替えたい、というケース向けの例はあまり見かけませんでした。
|
|
23
|
+
|
|
24
|
+
triggercam(本プログラム)では、それができます。
|
|
25
|
+
|
|
26
|
+
現状、単純な録画ON/OFFのみの機能となっています。
|
|
27
|
+
複数カメラ対応・動体検知・NVR的なUIが欲しい場合は、他の本格的なアプリケーションについても検討してください。
|
|
28
|
+
|
|
29
|
+
## インストール
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install triggercam
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
(まだPyPIには公開していません。今のところはソースから `pip install -e .` でインストールしてください)
|
|
36
|
+
|
|
37
|
+
## API
|
|
38
|
+
|
|
39
|
+
- `Camera(index=0, width=1280, height=720, fps=30, show_preview=True)` — カメラを開き、即座にバックグラウンドのキャプチャスレッドを開始します。
|
|
40
|
+
- `cam.start_recording(filename)` — `<filename>.mp4`への書き込みを開始します。Writerが開けなかった場合(非対応コーデックなど)は`RuntimeError`を送出します。すでに録画中の場合は何もしません。
|
|
41
|
+
- `cam.stop_recording()` — 録画を停止し、ファイル名を`<filename>_<タイムスタンプ>.mp4`にリネームします。最終的なパスを返します(録画していなかった場合は`None`)。
|
|
42
|
+
- `cam.running` — キャプチャスレッドが停止すると`False`になります(カメラが切断された場合、プレビューウィンドウで`q`が押された場合など)。
|
|
43
|
+
- `cam.close()` — スレッドを停止し、カメラ・Writerのリソースを解放します。プログラムが中断されても録画ファイルが壊れた状態で残らないよう、必ず呼び出してください(`with Camera() as cam:`を使えば自動的に呼ばれます)。
|
|
44
|
+
|
|
45
|
+
一通りの流れと、Ctrl+Cでの安全な終了方法は[`examples/basic_recording.py`](examples/basic_recording.py)を参照してください。
|
|
46
|
+
|
|
47
|
+
## 動作検証
|
|
48
|
+
|
|
49
|
+
### 検証環境(2026-08-21)
|
|
50
|
+
|
|
51
|
+
| 項目 | 内容 |
|
|
52
|
+
|---|---|
|
|
53
|
+
| OS | Windows 11 Pro |
|
|
54
|
+
| Python | 3.10.6(プロジェクト専用のvenv) |
|
|
55
|
+
| インストール方法 | `pip install -e .` |
|
|
56
|
+
| 依存パッケージ | opencv-python 5.0.0.93 |
|
|
57
|
+
| カメラ | Logi C270 HD WebCam(USB UVCカメラ) |
|
|
58
|
+
|
|
59
|
+
### 確認できたこと
|
|
60
|
+
|
|
61
|
+
- カメラが正しく認識され、プレビューウィンドウに実際の映像が表示されることを確認(起動してからプレビューウィンドウが表示されるまで少し時間がかかる)。
|
|
62
|
+
- `start_recording()` → `stop_recording()` の一連の流れが正常に動作し、`clip.mp4` が `clip_<タイムスタンプ>.mp4` に正しくリネームされることを確認。
|
|
63
|
+
- 録画サイクル(10秒待機→録画→待機→停止)が問題なく動作することを確認。
|
|
64
|
+
- 録画中に`Ctrl+C`で強制中断した際、そのファイルが正しく`stop_recording()`まで処理された状態で残ることを確認(未リネームの壊れたファイルが残らない)。
|
|
65
|
+
- 生成された動画ファイルはメディアプレイヤーで正常に再生可能なことを確認。
|
|
66
|
+
|
|
67
|
+
### 未検証
|
|
68
|
+
|
|
69
|
+
- Linux / macOSでの動作(カメラインデックスでのキャプチャのため動作するはずだが、実機未確認)
|
|
70
|
+
|
|
71
|
+
## ライセンス
|
|
72
|
+
|
|
73
|
+
MIT — [LICENSE](LICENSE)を参照してください。
|
|
74
|
+
|
|
75
|
+
## 背景
|
|
76
|
+
|
|
77
|
+
このライブラリは、[Qiitaに投稿した元のプログラム](https://qiita.com/tuo170/items/ca1933a88f3255792214)
|
|
78
|
+
( https://qiita.com/tuo170/items/ca1933a88f3255792214 )
|
|
79
|
+
を土台に、バグ修正・API設計・パッケージ化を行って作られています。
|
|
80
|
+
|
|
81
|
+
実装のコーディングやレビュー、設計の壁打ち相手としてClaude Codeを使用しました。
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "triggercam"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Thread-safe webcam capture with on-demand start/stop recording, built on OpenCV"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "tuo170" }]
|
|
13
|
+
keywords = ["opencv", "webcam", "camera", "recording", "threading"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Topic :: Multimedia :: Video :: Capture",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
]
|
|
20
|
+
dependencies = ["opencv-python>=4.5"]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://github.com/tuo170/triggercam"
|
|
24
|
+
Issues = "https://github.com/tuo170/triggercam/issues"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["src"]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Thread-safe webcam capture with on-demand start/stop recording.
|
|
2
|
+
|
|
3
|
+
The camera is opened once and continuously read on a background thread so
|
|
4
|
+
that a preview window (if enabled) stays responsive. Recording to a video
|
|
5
|
+
file can be toggled on and off at any time from another thread via
|
|
6
|
+
``start_recording()`` / ``stop_recording()``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import datetime
|
|
12
|
+
import os
|
|
13
|
+
import threading
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
import cv2
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Camera:
|
|
20
|
+
"""Continuously captures frames from a webcam on a background thread.
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
with Camera() as cam:
|
|
24
|
+
time.sleep(10)
|
|
25
|
+
cam.start_recording("clip")
|
|
26
|
+
time.sleep(10)
|
|
27
|
+
cam.stop_recording()
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
index: int = 0,
|
|
33
|
+
width: int = 1280,
|
|
34
|
+
height: int = 720,
|
|
35
|
+
fps: int = 30,
|
|
36
|
+
show_preview: bool = True,
|
|
37
|
+
) -> None:
|
|
38
|
+
self.index = index
|
|
39
|
+
self.width = width
|
|
40
|
+
self.height = height
|
|
41
|
+
self.fps = fps
|
|
42
|
+
self.show_preview = show_preview
|
|
43
|
+
|
|
44
|
+
self._cap = cv2.VideoCapture(self.index)
|
|
45
|
+
self._cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter.fourcc(*"MJPG"))
|
|
46
|
+
self._cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.width)
|
|
47
|
+
self._cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height)
|
|
48
|
+
self._cap.set(cv2.CAP_PROP_FPS, self.fps)
|
|
49
|
+
|
|
50
|
+
self._writer: Optional[cv2.VideoWriter] = None
|
|
51
|
+
self._writer_lock = threading.Lock()
|
|
52
|
+
self._recording = False
|
|
53
|
+
self._filename: Optional[str] = None
|
|
54
|
+
self._stopped = False
|
|
55
|
+
|
|
56
|
+
self._thread = threading.Thread(target=self._run, daemon=True)
|
|
57
|
+
self._thread.start()
|
|
58
|
+
|
|
59
|
+
# -- public API -----------------------------------------------------
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def running(self) -> bool:
|
|
63
|
+
"""False once the capture thread has stopped (e.g. camera lost, or
|
|
64
|
+
'q' pressed in the preview window)."""
|
|
65
|
+
return not self._stopped
|
|
66
|
+
|
|
67
|
+
def start_recording(self, filename: str) -> None:
|
|
68
|
+
"""Start writing frames to ``<filename>.mp4``.
|
|
69
|
+
|
|
70
|
+
Safe to call from any thread. No-op if already recording. Raises
|
|
71
|
+
``RuntimeError`` if the video writer could not be opened (e.g.
|
|
72
|
+
unsupported codec), so recording state is never silently wrong.
|
|
73
|
+
"""
|
|
74
|
+
with self._writer_lock:
|
|
75
|
+
if self._recording:
|
|
76
|
+
return
|
|
77
|
+
writer = cv2.VideoWriter(
|
|
78
|
+
f"{filename}.mp4",
|
|
79
|
+
cv2.VideoWriter.fourcc(*"mp4v"),
|
|
80
|
+
self.fps,
|
|
81
|
+
(self.width, self.height),
|
|
82
|
+
)
|
|
83
|
+
if not writer.isOpened():
|
|
84
|
+
raise RuntimeError(f"Failed to open video writer for {filename}.mp4")
|
|
85
|
+
# Only flip the flag once the writer is confirmed ready, so the
|
|
86
|
+
# capture thread never sees recording=True before there is
|
|
87
|
+
# somewhere to write to.
|
|
88
|
+
self._writer = writer
|
|
89
|
+
self._filename = filename
|
|
90
|
+
self._recording = True
|
|
91
|
+
|
|
92
|
+
def stop_recording(self) -> Optional[str]:
|
|
93
|
+
"""Stop recording and rename the file with a timestamp suffix.
|
|
94
|
+
|
|
95
|
+
Returns the final filename, or ``None`` if nothing was recording.
|
|
96
|
+
Safe to call even if recording never successfully started.
|
|
97
|
+
"""
|
|
98
|
+
with self._writer_lock:
|
|
99
|
+
if not self._recording:
|
|
100
|
+
return None
|
|
101
|
+
self._recording = False
|
|
102
|
+
if self._writer is not None:
|
|
103
|
+
self._writer.release()
|
|
104
|
+
self._writer = None
|
|
105
|
+
filename = self._filename
|
|
106
|
+
self._filename = None
|
|
107
|
+
src = f"{filename}.mp4"
|
|
108
|
+
if not os.path.exists(src):
|
|
109
|
+
return None
|
|
110
|
+
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
111
|
+
dst = f"{filename}_{timestamp}.mp4"
|
|
112
|
+
os.rename(src, dst)
|
|
113
|
+
return dst
|
|
114
|
+
|
|
115
|
+
def close(self) -> None:
|
|
116
|
+
"""Stop the capture thread and release all resources.
|
|
117
|
+
|
|
118
|
+
Finalizes any in-progress recording first, so Ctrl+C / normal
|
|
119
|
+
shutdown never leaves a corrupted, un-finalized video file. Safe to
|
|
120
|
+
call multiple times.
|
|
121
|
+
"""
|
|
122
|
+
if self._stopped:
|
|
123
|
+
return
|
|
124
|
+
self._stopped = True
|
|
125
|
+
self._thread.join(timeout=2)
|
|
126
|
+
self.stop_recording()
|
|
127
|
+
if self._cap.isOpened():
|
|
128
|
+
self._cap.release()
|
|
129
|
+
if self.show_preview:
|
|
130
|
+
cv2.destroyAllWindows()
|
|
131
|
+
|
|
132
|
+
def __enter__(self) -> "Camera":
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
def __exit__(self, exc_type, exc, tb) -> None:
|
|
136
|
+
self.close()
|
|
137
|
+
|
|
138
|
+
# -- internal ---------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
def _run(self) -> None:
|
|
141
|
+
while not self._stopped:
|
|
142
|
+
ret, frame = self._cap.read()
|
|
143
|
+
if not ret:
|
|
144
|
+
continue
|
|
145
|
+
if self.show_preview:
|
|
146
|
+
cv2.imshow("triggercam", frame)
|
|
147
|
+
if cv2.waitKey(1) & 0xFF == ord("q"):
|
|
148
|
+
self._stopped = True
|
|
149
|
+
break
|
|
150
|
+
with self._writer_lock:
|
|
151
|
+
if self._recording and self._writer is not None:
|
|
152
|
+
self._writer.write(frame)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: triggercam
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Thread-safe webcam capture with on-demand start/stop recording, built on OpenCV
|
|
5
|
+
Author: tuo170
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/tuo170/triggercam
|
|
8
|
+
Project-URL: Issues, https://github.com/tuo170/triggercam/issues
|
|
9
|
+
Keywords: opencv,webcam,camera,recording,threading
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Topic :: Multimedia :: Video :: Capture
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: opencv-python>=4.5
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# triggercam
|
|
21
|
+
|
|
22
|
+
> Thread-safe webcam capture for Python with on-demand start/stop recording, built on OpenCV.
|
|
23
|
+
|
|
24
|
+
OpenCVベースの、スレッドセーフなWebカメラキャプチャライブラリです。プレビューを表示し続けたまま、録画の開始・停止を好きなタイミングでトリガーできます。
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import time
|
|
28
|
+
from triggercam import Camera
|
|
29
|
+
|
|
30
|
+
with Camera() as cam:
|
|
31
|
+
time.sleep(10)
|
|
32
|
+
cam.start_recording("clip") # clip.mp4 に書き込み開始
|
|
33
|
+
time.sleep(10)
|
|
34
|
+
cam.stop_recording() # clip_20260101-120000.mp4 にリネーム
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## なぜtriggercamなのか
|
|
38
|
+
|
|
39
|
+
よくある録画系のサンプルは「プログラム起動と同時に録画開始、`q`キーで終了」という一発勝負の構成がほとんどです。
|
|
40
|
+
|
|
41
|
+
カメラのプレビューは動かし続けたまま、録画のON/OFFだけを自分のコードから好きなタイミング(センサーのトリガー、スケジュール、ボタン操作、検知イベントなど)で切り替えたい、というケース向けの例はあまり見かけませんでした。
|
|
42
|
+
|
|
43
|
+
triggercam(本プログラム)では、それができます。
|
|
44
|
+
|
|
45
|
+
現状、単純な録画ON/OFFのみの機能となっています。
|
|
46
|
+
複数カメラ対応・動体検知・NVR的なUIが欲しい場合は、他の本格的なアプリケーションについても検討してください。
|
|
47
|
+
|
|
48
|
+
## インストール
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install triggercam
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
(まだPyPIには公開していません。今のところはソースから `pip install -e .` でインストールしてください)
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
- `Camera(index=0, width=1280, height=720, fps=30, show_preview=True)` — カメラを開き、即座にバックグラウンドのキャプチャスレッドを開始します。
|
|
59
|
+
- `cam.start_recording(filename)` — `<filename>.mp4`への書き込みを開始します。Writerが開けなかった場合(非対応コーデックなど)は`RuntimeError`を送出します。すでに録画中の場合は何もしません。
|
|
60
|
+
- `cam.stop_recording()` — 録画を停止し、ファイル名を`<filename>_<タイムスタンプ>.mp4`にリネームします。最終的なパスを返します(録画していなかった場合は`None`)。
|
|
61
|
+
- `cam.running` — キャプチャスレッドが停止すると`False`になります(カメラが切断された場合、プレビューウィンドウで`q`が押された場合など)。
|
|
62
|
+
- `cam.close()` — スレッドを停止し、カメラ・Writerのリソースを解放します。プログラムが中断されても録画ファイルが壊れた状態で残らないよう、必ず呼び出してください(`with Camera() as cam:`を使えば自動的に呼ばれます)。
|
|
63
|
+
|
|
64
|
+
一通りの流れと、Ctrl+Cでの安全な終了方法は[`examples/basic_recording.py`](examples/basic_recording.py)を参照してください。
|
|
65
|
+
|
|
66
|
+
## 動作検証
|
|
67
|
+
|
|
68
|
+
### 検証環境(2026-08-21)
|
|
69
|
+
|
|
70
|
+
| 項目 | 内容 |
|
|
71
|
+
|---|---|
|
|
72
|
+
| OS | Windows 11 Pro |
|
|
73
|
+
| Python | 3.10.6(プロジェクト専用のvenv) |
|
|
74
|
+
| インストール方法 | `pip install -e .` |
|
|
75
|
+
| 依存パッケージ | opencv-python 5.0.0.93 |
|
|
76
|
+
| カメラ | Logi C270 HD WebCam(USB UVCカメラ) |
|
|
77
|
+
|
|
78
|
+
### 確認できたこと
|
|
79
|
+
|
|
80
|
+
- カメラが正しく認識され、プレビューウィンドウに実際の映像が表示されることを確認(起動してからプレビューウィンドウが表示されるまで少し時間がかかる)。
|
|
81
|
+
- `start_recording()` → `stop_recording()` の一連の流れが正常に動作し、`clip.mp4` が `clip_<タイムスタンプ>.mp4` に正しくリネームされることを確認。
|
|
82
|
+
- 録画サイクル(10秒待機→録画→待機→停止)が問題なく動作することを確認。
|
|
83
|
+
- 録画中に`Ctrl+C`で強制中断した際、そのファイルが正しく`stop_recording()`まで処理された状態で残ることを確認(未リネームの壊れたファイルが残らない)。
|
|
84
|
+
- 生成された動画ファイルはメディアプレイヤーで正常に再生可能なことを確認。
|
|
85
|
+
|
|
86
|
+
### 未検証
|
|
87
|
+
|
|
88
|
+
- Linux / macOSでの動作(カメラインデックスでのキャプチャのため動作するはずだが、実機未確認)
|
|
89
|
+
|
|
90
|
+
## ライセンス
|
|
91
|
+
|
|
92
|
+
MIT — [LICENSE](LICENSE)を参照してください。
|
|
93
|
+
|
|
94
|
+
## 背景
|
|
95
|
+
|
|
96
|
+
このライブラリは、[Qiitaに投稿した元のプログラム](https://qiita.com/tuo170/items/ca1933a88f3255792214)
|
|
97
|
+
( https://qiita.com/tuo170/items/ca1933a88f3255792214 )
|
|
98
|
+
を土台に、バグ修正・API設計・パッケージ化を行って作られています。
|
|
99
|
+
|
|
100
|
+
実装のコーディングやレビュー、設計の壁打ち相手としてClaude Codeを使用しました。
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/triggercam/__init__.py
|
|
5
|
+
src/triggercam/camera.py
|
|
6
|
+
src/triggercam.egg-info/PKG-INFO
|
|
7
|
+
src/triggercam.egg-info/SOURCES.txt
|
|
8
|
+
src/triggercam.egg-info/dependency_links.txt
|
|
9
|
+
src/triggercam.egg-info/requires.txt
|
|
10
|
+
src/triggercam.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
opencv-python>=4.5
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
triggercam
|