this may not be shown

This commit is contained in:
lda committed 2026-03-01 20:01:19 +07:00
1 parent 61af362da5
commit 358f4650b5
1 file changed
+46 -4
+46 -4
View File
@@ -6,6 +6,7 @@ import threading
import time import time
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from pathlib import Path
from queue import Empty, Queue from queue import Empty, Queue
from typing import Any, Callable from typing import Any, Callable
@@ -54,6 +55,7 @@ class PipelineManager:
self._thread: threading.Thread | None = None self._thread: threading.Thread | None = None
self._start_thread: threading.Thread | None = None self._start_thread: threading.Thread | None = None
self._sinks: list[Sink] = [] self._sinks: list[Sink] = []
self._session_id = 0
self._source_factory: SourceFactory | None = None self._source_factory: SourceFactory | None = None
self._sink_factory: SinkFactory | None = None self._sink_factory: SinkFactory | None = None
@@ -62,6 +64,7 @@ class PipelineManager:
self._source_spec: str = "0" self._source_spec: str = "0"
self.running = False self.running = False
self.starting = False self.starting = False
self.stopping = False
self.paused = False self.paused = False
self.last_error: str | None = None self.last_error: str | None = None
@@ -78,15 +81,17 @@ class PipelineManager:
self._pipeline_factory = pipeline_factory self._pipeline_factory = pipeline_factory
self._source_spec = initial_source self._source_spec = initial_source
def _close_sinks(self): def _close_sinks(self, sinks: list[Sink] | None = None):
for sink in self._sinks: sink_list = self._sinks if sinks is None else sinks
for sink in sink_list:
try: try:
sink.close() sink.close()
except Exception: except Exception:
logger.exception("Error closing sink: %s", sink.__class__.__name__) logger.exception("Error closing sink: %s", sink.__class__.__name__)
if sinks is None:
self._sinks = [] self._sinks = []
def _run_wrapper(self): def _run_wrapper(self, session_id: int):
global pipeline, source global pipeline, source
assert pipeline is not None and source is not None assert pipeline is not None and source is not None
try: try:
@@ -97,8 +102,11 @@ class PipelineManager:
logger.exception("Pipeline crashed") logger.exception("Pipeline crashed")
finally: finally:
with self._lock: with self._lock:
if self._session_id == session_id:
self.running = False self.running = False
# Only auto-close sinks when pipeline exits by itself self.stopping = False
self.starting = False
self.paused = False
self._close_sinks() self._close_sinks()
pipeline = None pipeline = None
source = None source = None
@@ -109,6 +117,9 @@ class PipelineManager:
if self.starting: if self.starting:
return False, "Pipeline is starting" return False, "Pipeline is starting"
if self.stopping:
return False, "Pipeline is stopping"
if self.running: if self.running:
return False, "Pipeline already running" return False, "Pipeline already running"
@@ -151,9 +162,12 @@ class PipelineManager:
self._sinks = sinks_obj self._sinks = sinks_obj
pipeline = pipeline_obj pipeline = pipeline_obj
live_frame = None live_frame = None
self._session_id += 1
session_id = self._session_id
self._thread = threading.Thread( self._thread = threading.Thread(
target=self._run_wrapper, target=self._run_wrapper,
args=(session_id,),
daemon=True, daemon=True,
name="pipeline", name="pipeline",
) )
@@ -189,8 +203,13 @@ class PipelineManager:
def stop(self) -> tuple[bool, str]: def stop(self) -> tuple[bool, str]:
global pipeline, source global pipeline, source
with self._lock: with self._lock:
if self.stopping:
return False, "Pipeline is stopping"
self.stopping = True
thread = self._thread thread = self._thread
source_obj = source source_obj = source
stop_session_id = self._session_id
if source_obj is not None: if source_obj is not None:
try: try:
@@ -202,8 +221,12 @@ class PipelineManager:
thread.join(timeout=3.0) thread.join(timeout=3.0)
with self._lock: with self._lock:
if self._session_id != stop_session_id:
self.stopping = False
return True, "Pipeline stop request completed"
self.starting = False self.starting = False
self.running = False self.running = False
self.stopping = False
self.paused = False self.paused = False
self._close_sinks() self._close_sinks()
pipeline = None pipeline = None
@@ -256,6 +279,7 @@ class PipelineManager:
return { return {
"running": self.running, "running": self.running,
"starting": self.starting, "starting": self.starting,
"stopping": self.stopping,
"paused": self.paused, "paused": self.paused,
"source": self._source_spec, "source": self._source_spec,
"last_error": self.last_error, "last_error": self.last_error,
@@ -287,6 +311,24 @@ class ClipJob:
"started_at": self.started_at, "started_at": self.started_at,
"finished_at": self.finished_at, "finished_at": self.finished_at,
"error": self.error, "error": self.error,
"outputs": clip_job_outputs(self.job_id),
}
def clip_job_outputs(job_id: str) -> dict[str, Any]:
base_local = Path("static") / "clip_jobs" / job_id
events_local = base_local / "events"
event_clips: list[str] = []
if events_local.exists():
for event_file in sorted(events_local.glob("event_*.mp4")):
event_clips.append(f"/static/clip_jobs/{job_id}/events/{event_file.name}")
return {
"processed_video": f"/static/clip_jobs/{job_id}/processed.mp4",
"alerts_log": f"/static/clip_jobs/{job_id}/alerts.jsonl",
"events_dir": f"/static/clip_jobs/{job_id}/events/",
"event_clips": event_clips,
} }