OTVision 0.6.7__py3-none-any.whl → 0.6.8__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.
- OTVision/application/config.py +18 -0
- OTVision/application/config_parser.py +17 -9
- OTVision/application/detect/update_detect_config_with_cli_args.py +16 -0
- OTVision/application/event/__init__.py +0 -0
- OTVision/application/event/new_video_start.py +9 -0
- OTVision/application/video/__init__.py +0 -0
- OTVision/application/video/generate_video.py +15 -0
- OTVision/config.py +2 -0
- OTVision/detect/builder.py +27 -8
- OTVision/detect/cli.py +44 -1
- OTVision/detect/detected_frame_producer.py +14 -0
- OTVision/detect/detected_frame_producer_factory.py +43 -0
- OTVision/detect/file_based_detect_builder.py +39 -3
- OTVision/detect/rtsp_based_detect_builder.py +35 -3
- OTVision/detect/rtsp_input_source.py +119 -34
- OTVision/detect/video_input_source.py +38 -6
- OTVision/detect/yolo.py +9 -1
- OTVision/domain/cli.py +10 -0
- OTVision/domain/input_source_detect.py +1 -3
- OTVision/domain/time.py +2 -2
- OTVision/domain/video_writer.py +30 -0
- OTVision/plugin/ffmpeg_video_writer.py +300 -0
- OTVision/plugin/generate_video.py +24 -0
- OTVision/version.py +1 -1
- {otvision-0.6.7.dist-info → otvision-0.6.8.dist-info}/METADATA +6 -5
- {otvision-0.6.7.dist-info → otvision-0.6.8.dist-info}/RECORD +28 -20
- OTVision/application/detect/detected_frame_producer.py +0 -24
- {otvision-0.6.7.dist-info → otvision-0.6.8.dist-info}/WHEEL +0 -0
- {otvision-0.6.7.dist-info → otvision-0.6.8.dist-info}/licenses/LICENSE +0 -0
OTVision/application/config.py
CHANGED
|
@@ -2,6 +2,12 @@ from dataclasses import dataclass, field
|
|
|
2
2
|
from datetime import datetime, timedelta
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
+
from OTVision.plugin.ffmpeg_video_writer import (
|
|
6
|
+
ConstantRateFactor,
|
|
7
|
+
EncodingSpeed,
|
|
8
|
+
VideoCodec,
|
|
9
|
+
)
|
|
10
|
+
|
|
5
11
|
AVAILABLE_WEIGHTS = "AVAILABLEWEIGHTS"
|
|
6
12
|
CALIBRATIONS = "CALIBRATIONS"
|
|
7
13
|
COL_WIDTH = "COLWIDTH"
|
|
@@ -58,6 +64,10 @@ LOG_DIR = "LOG_DIR"
|
|
|
58
64
|
START_TIME = "START_TIME"
|
|
59
65
|
DETECT_END = "DETECT_END"
|
|
60
66
|
DETECT_START = "DETECT_START"
|
|
67
|
+
WRITE_VIDEO = "WRITE_VIDEO"
|
|
68
|
+
VIDEO_CODEC = "VIDEO_CODEC"
|
|
69
|
+
ENCODING_SPEED = "ENCODING_SPEED"
|
|
70
|
+
CRF = "CRF"
|
|
61
71
|
DATETIME_FORMAT = "%Y-%m-%d_%H-%M-%S"
|
|
62
72
|
DEFAULT_EXPECTED_DURATION: timedelta = timedelta(minutes=15)
|
|
63
73
|
"""Default length of a video is 15 minutes."""
|
|
@@ -289,6 +299,10 @@ class DetectConfig:
|
|
|
289
299
|
start_time: datetime | None = None
|
|
290
300
|
detect_start: int | None = None
|
|
291
301
|
detect_end: int | None = None
|
|
302
|
+
write_video: bool = False
|
|
303
|
+
video_codec: VideoCodec = VideoCodec.H264_SOFTWARE
|
|
304
|
+
encoding_speed: EncodingSpeed = EncodingSpeed.FAST
|
|
305
|
+
crf: ConstantRateFactor = ConstantRateFactor.DEFAULT
|
|
292
306
|
|
|
293
307
|
def to_dict(self) -> dict:
|
|
294
308
|
expected_duration = (
|
|
@@ -306,6 +320,10 @@ class DetectConfig:
|
|
|
306
320
|
START_TIME: self.start_time,
|
|
307
321
|
DETECT_START: self.detect_start,
|
|
308
322
|
DETECT_END: self.detect_end,
|
|
323
|
+
WRITE_VIDEO: self.write_video,
|
|
324
|
+
VIDEO_CODEC: self.video_codec.value,
|
|
325
|
+
ENCODING_SPEED: self.encoding_speed.value,
|
|
326
|
+
CRF: self.crf.name,
|
|
309
327
|
}
|
|
310
328
|
|
|
311
329
|
|
|
@@ -5,12 +5,14 @@ from OTVision.application.config import (
|
|
|
5
5
|
COL_WIDTH,
|
|
6
6
|
CONF,
|
|
7
7
|
CONVERT,
|
|
8
|
+
CRF,
|
|
8
9
|
DATETIME_FORMAT,
|
|
9
10
|
DEFAULT_FILETYPE,
|
|
10
11
|
DELETE_INPUT,
|
|
11
12
|
DETECT,
|
|
12
13
|
DETECT_END,
|
|
13
14
|
DETECT_START,
|
|
15
|
+
ENCODING_SPEED,
|
|
14
16
|
EXPECTED_DURATION,
|
|
15
17
|
FLUSH_BUFFER_SIZE,
|
|
16
18
|
FONT,
|
|
@@ -51,8 +53,10 @@ from OTVision.application.config import (
|
|
|
51
53
|
TRANSFORM,
|
|
52
54
|
UNDISTORT,
|
|
53
55
|
VID,
|
|
56
|
+
VIDEO_CODEC,
|
|
54
57
|
WEIGHTS,
|
|
55
58
|
WINDOW,
|
|
59
|
+
WRITE_VIDEO,
|
|
56
60
|
YOLO,
|
|
57
61
|
Config,
|
|
58
62
|
ConvertConfig,
|
|
@@ -181,15 +185,19 @@ class ConfigParser:
|
|
|
181
185
|
|
|
182
186
|
start_time = self._parse_start_time(data)
|
|
183
187
|
return DetectConfig(
|
|
184
|
-
sources,
|
|
185
|
-
data.get(RUN_CHAINED, DetectConfig.run_chained),
|
|
186
|
-
yolo_config,
|
|
187
|
-
expected_duration,
|
|
188
|
-
data.get(OVERWRITE, DetectConfig.overwrite),
|
|
189
|
-
data.get(HALF_PRECISION, DetectConfig.half_precision),
|
|
190
|
-
start_time,
|
|
191
|
-
data.get(DETECT_START, DetectConfig.detect_start),
|
|
192
|
-
data.get(DETECT_END, DetectConfig.detect_end),
|
|
188
|
+
paths=sources,
|
|
189
|
+
run_chained=data.get(RUN_CHAINED, DetectConfig.run_chained),
|
|
190
|
+
yolo_config=yolo_config,
|
|
191
|
+
expected_duration=expected_duration,
|
|
192
|
+
overwrite=data.get(OVERWRITE, DetectConfig.overwrite),
|
|
193
|
+
half_precision=data.get(HALF_PRECISION, DetectConfig.half_precision),
|
|
194
|
+
start_time=start_time,
|
|
195
|
+
detect_start=data.get(DETECT_START, DetectConfig.detect_start),
|
|
196
|
+
detect_end=data.get(DETECT_END, DetectConfig.detect_end),
|
|
197
|
+
write_video=data.get(WRITE_VIDEO, DetectConfig.write_video),
|
|
198
|
+
video_codec=data.get(VIDEO_CODEC, DetectConfig.video_codec),
|
|
199
|
+
encoding_speed=data.get(ENCODING_SPEED, DetectConfig.encoding_speed),
|
|
200
|
+
crf=data.get(CRF, DetectConfig.crf),
|
|
193
201
|
)
|
|
194
202
|
|
|
195
203
|
def parse_yolo_config(self, data: dict) -> YoloConfig:
|
|
@@ -84,6 +84,22 @@ class UpdateDetectConfigWithCliArgs:
|
|
|
84
84
|
if cli_args.detect_end is not None
|
|
85
85
|
else detect_config.detect_end
|
|
86
86
|
),
|
|
87
|
+
write_video=(
|
|
88
|
+
cli_args.write_video
|
|
89
|
+
if cli_args.write_video is not None
|
|
90
|
+
else detect_config.write_video
|
|
91
|
+
),
|
|
92
|
+
video_codec=(
|
|
93
|
+
cli_args.video_codec
|
|
94
|
+
if cli_args.video_codec is not None
|
|
95
|
+
else detect_config.video_codec
|
|
96
|
+
),
|
|
97
|
+
encoding_speed=(
|
|
98
|
+
cli_args.encoding_speed
|
|
99
|
+
if cli_args.encoding_speed is not None
|
|
100
|
+
else detect_config.encoding_speed
|
|
101
|
+
),
|
|
102
|
+
crf=cli_args.crf if cli_args.crf is not None else detect_config.crf,
|
|
87
103
|
)
|
|
88
104
|
|
|
89
105
|
def _update_log_config(self, config: Config, cli_args: DetectCliArgs) -> _LogConfig:
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from OTVision.abstraction.pipes_and_filter import Filter
|
|
2
|
+
from OTVision.domain.frame import Frame
|
|
3
|
+
from OTVision.domain.input_source_detect import InputSourceDetect
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GenerateVideo:
|
|
7
|
+
def __init__(
|
|
8
|
+
self, input_source: InputSourceDetect, video_writer: Filter[Frame, Frame]
|
|
9
|
+
) -> None:
|
|
10
|
+
self._input_source = input_source
|
|
11
|
+
self._video_writer = video_writer
|
|
12
|
+
|
|
13
|
+
def generate(self) -> None:
|
|
14
|
+
for frame in self._video_writer.filter(self._input_source.produce()):
|
|
15
|
+
pass
|
OTVision/config.py
CHANGED
|
@@ -77,6 +77,7 @@ from OTVision.application.config import (
|
|
|
77
77
|
VIDEOS,
|
|
78
78
|
WEIGHTS,
|
|
79
79
|
WINDOW,
|
|
80
|
+
WRITE_VIDEO,
|
|
80
81
|
YOLO,
|
|
81
82
|
Config,
|
|
82
83
|
)
|
|
@@ -176,6 +177,7 @@ CONFIG[DETECT][OVERWRITE] = True
|
|
|
176
177
|
CONFIG[DETECT][HALF_PRECISION] = False
|
|
177
178
|
CONFIG[DETECT][DETECT_START] = None
|
|
178
179
|
CONFIG[DETECT][DETECT_END] = None
|
|
180
|
+
CONFIG[DETECT][WRITE_VIDEO] = False
|
|
179
181
|
|
|
180
182
|
# TRACK
|
|
181
183
|
CONFIG[TRACK] = {}
|
OTVision/detect/builder.py
CHANGED
|
@@ -4,7 +4,7 @@ from functools import cached_property
|
|
|
4
4
|
|
|
5
5
|
from OTVision.abstraction.observer import Subject
|
|
6
6
|
from OTVision.application.buffer import Buffer
|
|
7
|
-
from OTVision.application.config import Config
|
|
7
|
+
from OTVision.application.config import Config, DetectConfig
|
|
8
8
|
from OTVision.application.config_parser import ConfigParser
|
|
9
9
|
from OTVision.application.configure_logger import ConfigureLogger
|
|
10
10
|
from OTVision.application.detect.current_object_detector import CurrentObjectDetector
|
|
@@ -12,9 +12,6 @@ from OTVision.application.detect.current_object_detector_metadata import (
|
|
|
12
12
|
CurrentObjectDetectorMetadata,
|
|
13
13
|
)
|
|
14
14
|
from OTVision.application.detect.detected_frame_factory import DetectedFrameFactory
|
|
15
|
-
from OTVision.application.detect.detected_frame_producer import (
|
|
16
|
-
SimpleDetectedFrameProducer,
|
|
17
|
-
)
|
|
18
15
|
from OTVision.application.detect.detection_file_save_path_provider import (
|
|
19
16
|
DetectionFileSavePathProvider,
|
|
20
17
|
)
|
|
@@ -34,6 +31,10 @@ from OTVision.detect.detected_frame_buffer import (
|
|
|
34
31
|
DetectedFrameBufferEvent,
|
|
35
32
|
FlushEvent,
|
|
36
33
|
)
|
|
34
|
+
from OTVision.detect.detected_frame_producer import (
|
|
35
|
+
DetectedFrameProducerFactory,
|
|
36
|
+
SimpleDetectedFrameProducer,
|
|
37
|
+
)
|
|
37
38
|
from OTVision.detect.otdet import OtdetBuilder
|
|
38
39
|
from OTVision.detect.otdet_file_writer import OtdetFileWriter
|
|
39
40
|
from OTVision.detect.plugin_av.rotate_frame import AvVideoFrameRotator
|
|
@@ -47,6 +48,7 @@ from OTVision.domain.frame import DetectedFrame
|
|
|
47
48
|
from OTVision.domain.input_source_detect import InputSourceDetect
|
|
48
49
|
from OTVision.domain.object_detection import ObjectDetectorFactory
|
|
49
50
|
from OTVision.domain.serialization import Deserializer
|
|
51
|
+
from OTVision.domain.video_writer import VideoWriter
|
|
50
52
|
from OTVision.plugin.yaml_serialization import YamlDeserializer
|
|
51
53
|
|
|
52
54
|
|
|
@@ -152,9 +154,17 @@ class DetectBuilder(ABC):
|
|
|
152
154
|
@cached_property
|
|
153
155
|
def detected_frame_producer(self) -> DetectedFrameProducer:
|
|
154
156
|
return SimpleDetectedFrameProducer(
|
|
157
|
+
producer_factory=self.detected_frame_producer_factory,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
@cached_property
|
|
161
|
+
def detected_frame_producer_factory(self) -> DetectedFrameProducerFactory:
|
|
162
|
+
return DetectedFrameProducerFactory(
|
|
155
163
|
input_source=self.input_source,
|
|
164
|
+
video_writer_filter=self.video_file_writer,
|
|
156
165
|
detection_filter=self.current_object_detector,
|
|
157
166
|
detected_frame_buffer=self.detected_frame_buffer,
|
|
167
|
+
get_current_config=self.get_current_config,
|
|
158
168
|
)
|
|
159
169
|
|
|
160
170
|
@cached_property
|
|
@@ -165,6 +175,10 @@ class DetectBuilder(ABC):
|
|
|
165
175
|
def yaml_deserializer(self) -> Deserializer:
|
|
166
176
|
return YamlDeserializer()
|
|
167
177
|
|
|
178
|
+
@property
|
|
179
|
+
def detect_config(self) -> DetectConfig:
|
|
180
|
+
return self.current_config.get().detect
|
|
181
|
+
|
|
168
182
|
def __init__(self, argv: list[str] | None = None) -> None:
|
|
169
183
|
self.argv = argv
|
|
170
184
|
|
|
@@ -173,6 +187,15 @@ class DetectBuilder(ABC):
|
|
|
173
187
|
def input_source(self) -> InputSourceDetect:
|
|
174
188
|
raise NotImplementedError
|
|
175
189
|
|
|
190
|
+
@property
|
|
191
|
+
@abstractmethod
|
|
192
|
+
def video_file_writer(self) -> VideoWriter:
|
|
193
|
+
raise NotImplementedError
|
|
194
|
+
|
|
195
|
+
@abstractmethod
|
|
196
|
+
def register_observers(self) -> None:
|
|
197
|
+
raise NotImplementedError
|
|
198
|
+
|
|
176
199
|
def build(self) -> OTVisionVideoDetect:
|
|
177
200
|
self.register_observers()
|
|
178
201
|
self._preload_object_detection_model()
|
|
@@ -181,7 +204,3 @@ class DetectBuilder(ABC):
|
|
|
181
204
|
def _preload_object_detection_model(self) -> None:
|
|
182
205
|
model = self.current_object_detector.get()
|
|
183
206
|
model.preload()
|
|
184
|
-
|
|
185
|
-
def register_observers(self) -> None:
|
|
186
|
-
self.input_source.register(self.detected_frame_buffer.on_flush)
|
|
187
|
-
self.detected_frame_buffer.register(self.otdet_file_writer.write)
|
OTVision/detect/cli.py
CHANGED
|
@@ -6,6 +6,11 @@ from OTVision.application.config import DATETIME_FORMAT
|
|
|
6
6
|
from OTVision.domain.cli import CliParseError, DetectCliArgs, DetectCliParser
|
|
7
7
|
from OTVision.helpers.files import check_if_all_paths_exist
|
|
8
8
|
from OTVision.helpers.log import DEFAULT_LOG_FILE, VALID_LOG_LEVELS
|
|
9
|
+
from OTVision.plugin.ffmpeg_video_writer import (
|
|
10
|
+
ConstantRateFactor,
|
|
11
|
+
EncodingSpeed,
|
|
12
|
+
VideoCodec,
|
|
13
|
+
)
|
|
9
14
|
|
|
10
15
|
|
|
11
16
|
class ArgparseDetectCliParser(DetectCliParser):
|
|
@@ -128,6 +133,34 @@ class ArgparseDetectCliParser(DetectCliParser):
|
|
|
128
133
|
help="Specify end of detection in seconds.",
|
|
129
134
|
required=False,
|
|
130
135
|
)
|
|
136
|
+
self._parser.add_argument(
|
|
137
|
+
"--write-video",
|
|
138
|
+
default=None,
|
|
139
|
+
action="store_true",
|
|
140
|
+
help="Write video to output folder. Not supported on Windows.",
|
|
141
|
+
required=False,
|
|
142
|
+
)
|
|
143
|
+
self._parser.add_argument(
|
|
144
|
+
"--video-codec",
|
|
145
|
+
default=None,
|
|
146
|
+
choices=VideoCodec.as_list(),
|
|
147
|
+
help="Video codec for video writer. Default is 'libx264'",
|
|
148
|
+
required=False,
|
|
149
|
+
)
|
|
150
|
+
self._parser.add_argument(
|
|
151
|
+
"--encoding-speed",
|
|
152
|
+
default=None,
|
|
153
|
+
choices=EncodingSpeed.as_list(),
|
|
154
|
+
help="Encoding speed for video writer. Default is 'fast'",
|
|
155
|
+
required=False,
|
|
156
|
+
)
|
|
157
|
+
self._parser.add_argument(
|
|
158
|
+
"--crf",
|
|
159
|
+
default=None,
|
|
160
|
+
choices=ConstantRateFactor.as_list(),
|
|
161
|
+
help="Constant rate factor for video writer. Default is 'DEFAULT'",
|
|
162
|
+
required=False,
|
|
163
|
+
)
|
|
131
164
|
|
|
132
165
|
def parse(self) -> DetectCliArgs:
|
|
133
166
|
args = self._parser.parse_args(self._argv)
|
|
@@ -151,11 +184,21 @@ class ArgparseDetectCliParser(DetectCliParser):
|
|
|
151
184
|
detect_start=(
|
|
152
185
|
int(args.detect_start) if args.detect_start is not None else None
|
|
153
186
|
),
|
|
154
|
-
detect_end=int(args.detect_end) if args.detect_end is not None else None,
|
|
187
|
+
detect_end=(int(args.detect_end) if args.detect_end is not None else None),
|
|
155
188
|
logfile=Path(args.logfile),
|
|
156
189
|
log_level_console=args.log_level_console,
|
|
157
190
|
log_level_file=args.log_level_file,
|
|
158
191
|
logfile_overwrite=args.logfile_overwrite,
|
|
192
|
+
write_video=args.write_video,
|
|
193
|
+
video_codec=(
|
|
194
|
+
VideoCodec(args.video_codec) if args.video_codec is not None else None
|
|
195
|
+
),
|
|
196
|
+
encoding_speed=(
|
|
197
|
+
EncodingSpeed(args.encoding_speed)
|
|
198
|
+
if args.encoding_speed is not None
|
|
199
|
+
else None
|
|
200
|
+
),
|
|
201
|
+
crf=ConstantRateFactor[args.crf] if args.crf is not None else None,
|
|
159
202
|
)
|
|
160
203
|
|
|
161
204
|
def _parse_start_time(self, start_time: str | None) -> datetime | None:
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import Generator
|
|
2
|
+
|
|
3
|
+
from OTVision.detect.detected_frame_producer_factory import DetectedFrameProducerFactory
|
|
4
|
+
from OTVision.domain.detect_producer_consumer import DetectedFrameProducer
|
|
5
|
+
from OTVision.domain.frame import DetectedFrame
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SimpleDetectedFrameProducer(DetectedFrameProducer):
|
|
9
|
+
|
|
10
|
+
def __init__(self, producer_factory: DetectedFrameProducerFactory) -> None:
|
|
11
|
+
self._producer_factory = producer_factory
|
|
12
|
+
|
|
13
|
+
def produce(self) -> Generator[DetectedFrame, None, None]:
|
|
14
|
+
return self._producer_factory.create()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from typing import Generator
|
|
2
|
+
|
|
3
|
+
from OTVision.abstraction.pipes_and_filter import Filter
|
|
4
|
+
from OTVision.application.get_current_config import GetCurrentConfig
|
|
5
|
+
from OTVision.domain.frame import DetectedFrame, Frame
|
|
6
|
+
from OTVision.domain.input_source_detect import InputSourceDetect
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DetectedFrameProducerFactory:
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
input_source: InputSourceDetect,
|
|
13
|
+
video_writer_filter: Filter[Frame, Frame],
|
|
14
|
+
detection_filter: Filter[Frame, DetectedFrame],
|
|
15
|
+
detected_frame_buffer: Filter[DetectedFrame, DetectedFrame],
|
|
16
|
+
get_current_config: GetCurrentConfig,
|
|
17
|
+
) -> None:
|
|
18
|
+
self._input_source = input_source
|
|
19
|
+
self._video_writer_filter = video_writer_filter
|
|
20
|
+
self._detection_filter = detection_filter
|
|
21
|
+
self._detected_frame_buffer = detected_frame_buffer
|
|
22
|
+
self._get_current_config = get_current_config
|
|
23
|
+
|
|
24
|
+
def create(self) -> Generator[DetectedFrame, None, None]:
|
|
25
|
+
if self._get_current_config.get().detect.write_video:
|
|
26
|
+
return self.__create_with_video_writer()
|
|
27
|
+
return self.__create_without_video_writer()
|
|
28
|
+
|
|
29
|
+
def __create_without_video_writer(
|
|
30
|
+
self,
|
|
31
|
+
) -> Generator[DetectedFrame, None, None]:
|
|
32
|
+
return self._detected_frame_buffer.filter(
|
|
33
|
+
self._detection_filter.filter(self._input_source.produce())
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def __create_with_video_writer(
|
|
37
|
+
self,
|
|
38
|
+
) -> Generator[DetectedFrame, None, None]:
|
|
39
|
+
return self._detected_frame_buffer.filter(
|
|
40
|
+
self._detection_filter.filter(
|
|
41
|
+
self._video_writer_filter.filter(self._input_source.produce())
|
|
42
|
+
)
|
|
43
|
+
)
|
|
@@ -1,19 +1,55 @@
|
|
|
1
1
|
from functools import cached_property
|
|
2
2
|
|
|
3
3
|
from OTVision.abstraction.observer import Subject
|
|
4
|
+
from OTVision.application.event.new_video_start import NewVideoStartEvent
|
|
4
5
|
from OTVision.detect.builder import DetectBuilder
|
|
5
6
|
from OTVision.detect.detected_frame_buffer import FlushEvent
|
|
6
7
|
from OTVision.detect.video_input_source import VideoSource
|
|
7
|
-
from OTVision.domain.
|
|
8
|
+
from OTVision.domain.video_writer import VideoWriter
|
|
9
|
+
from OTVision.plugin.ffmpeg_video_writer import (
|
|
10
|
+
FfmpegVideoWriter,
|
|
11
|
+
PixelFormat,
|
|
12
|
+
VideoFormat,
|
|
13
|
+
append_save_suffix_to_save_location,
|
|
14
|
+
)
|
|
8
15
|
|
|
9
16
|
|
|
10
17
|
class FileBasedDetectBuilder(DetectBuilder):
|
|
18
|
+
|
|
11
19
|
@cached_property
|
|
12
|
-
def input_source(self) ->
|
|
20
|
+
def input_source(self) -> VideoSource:
|
|
13
21
|
return VideoSource(
|
|
14
|
-
|
|
22
|
+
subject_flush=Subject[FlushEvent](),
|
|
23
|
+
subject_new_video_start=Subject[NewVideoStartEvent](),
|
|
15
24
|
get_current_config=self.get_current_config,
|
|
16
25
|
frame_rotator=self.frame_rotator,
|
|
17
26
|
timestamper_factory=self.timestamper_factory,
|
|
18
27
|
save_path_provider=self.detection_file_save_path_provider,
|
|
19
28
|
)
|
|
29
|
+
|
|
30
|
+
@cached_property
|
|
31
|
+
def video_file_writer(self) -> VideoWriter:
|
|
32
|
+
# Using save_location_strategy=keep_original_save_location is not supported for
|
|
33
|
+
# file-based detection. Otherwise, we would be overwriting the input source that
|
|
34
|
+
# we are reading from.
|
|
35
|
+
return FfmpegVideoWriter(
|
|
36
|
+
save_location_strategy=append_save_suffix_to_save_location,
|
|
37
|
+
encoding_speed=self.detect_config.encoding_speed,
|
|
38
|
+
input_format=VideoFormat.RAW,
|
|
39
|
+
output_format=VideoFormat.MP4,
|
|
40
|
+
input_pixel_format=PixelFormat.RGB24,
|
|
41
|
+
output_pixel_format=PixelFormat.YUV420P,
|
|
42
|
+
output_video_codec=self.detect_config.video_codec,
|
|
43
|
+
constant_rate_factor=self.detect_config.crf,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def register_observers(self) -> None:
|
|
47
|
+
if self.detect_config.write_video:
|
|
48
|
+
self.input_source.subject_new_video_start.register(
|
|
49
|
+
self.video_file_writer.notify_on_new_video_start
|
|
50
|
+
)
|
|
51
|
+
self.input_source.subject_flush.register(
|
|
52
|
+
self.video_file_writer.notify_on_flush_event
|
|
53
|
+
)
|
|
54
|
+
self.input_source.subject_flush.register(self.detected_frame_buffer.on_flush)
|
|
55
|
+
self.detected_frame_buffer.register(self.otdet_file_writer.write)
|
|
@@ -2,11 +2,18 @@ from functools import cached_property
|
|
|
2
2
|
|
|
3
3
|
from OTVision.abstraction.observer import Subject
|
|
4
4
|
from OTVision.application.config import StreamConfig
|
|
5
|
+
from OTVision.application.event.new_video_start import NewVideoStartEvent
|
|
5
6
|
from OTVision.detect.builder import DetectBuilder
|
|
6
7
|
from OTVision.detect.detected_frame_buffer import FlushEvent
|
|
7
8
|
from OTVision.detect.rtsp_input_source import Counter, RtspInputSource
|
|
8
|
-
from OTVision.domain.input_source_detect import InputSourceDetect
|
|
9
9
|
from OTVision.domain.time import CurrentDatetimeProvider, DatetimeProvider
|
|
10
|
+
from OTVision.domain.video_writer import VideoWriter
|
|
11
|
+
from OTVision.plugin.ffmpeg_video_writer import (
|
|
12
|
+
FfmpegVideoWriter,
|
|
13
|
+
PixelFormat,
|
|
14
|
+
VideoFormat,
|
|
15
|
+
keep_original_save_location,
|
|
16
|
+
)
|
|
10
17
|
|
|
11
18
|
FLUSH_BUFFER_SIZE = 18000
|
|
12
19
|
FLUSH_BUFFER_SIZE = 1200
|
|
@@ -24,9 +31,10 @@ class RtspBasedDetectBuilder(DetectBuilder):
|
|
|
24
31
|
return config.stream
|
|
25
32
|
|
|
26
33
|
@cached_property
|
|
27
|
-
def input_source(self) ->
|
|
34
|
+
def input_source(self) -> RtspInputSource:
|
|
28
35
|
return RtspInputSource(
|
|
29
|
-
|
|
36
|
+
subject_flush=Subject[FlushEvent](),
|
|
37
|
+
subject_new_video_start=Subject[NewVideoStartEvent](),
|
|
30
38
|
datetime_provider=self.datetime_provider,
|
|
31
39
|
frame_counter=Counter(),
|
|
32
40
|
get_current_config=self.get_current_config,
|
|
@@ -35,3 +43,27 @@ class RtspBasedDetectBuilder(DetectBuilder):
|
|
|
35
43
|
@cached_property
|
|
36
44
|
def datetime_provider(self) -> DatetimeProvider:
|
|
37
45
|
return CurrentDatetimeProvider()
|
|
46
|
+
|
|
47
|
+
@cached_property
|
|
48
|
+
def video_file_writer(self) -> VideoWriter:
|
|
49
|
+
return FfmpegVideoWriter(
|
|
50
|
+
save_location_strategy=keep_original_save_location,
|
|
51
|
+
encoding_speed=self.detect_config.encoding_speed,
|
|
52
|
+
input_format=VideoFormat.RAW,
|
|
53
|
+
output_format=VideoFormat.MP4,
|
|
54
|
+
input_pixel_format=PixelFormat.RGB24,
|
|
55
|
+
output_pixel_format=PixelFormat.YUV420P,
|
|
56
|
+
output_video_codec=self.detect_config.video_codec,
|
|
57
|
+
constant_rate_factor=self.detect_config.crf,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def register_observers(self) -> None:
|
|
61
|
+
if self.detect_config.write_video:
|
|
62
|
+
self.input_source.subject_new_video_start.register(
|
|
63
|
+
self.video_file_writer.notify_on_new_video_start
|
|
64
|
+
)
|
|
65
|
+
self.input_source.subject_flush.register(
|
|
66
|
+
self.video_file_writer.notify_on_flush_event
|
|
67
|
+
)
|
|
68
|
+
self.input_source.subject_flush.register(self.detected_frame_buffer.on_flush)
|
|
69
|
+
self.detected_frame_buffer.register(self.otdet_file_writer.write)
|