forked from dsutanto/zenai-kpc-python
update recording feature saat status IN/OUT
This commit is contained in:
1 parent
3c30ded269
commit
c80984ba80
6 files changed
+535
-13
No files matched your search
+75
-6
@@ -9,6 +9,7 @@ import cv2
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import signal
|
||||
import socket
|
||||
import threading
|
||||
@@ -23,6 +24,7 @@ load_dotenv()
|
||||
|
||||
from rknnlite.api import RKNNLite
|
||||
from counter_store import CounterStore
|
||||
from video_session_writer import VideoSessionWriter
|
||||
|
||||
# --- config (override via env / .env) ---
|
||||
OUTPUT_DIR = os.getenv("OUTPUT_DIR", "/opt/jetson-counter")
|
||||
@@ -96,12 +98,23 @@ RECONNECT_DELAY_SEC = int(os.getenv("RECONNECT_DELAY_SEC", "3"))
|
||||
MAX_RECONNECT_ATTEMPTS = int(os.getenv("MAX_RECONNECT_ATTEMPTS", "0"))
|
||||
TRACKED_PRUNE_SEC = int(os.getenv("TRACKED_PRUNE_SEC", "300"))
|
||||
RECORD_VIDEO = os.getenv("RECORD_VIDEO", "false").lower() == "true"
|
||||
VIDEO_SEGMENT_SEC = int(os.getenv("VIDEO_SEGMENT_SEC", "3600"))
|
||||
VIDEO_SEGMENT_SEC = int(os.getenv("VIDEO_SEGMENT_SEC", "3600")) # legacy segment mode only
|
||||
OUTPUT_FPS = int(os.getenv("OUTPUT_FPS", "15"))
|
||||
# ByteTrack-style session recording: encode in SHM, move to VIDEO_OUTPUT_DIR on session end.
|
||||
SHM_DIR = os.getenv("SHM_DIR", "/dev/shm/zenai-kpc-counter")
|
||||
VIDEO_OUTPUT_DIR = os.getenv("VIDEO_OUTPUT_DIR", OUTPUT_DIR)
|
||||
RECORD_END_DELAY = float(os.getenv("RECORD_END_DELAY", "1"))
|
||||
RECORD_DISCARD_EMPTY = os.getenv("RECORD_DISCARD_EMPTY", "true").lower() == "true"
|
||||
RECORD_RETENTION_DAYS = int(os.getenv("RECORD_RETENTION_DAYS", "3"))
|
||||
_crf_raw = os.getenv("RECORD_CRF", "").strip()
|
||||
RECORD_CRF = int(_crf_raw) if _crf_raw else -1
|
||||
RECORD_PRESET = os.getenv("RECORD_PRESET", "").strip()
|
||||
# segment = old hourly dump to OUTPUT_DIR; session = SHM + webhook/control lifecycle
|
||||
RECORD_MODE = os.getenv("RECORD_MODE", "session").strip().lower()
|
||||
|
||||
LIVE_STREAM_ENABLED = os.getenv("LIVE_STREAM_ENABLED", "false").lower() == "true"
|
||||
LIVE_STREAM_FRAME_PATH = os.getenv(
|
||||
"LIVE_STREAM_FRAME_PATH", "/dev/shm/jetson-counter/live_frame.jpg"
|
||||
"LIVE_STREAM_FRAME_PATH", f"{SHM_DIR}/live_frame.jpg"
|
||||
)
|
||||
LIVE_STREAM_QUALITY = int(os.getenv("LIVE_STREAM_QUALITY", "75"))
|
||||
LIVE_STREAM_EVERY_N = int(os.getenv("LIVE_STREAM_EVERY_N", "2"))
|
||||
@@ -145,7 +158,7 @@ CONTROL_SOCKET_PORT = int(os.getenv("CONTROL_SOCKET_PORT", "5090"))
|
||||
STATUS_WEBHOOK_ENABLED = os.getenv("STATUS_WEBHOOK_ENABLED", "false").lower() == "true"
|
||||
STATUS_WEBHOOK_FILE = os.getenv(
|
||||
"STATUS_WEBHOOK_FILE",
|
||||
"/opt/zenai-kpc-python/status_webhook_state",
|
||||
"/opt/zenai-kpc-python/status_webhook_state.json",
|
||||
)
|
||||
_active_raw = os.getenv("STATUS_WEBHOOK_ACTIVE_VALUES", "IN,OUT")
|
||||
STATUS_WEBHOOK_ACTIVE_VALUES = frozenset(
|
||||
@@ -1297,7 +1310,36 @@ def run():
|
||||
print(f"State: {STATE_FILE}")
|
||||
|
||||
if RECORD_VIDEO:
|
||||
video_writer = VideoSegmentWriter(OUTPUT_DIR, w, h, fps, VIDEO_SEGMENT_SEC)
|
||||
if RECORD_MODE == "segment":
|
||||
video_writer = VideoSegmentWriter(OUTPUT_DIR, w, h, fps, VIDEO_SEGMENT_SEC)
|
||||
print(
|
||||
f"Recording (segment): {OUTPUT_DIR} every {VIDEO_SEGMENT_SEC}s"
|
||||
)
|
||||
else:
|
||||
video_writer = VideoSessionWriter(
|
||||
shm_dir=SHM_DIR,
|
||||
video_output_dir=VIDEO_OUTPUT_DIR,
|
||||
w=w,
|
||||
h=h,
|
||||
fps=fps if fps and fps > 1 else OUTPUT_FPS,
|
||||
end_delay_sec=RECORD_END_DELAY,
|
||||
retention_days=RECORD_RETENTION_DAYS,
|
||||
crf=RECORD_CRF,
|
||||
preset=RECORD_PRESET,
|
||||
discard_empty=RECORD_DISCARD_EMPTY,
|
||||
)
|
||||
print(
|
||||
f"Recording (session): SHM={SHM_DIR} → out={VIDEO_OUTPUT_DIR} | "
|
||||
f"end_delay={RECORD_END_DELAY}s discard_empty={RECORD_DISCARD_EMPTY} | "
|
||||
f"ffmpeg={'yes' if shutil.which('ffmpeg') else 'NO'}"
|
||||
)
|
||||
# If webhook already active at startup, begin a session immediately.
|
||||
if STATUS_WEBHOOK_ENABLED and status_nyala_active:
|
||||
video_writer.note_session_start(
|
||||
status_webhook_value, store.get_counting_date()
|
||||
)
|
||||
elif not STATUS_WEBHOOK_ENABLED and counting_active:
|
||||
video_writer.note_session_start("SESSION", store.get_counting_date())
|
||||
|
||||
reconnect_count = 0
|
||||
|
||||
@@ -1338,14 +1380,37 @@ def run():
|
||||
f"[{now_str()}] Counting "
|
||||
f"{'RESUMED' if counting_active else 'PAUSED'} via control file"
|
||||
)
|
||||
if (
|
||||
isinstance(video_writer, VideoSessionWriter)
|
||||
and not STATUS_WEBHOOK_ENABLED
|
||||
):
|
||||
if counting_active:
|
||||
video_writer.note_session_start(
|
||||
"SESSION", store.get_counting_date()
|
||||
)
|
||||
else:
|
||||
video_writer.note_session_end()
|
||||
if STATUS_WEBHOOK_ENABLED:
|
||||
new_status = read_status_webhook()
|
||||
if new_status != status_webhook_value:
|
||||
prev_status = status_webhook_value
|
||||
status_webhook_value = new_status
|
||||
status_nyala_active = status_webhook_allows_counting(
|
||||
status_webhook_value
|
||||
)
|
||||
print(f"[{now_str()}] Status webhook {status_webhook_value}")
|
||||
if isinstance(video_writer, VideoSessionWriter):
|
||||
was_active = status_webhook_allows_counting(prev_status)
|
||||
if status_nyala_active and not was_active:
|
||||
video_writer.note_session_start(
|
||||
status_webhook_value, store.get_counting_date()
|
||||
)
|
||||
elif was_active and not status_nyala_active:
|
||||
video_writer.note_session_end()
|
||||
|
||||
# Raw frames only (before overlay). Session writer encodes here every frame.
|
||||
if isinstance(video_writer, VideoSessionWriter):
|
||||
video_writer.feed_frame(frame)
|
||||
|
||||
counting_allowed = counting_active and status_nyala_active
|
||||
skip_inference = False
|
||||
@@ -1578,6 +1643,8 @@ def run():
|
||||
object_crossed_frame = True
|
||||
cross_events_frame.append((tid, direction))
|
||||
crossing_times.append(mono)
|
||||
if isinstance(video_writer, VideoSessionWriter):
|
||||
video_writer.note_crossing()
|
||||
object_cross_flash1[tid] = CROSS_FLASH_FRAMES
|
||||
object_cross_flash2[tid] = CROSS_FLASH_FRAMES
|
||||
popups.append(
|
||||
@@ -1667,7 +1734,7 @@ def run():
|
||||
count_in_pulse = max(0, count_in_pulse - 1)
|
||||
count_out_pulse = max(0, count_out_pulse - 1)
|
||||
|
||||
if video_writer is not None:
|
||||
if isinstance(video_writer, VideoSegmentWriter):
|
||||
video_writer.write(frame)
|
||||
|
||||
if LIVE_STREAM_ENABLED and frame_idx % LIVE_STREAM_EVERY_N == 0:
|
||||
@@ -1723,7 +1790,9 @@ def run():
|
||||
prune_stale_tracks(object_tracked, mono)
|
||||
|
||||
cap.release()
|
||||
if video_writer is not None:
|
||||
if isinstance(video_writer, VideoSessionWriter):
|
||||
video_writer.shutdown()
|
||||
elif video_writer is not None:
|
||||
video_writer.release()
|
||||
if cross_logger:
|
||||
cross_logger.close()
|
||||
|
||||
Reference in new issue
Block a user