PlanOpticon

Phase 1: Add pydantic data models and standardized output structure Stories 1.1-1.4: Pydantic models for all output types (DiagramResult, ScreenCapture, TranscriptSegment, ActionItem, KeyPoint, Entity, Relationship, KnowledgeGraphData, VideoManifest, BatchManifest), standardized video/batch output directory layout, and manifest I/O helpers.

leo 2026-02-14 22:11 trunk
Commit b2df90efa9defc9d4fd85c44845818c0329dc6c7a746572118f742c1345b3c16
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -0,0 +1,9 @@
1
+"""Tests forimport json
2
+
3
+import pytestor pydantic data models."""
4
+
5
+from video_processor.models import (
6
+ ActionItem,
7
+ BatchManifest,
8
+ BatchVideoEntry,
9
+ Di7 Di"""Tests f confidence=0.45
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -0,0 +1,9 @@
1 """Tests forimport json
2
3 import pytestor pydantic data models."""
4
5 from video_processor.models import (
6 ActionItem,
7 BatchManifest,
8 BatchVideoEntry,
9 Di7 Di"""Tests f confidence=0.45
--- a/tests/test_output_structure.py
+++ b/tests/test_output_structure.py
@@ -0,0 +1,144 @@
1
+"""Tests for output structure and manifest I/O."""
2
+
3
+import json
4
+import tempfile
5
+from pathlib"""Tests for output structure and manifest I/O."""
6
+
7
+import json
8
+
9
+from video_processor.models import (
10
+ ActionItem,
11
+ BatchManifest,
12
+ BatchVideoEntry,
13
+ DiagramResult,
14
+ KeyPoint,
15
+ ProcessingStats,
16
+ VideoManifest,
17
+ VideoMetadata,
18
+)
19
+from video_processor.output_structure import (
20
+ create_batch_output_dirs,
21
+ create_video_output_dirs,
22
+ read_batch_manifest,
23
+ read_video_manifest,
24
+ write_batch_manifest,
25
+ write_video_manifest,
26
+)
27
+
28
+
29
+class TestCreateVideoOutputDirs:
30
+ def test_creates_all_directories(self, tmp_path):
31
+ dirs = create_video_output_dirs(tmp_path / "output", "test_video")
32
+ assert dirs["root"].exists()
33
+ assert dirs["transcript"].exists()
34
+ assert dirs["frames"].exists()
35
+ assert dirs["diagrams"].exists()
36
+ assert dirs["captures"].exists()
37
+ assert dirs["results"].exists()
38
+ assert dirs["cache"].exists()
39
+
40
+ def test_expected_layout(self, tmp_path):
41
+ dirs = create_video_output_dirs(tmp_path / "output", "my_video")
42
+ base = tmp_path / "output"
43
+ assert dirs["transcript"] == base / "transcript"
44
+ assert dirs["frames"] == base / "frames"
45
+ assert dirs["diagrams"] == base / "diagrams"
46
+ assert dirs["captures"] == base / "captures"
47
+ assert dirs["results"] == base / "results"
48
+ assert dirs["cache"] == base / "cache"
49
+
50
+ def test_idempotent(self, tmp_path):
51
+ out = tmp_path / "output"
52
+ dirs1 = create_video_output_dirs(out, "v")
53
+ dirs2 = create_video_output_dirs(out, "v")
54
+ assert dirs1 == dirs2
55
+
56
+
57
+class TestCreateBatchOutputDirs:
58
+ def test_creates_directories(self, tmp_path):
59
+ dirs = create_batch_output_dirs(tmp_path / "batch", "my_batch")
60
+ assert dirs["root"].exists()
61
+ assert dirs["videos"].exists()
62
+
63
+ def test_expected_layout(self, tmp_path):
64
+ dirs = create_batch_output_dirs(tmp_path / "batch", "b")
65
+ base = tmp_path / "batch"
66
+ assert dirs["videos"] == base / "videos"
67
+
68
+
69
+class TestVideoManifestIO:
70
+ def _sample_manifest(self) -> VideoManifest:
71
+ return VideoManifest(
72
+ video=VideoMetadata(title="Test", duration_seconds=120.0),
73
+ stats=ProcessingStats(frames_extracted=10, diagrams_detected=2),
74
+ transcript_json="transcript/transcript.json",
75
+ analysis_md="results/analysis.md",
76
+ key_points=[KeyPoint(point="Point 1")],
77
+ action_items=[ActionItem(action="Do thing")],
78
+ diagrams=[DiagramResult(frame_index=0, confidence=0.9)],
79
+ )
80
+
81
+ def test_write_and_read(self, tmp_path):
82
+ manifest = self._sample_manifest()
83
+ write_video_manifest(manifest, tmp_path)
84
+
85
+ restored = read_video_manifest(tmp_path)
86
+ assert restored.video.title == "Test"
87
+ assert restored.stats.frames_extracted == 10
88
+ assert len(restored.key_points) == 1
89
+ assert len(restored.diagrams) == 1
90
+
91
+ def test_manifest_file_is_valid_json(self, tmp_path):
92
+ manifest = self._sample_manifest()
93
+ write_video_manifest(manifest, tmp_path)
94
+
95
+ path = tmp_path / "manifest.json"
96
+ data = json.loads(path.read_text())
97
+ assert data["version"] == "1.0"
98
+ assert data["video"]["title"] == "Test"
99
+
100
+ def test_creates_parent_dirs(self, tmp_path):
101
+ manifest = self._sample_manifest()
102
+ nested = tmp_path / "a" / "b" / "c"
103
+ write_video_manifest(manifest, nested)
104
+ assert (nested / "manifest.json").exists()
105
+
106
+
107
+class TestBatchManifestIO:
108
+ def _sample_batch(self) -> BatchManifest:
109
+ return BatchManifest(
110
+ title="Test Batch",
111
+ total_videos=2,
112
+ completed_videos=2,
113
+ videos=[
114
+ BatchVideoEntry(
115
+ video_name="v1",
116
+ manifest_path="videos/v1/manifest.json",
117
+ status="completed",
118
+ ),
119
+ BatchVideoEntry(
120
+ video_name="v2",
121
+ manifest_path="videos/v2/manifest.json",
122
+ status="completed",
123
+ ),
124
+ ],
125
+ batch_summary_md="batch_summary.md",
126
+ )
127
+
128
+ def test_write_and_read(self, tmp_path):
129
+ manifest = self._sample_batch()
130
+ write_batch_manifest(manifest, tmp_path)
131
+
132
+ restored = read_batch_manifest(tmp_path)
133
+ assert restored.title == "Test Batch"
134
+ assert restored.total_videos == 2
135
+ assert len(restored.videos) == 2
136
+ assert restored.videos[0].video_name == "v1"
137
+
138
+ def test_manifest_file_is_valid_json(self, tmp_path):
139
+ manifest = self._sample_batch()
140
+ write_batch_manifest(manifest, tmp_path)
141
+
142
+ path = tmp_path / "manifest.json"
143
+ data = json.loads(path.read_text())
144
+ a
--- a/tests/test_output_structure.py
+++ b/tests/test_output_structure.py
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/tests/test_output_structure.py
+++ b/tests/test_output_structure.py
@@ -0,0 +1,144 @@
1 """Tests for output structure and manifest I/O."""
2
3 import json
4 import tempfile
5 from pathlib"""Tests for output structure and manifest I/O."""
6
7 import json
8
9 from video_processor.models import (
10 ActionItem,
11 BatchManifest,
12 BatchVideoEntry,
13 DiagramResult,
14 KeyPoint,
15 ProcessingStats,
16 VideoManifest,
17 VideoMetadata,
18 )
19 from video_processor.output_structure import (
20 create_batch_output_dirs,
21 create_video_output_dirs,
22 read_batch_manifest,
23 read_video_manifest,
24 write_batch_manifest,
25 write_video_manifest,
26 )
27
28
29 class TestCreateVideoOutputDirs:
30 def test_creates_all_directories(self, tmp_path):
31 dirs = create_video_output_dirs(tmp_path / "output", "test_video")
32 assert dirs["root"].exists()
33 assert dirs["transcript"].exists()
34 assert dirs["frames"].exists()
35 assert dirs["diagrams"].exists()
36 assert dirs["captures"].exists()
37 assert dirs["results"].exists()
38 assert dirs["cache"].exists()
39
40 def test_expected_layout(self, tmp_path):
41 dirs = create_video_output_dirs(tmp_path / "output", "my_video")
42 base = tmp_path / "output"
43 assert dirs["transcript"] == base / "transcript"
44 assert dirs["frames"] == base / "frames"
45 assert dirs["diagrams"] == base / "diagrams"
46 assert dirs["captures"] == base / "captures"
47 assert dirs["results"] == base / "results"
48 assert dirs["cache"] == base / "cache"
49
50 def test_idempotent(self, tmp_path):
51 out = tmp_path / "output"
52 dirs1 = create_video_output_dirs(out, "v")
53 dirs2 = create_video_output_dirs(out, "v")
54 assert dirs1 == dirs2
55
56
57 class TestCreateBatchOutputDirs:
58 def test_creates_directories(self, tmp_path):
59 dirs = create_batch_output_dirs(tmp_path / "batch", "my_batch")
60 assert dirs["root"].exists()
61 assert dirs["videos"].exists()
62
63 def test_expected_layout(self, tmp_path):
64 dirs = create_batch_output_dirs(tmp_path / "batch", "b")
65 base = tmp_path / "batch"
66 assert dirs["videos"] == base / "videos"
67
68
69 class TestVideoManifestIO:
70 def _sample_manifest(self) -> VideoManifest:
71 return VideoManifest(
72 video=VideoMetadata(title="Test", duration_seconds=120.0),
73 stats=ProcessingStats(frames_extracted=10, diagrams_detected=2),
74 transcript_json="transcript/transcript.json",
75 analysis_md="results/analysis.md",
76 key_points=[KeyPoint(point="Point 1")],
77 action_items=[ActionItem(action="Do thing")],
78 diagrams=[DiagramResult(frame_index=0, confidence=0.9)],
79 )
80
81 def test_write_and_read(self, tmp_path):
82 manifest = self._sample_manifest()
83 write_video_manifest(manifest, tmp_path)
84
85 restored = read_video_manifest(tmp_path)
86 assert restored.video.title == "Test"
87 assert restored.stats.frames_extracted == 10
88 assert len(restored.key_points) == 1
89 assert len(restored.diagrams) == 1
90
91 def test_manifest_file_is_valid_json(self, tmp_path):
92 manifest = self._sample_manifest()
93 write_video_manifest(manifest, tmp_path)
94
95 path = tmp_path / "manifest.json"
96 data = json.loads(path.read_text())
97 assert data["version"] == "1.0"
98 assert data["video"]["title"] == "Test"
99
100 def test_creates_parent_dirs(self, tmp_path):
101 manifest = self._sample_manifest()
102 nested = tmp_path / "a" / "b" / "c"
103 write_video_manifest(manifest, nested)
104 assert (nested / "manifest.json").exists()
105
106
107 class TestBatchManifestIO:
108 def _sample_batch(self) -> BatchManifest:
109 return BatchManifest(
110 title="Test Batch",
111 total_videos=2,
112 completed_videos=2,
113 videos=[
114 BatchVideoEntry(
115 video_name="v1",
116 manifest_path="videos/v1/manifest.json",
117 status="completed",
118 ),
119 BatchVideoEntry(
120 video_name="v2",
121 manifest_path="videos/v2/manifest.json",
122 status="completed",
123 ),
124 ],
125 batch_summary_md="batch_summary.md",
126 )
127
128 def test_write_and_read(self, tmp_path):
129 manifest = self._sample_batch()
130 write_batch_manifest(manifest, tmp_path)
131
132 restored = read_batch_manifest(tmp_path)
133 assert restored.title == "Test Batch"
134 assert restored.total_videos == 2
135 assert len(restored.videos) == 2
136 assert restored.videos[0].video_name == "v1"
137
138 def test_manifest_file_is_valid_json(self, tmp_path):
139 manifest = self._sample_batch()
140 write_batch_manifest(manifest, tmp_path)
141
142 path = tmp_path / "manifest.json"
143 data = json.loads(path.read_text())
144 a
--- a/video_processor/models.py
+++ b/video_processor/models.py
@@ -0,0 +1,190 @@
1
+"""Pydantic data models for PlanOpticon output."""
2
+
3
+from datetime import datetime
4
+pathlib import Pathetime
5
+from enum import Enum
6
+from typing import Any, Dict, List, OptionalProtocol, runti: str = "") -> None: ...
7
+
8
+
9
+class DiagramType(str, Enum):
10
+ "" flowchart = "flowchart"
11
+ sequence = "sequence"
12
+ architecture = "architecture"
13
+ whiteboard = "whiteboard"
14
+ chart = "chart"
15
+ table = "table"
16
+ slide = "slide"
17
+ screenshot = "screenshot"
18
+ unknown = "unknown"
19
+
20
+
21
+class OutputFormat(str, Enum):
22
+ """Av markdown = "markdown"
23
+ json = "json"
24
+ html = "html"
25
+ pdf = "pdf"
26
+ pdf"
27
+ pptx = "pptx"
28
+ svg = "svg"
29
+ png = "png"
30
+
31
+
32
+class TranscriptSegment(BaseModel):
33
+ """A single segmen of transcribed audio."""
34
+
35
+ start: float = Field(description="Start time in seconds")
36
+ end: float = Field(description="End time in seconds")
37
+ text: str = Field(description="Transcribed text")
38
+ speaker: Optional[str] = Field(default=None, description="Speaker identifier")
39
+ confidence: Optional[float] = Field(default=None, description="Transcription confidence 0-1")
40
+
41
+
42
+class ActionItem(BaseMod action="Relative path
43
+ action: str = Field(description="The action to be taken")
44
+ assignee: Optional[str] = Field(default=None, description="Person responsible")
45
+ deadline: Optional[str] = Field(default=None, description="Deadline or timeframe")
46
+ priority: Optional[str] = Field(default=None, description="Priority level")
47
+ context: Optional[str] = Field(default=None, description="Additional context")
48
+ souss ActionItem(BaseModel):
49
+ default=None, d (transcript/diagram)"
50
+ )
51
+
52
+
53
+class KeyPoint(BaseModel):
54
+ """A key point xtracted from content."""
55
+
56
+ point: str = Field(description="The key point")
57
+ topic: Optional[str] = Field(default=None, description="Topic or category")
58
+ details: Optional[str] = Field(default=None, description="Supporting details")
59
+ timestamp: Optional[float] = Field(default=None, description="Timestamp in video (seconds)")
60
+ source: Optional[str] = Field(default=None, description="Where thie, code, document, terminal, browse] = Field(
61
+ default_f"""Pydantic data models for PlanOpticon output."""
62
+
63
+from datetime import datetime
64
+from enummodels for PlanOpticon output."""
65
+
66
+from datetime import datetime
67
+from enum import Enum
68
+from typing import Any, Dict, List, OptionalProtocol, run in seconds")
69
+ end: float = Field(description="End time in seconds")
70
+ text: str = Field(description="Transcribed text")
71
+ speaker: Optional[str] = Field(default=None, description="Speaker identifier")
72
+ confidence: Optional[float] = Field(default=None, description="Transcription confidence 0-1")
73
+
74
+
75
+class ActionItem(BaseModel):
76
+ """An action item extracted from content."""
77
+
78
+ action: str = Field(description="The action to be taken")
79
+ assignee: Optional[str] = Field(default=None, description="Person responsible")
80
+ deadline: Optional[str] = Field(default=None, description="Deadline or timeframe")
81
+ priority: Optional[str] = Field(default=None, description="Priority level")
82
+ context: Optional
83
+ vel")
84
+ context: Optional[str] = Field(default=None, description="Additional context")
85
+ source: Optional[str] = Field(
86
+ default=None, description="Where this was found (transcript/diagram)"
87
+ )
88
+
89
+
90
+class KeyPoint(BaseModel):
91
+ """A key point extracted from content."""
92
+
93
+ point: str = Field(description="The key point")
94
+ topic: Optional[str] = Field(default=None, description="Topic or category")
95
+ details: Optional[str] = Field(default=None, description="Supporting details")
96
+ timestamp: Optional[float] = Field(default=None, description="Timestamp in vido (seconds)")
97
+ source: Optional[str] = Field(default=None, description="Where this was found")
98
+ related_diagrams: List[int] = Field(
99
+ default_factory=list, description="Indices of related diagrams"
100
+ )
101
+
102
+
103
+class DiagramResult(BaseModel):
104
+ """Result from diagram extraction and analysis."""
105
+
106
+ frame_index: int = Field(description="Index of the source frame")
107
+ timestamtional[float] = Field(default=None, description="Transcription confidenModel):
108
+ Field(default=None, description="Mermaid syntax representation nameot")
109
+ confidelt=None, descriptia: Optional[Dict[str, Any]] = Field(
110
+ default=None, description="Chart data for reproduction (labels, values, chart_type)"
111
+ )
112
+ image_path: Optional[str] = Field(default=None, description="Relative path tss ActionItem(BaseModel):
113
+ path to rendered ] = Field(default=None, description="Relative path to rendered PNG")
114
+ merma
115
+ id_path: Optional[str] = Field(default=None, description="Relative path to mermaid source")
116
+
117
+
118
+class ScreenCapture(BaseModel):
119
+ """A screen ime
120
+from enum import Enum
121
+from typing import Any, Dict, List, OptionalProtocol, runti: str = "") -> None: ...
122
+
123
+
124
+class DiagramType(str, Enum):
125
+ """Types of visual content detected in video frames."""
126
+
127
+ flowchart = "flowchart"
128
+ sequence = "sequence"
129
+ architecture = "architecture"
130
+ whiteboard = "whiteboard"
131
+ chart = "chart"
132
+ table = "table"
133
+ slide = "slide"
134
+ screenshot = "screenshot"
135
+ unknown = "unknown"
136
+
137
+
138
+class OutputFormat(str, En"""Pydantic data models for PlanOpticon output."""
139
+
140
+from datetime import datetime
141
+from enum import Enum
142
+from typing import Any, Dict, Lie, code, document, terminal, eyPoint(B, description="Text visible in the diagram")
143
+ elements: List[str] = F elements: List[str] = Field(default_factory=list, description="Identified elements")
144
+ relationships: List[str] = Field(default_factory=list, description="Identified relationships")
145
+ mermaid: Optional[str] = Field(default=None, description="Mermaid syntax representation")
146
+ chart_data: Opti default=None, description="Chart data for reproduction (labels, values, chart_type)"
147
+ )
148
+ image_path: Optional[str] = Field(default=None, description="Relative path to original frame")
149
+ svg_path: Optional[str] = Field(default=None, description="Relative path to rendered SVG")
150
+ png
151
+ ""Pydantic con output."""
152
+
153
+from datetime import datetime
154
+from enum import Enum
155
+from typing import Any, Dict, List, Optionaantic data models for PlanOpticon output."""
156
+
157
+from datetime import datetime
158
+from enum import Enum
159
+from typing import Any, Dict, List, OptionalProtocol, runti: str = "") -> None: ...
160
+
161
+
162
+class DiagramType(str, Enum):
163
+ """Types of visual content detected in video frames."""
164
+
165
+ flowchart = "flowchart"
166
+ sequence = "sequence"
167
+ architecture = "architecture"
168
+ whiteboard = "whiteboard"
169
+ chart = "chart"
170
+ table = "table"
171
+ slide = "slide"
172
+ screenshot = "screensr] = Field(default=None, description="Brief description of the content")
173
+ image_path: Optional= Field(
174
+ default=0.0, description="Detection confidence that triggered fallback"
175
+ )
176
+ content_type: Optional[str] = Field(
177
+ default=None,
178
+ description="Content type: slide, code, document, terminal, browser, chat, other",
179
+ )
180
+ eyPoint(B, description="Text visible in the diagram")
181
+ elements: List[str] = Field(default_factory=list, description="Identified elements")
182
+ relationships: List[str] = Field(default_factory=list, description="Identified relationships")
183
+ mermaid: Optional[str] = Field(default=None, description="Mermaid syntax representation")
184
+ chart_data: Opti default=None, description="Chart data for reproduction (labels, values, chart_type)"
185
+ )
186
+ image_path: Optional[str] = Field(default=None, description="Relative path to original frame")
187
+ svg_path: Optional[str] = Field(default=None, description="Relative path to rendered SVG")
188
+ png_path: Optional[str] = Field(default=None, description="Relative path to rendered PNG")
189
+ mermaid_path: Optional[str] = Field(defae, code, document, terminal, browseption="Timestamp in video (second
190
+
--- a/video_processor/models.py
+++ b/video_processor/models.py
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/video_processor/models.py
+++ b/video_processor/models.py
@@ -0,0 +1,190 @@
1 """Pydantic data models for PlanOpticon output."""
2
3 from datetime import datetime
4 pathlib import Pathetime
5 from enum import Enum
6 from typing import Any, Dict, List, OptionalProtocol, runti: str = "") -> None: ...
7
8
9 class DiagramType(str, Enum):
10 "" flowchart = "flowchart"
11 sequence = "sequence"
12 architecture = "architecture"
13 whiteboard = "whiteboard"
14 chart = "chart"
15 table = "table"
16 slide = "slide"
17 screenshot = "screenshot"
18 unknown = "unknown"
19
20
21 class OutputFormat(str, Enum):
22 """Av markdown = "markdown"
23 json = "json"
24 html = "html"
25 pdf = "pdf"
26 pdf"
27 pptx = "pptx"
28 svg = "svg"
29 png = "png"
30
31
32 class TranscriptSegment(BaseModel):
33 """A single segmen of transcribed audio."""
34
35 start: float = Field(description="Start time in seconds")
36 end: float = Field(description="End time in seconds")
37 text: str = Field(description="Transcribed text")
38 speaker: Optional[str] = Field(default=None, description="Speaker identifier")
39 confidence: Optional[float] = Field(default=None, description="Transcription confidence 0-1")
40
41
42 class ActionItem(BaseMod action="Relative path
43 action: str = Field(description="The action to be taken")
44 assignee: Optional[str] = Field(default=None, description="Person responsible")
45 deadline: Optional[str] = Field(default=None, description="Deadline or timeframe")
46 priority: Optional[str] = Field(default=None, description="Priority level")
47 context: Optional[str] = Field(default=None, description="Additional context")
48 souss ActionItem(BaseModel):
49 default=None, d (transcript/diagram)"
50 )
51
52
53 class KeyPoint(BaseModel):
54 """A key point xtracted from content."""
55
56 point: str = Field(description="The key point")
57 topic: Optional[str] = Field(default=None, description="Topic or category")
58 details: Optional[str] = Field(default=None, description="Supporting details")
59 timestamp: Optional[float] = Field(default=None, description="Timestamp in video (seconds)")
60 source: Optional[str] = Field(default=None, description="Where thie, code, document, terminal, browse] = Field(
61 default_f"""Pydantic data models for PlanOpticon output."""
62
63 from datetime import datetime
64 from enummodels for PlanOpticon output."""
65
66 from datetime import datetime
67 from enum import Enum
68 from typing import Any, Dict, List, OptionalProtocol, run in seconds")
69 end: float = Field(description="End time in seconds")
70 text: str = Field(description="Transcribed text")
71 speaker: Optional[str] = Field(default=None, description="Speaker identifier")
72 confidence: Optional[float] = Field(default=None, description="Transcription confidence 0-1")
73
74
75 class ActionItem(BaseModel):
76 """An action item extracted from content."""
77
78 action: str = Field(description="The action to be taken")
79 assignee: Optional[str] = Field(default=None, description="Person responsible")
80 deadline: Optional[str] = Field(default=None, description="Deadline or timeframe")
81 priority: Optional[str] = Field(default=None, description="Priority level")
82 context: Optional
83 vel")
84 context: Optional[str] = Field(default=None, description="Additional context")
85 source: Optional[str] = Field(
86 default=None, description="Where this was found (transcript/diagram)"
87 )
88
89
90 class KeyPoint(BaseModel):
91 """A key point extracted from content."""
92
93 point: str = Field(description="The key point")
94 topic: Optional[str] = Field(default=None, description="Topic or category")
95 details: Optional[str] = Field(default=None, description="Supporting details")
96 timestamp: Optional[float] = Field(default=None, description="Timestamp in vido (seconds)")
97 source: Optional[str] = Field(default=None, description="Where this was found")
98 related_diagrams: List[int] = Field(
99 default_factory=list, description="Indices of related diagrams"
100 )
101
102
103 class DiagramResult(BaseModel):
104 """Result from diagram extraction and analysis."""
105
106 frame_index: int = Field(description="Index of the source frame")
107 timestamtional[float] = Field(default=None, description="Transcription confidenModel):
108 Field(default=None, description="Mermaid syntax representation nameot")
109 confidelt=None, descriptia: Optional[Dict[str, Any]] = Field(
110 default=None, description="Chart data for reproduction (labels, values, chart_type)"
111 )
112 image_path: Optional[str] = Field(default=None, description="Relative path tss ActionItem(BaseModel):
113 path to rendered ] = Field(default=None, description="Relative path to rendered PNG")
114 merma
115 id_path: Optional[str] = Field(default=None, description="Relative path to mermaid source")
116
117
118 class ScreenCapture(BaseModel):
119 """A screen ime
120 from enum import Enum
121 from typing import Any, Dict, List, OptionalProtocol, runti: str = "") -> None: ...
122
123
124 class DiagramType(str, Enum):
125 """Types of visual content detected in video frames."""
126
127 flowchart = "flowchart"
128 sequence = "sequence"
129 architecture = "architecture"
130 whiteboard = "whiteboard"
131 chart = "chart"
132 table = "table"
133 slide = "slide"
134 screenshot = "screenshot"
135 unknown = "unknown"
136
137
138 class OutputFormat(str, En"""Pydantic data models for PlanOpticon output."""
139
140 from datetime import datetime
141 from enum import Enum
142 from typing import Any, Dict, Lie, code, document, terminal, eyPoint(B, description="Text visible in the diagram")
143 elements: List[str] = F elements: List[str] = Field(default_factory=list, description="Identified elements")
144 relationships: List[str] = Field(default_factory=list, description="Identified relationships")
145 mermaid: Optional[str] = Field(default=None, description="Mermaid syntax representation")
146 chart_data: Opti default=None, description="Chart data for reproduction (labels, values, chart_type)"
147 )
148 image_path: Optional[str] = Field(default=None, description="Relative path to original frame")
149 svg_path: Optional[str] = Field(default=None, description="Relative path to rendered SVG")
150 png
151 ""Pydantic con output."""
152
153 from datetime import datetime
154 from enum import Enum
155 from typing import Any, Dict, List, Optionaantic data models for PlanOpticon output."""
156
157 from datetime import datetime
158 from enum import Enum
159 from typing import Any, Dict, List, OptionalProtocol, runti: str = "") -> None: ...
160
161
162 class DiagramType(str, Enum):
163 """Types of visual content detected in video frames."""
164
165 flowchart = "flowchart"
166 sequence = "sequence"
167 architecture = "architecture"
168 whiteboard = "whiteboard"
169 chart = "chart"
170 table = "table"
171 slide = "slide"
172 screenshot = "screensr] = Field(default=None, description="Brief description of the content")
173 image_path: Optional= Field(
174 default=0.0, description="Detection confidence that triggered fallback"
175 )
176 content_type: Optional[str] = Field(
177 default=None,
178 description="Content type: slide, code, document, terminal, browser, chat, other",
179 )
180 eyPoint(B, description="Text visible in the diagram")
181 elements: List[str] = Field(default_factory=list, description="Identified elements")
182 relationships: List[str] = Field(default_factory=list, description="Identified relationships")
183 mermaid: Optional[str] = Field(default=None, description="Mermaid syntax representation")
184 chart_data: Opti default=None, description="Chart data for reproduction (labels, values, chart_type)"
185 )
186 image_path: Optional[str] = Field(default=None, description="Relative path to original frame")
187 svg_path: Optional[str] = Field(default=None, description="Relative path to rendered SVG")
188 png_path: Optional[str] = Field(default=None, description="Relative path to rendered PNG")
189 mermaid_path: Optional[str] = Field(defae, code, document, terminal, browseption="Timestamp in video (second
190
--- a/video_processor/output_structure.py
+++ b/video_processor/output_structure.py
@@ -0,0 +1,56 @@
1
+"""Standardized output directory structure and manifest I/O for PlanOpticon."""
2
+
3
+import json
4
+import logging
5
+from pathlib import Path
6
+from typing import Dict
7
+
8
+from video_processor.models import BatchManifest, VideoManifest
9
+
10
+logger = logging.getLogger(__name__)
11
+
12
+
13
+def create_video_output_dirs(output_dir: str | Path, video_name: str) -> Dict[str, Path]:
14
+ """
15
+ Create standardized single-video output directory structure.
16
+
17
+ Layout:
18
+ output_dir/
19
+ manifest.json
20
+ transcript/
21
+ transcript.json, .txt, .srt
22
+ frames/
23
+ frame_0000.jpg ...
24
+ diagrams/
25
+ diagram_0.json, .jpg, .mermaid, .svg, .png
26
+ captures/
27
+ capture_0.jpg, capture_0.json
28
+ results/
29
+ analysis.md, .html, .pdf
30
+ knowledge_graph.ry structure and mani"s/
31
+ video_1/manifest.json
32
+ video_2/manifest.json
33
+ ...
34
+
35
+ Returns dict mapping directory names to Path objects.
36
+ """
37
+ base = Path(output_dir)
38
+ dirs = {
39
+ "root": base,
40
+ "videos": base / "videos",
41
+ }
42
+ for d in dirs.values():
43
+ d.mkdir(parents=True, exist_ok=True)
44
+ logger.info(f"Created batch output structure for '{batch_name}' at {base}")
45
+ return dirs
46
+
47
+
48
+# --- Manifest I/O ---
49
+
50
+
51
+def write_video_manifest(manifest: VideoManifest, output_dir: str | Path) -> Path:
52
+ """Write a VideoManifest to output_dir/manifest.json."""
53
+ path = Path(output_dir) / "manifest.json"
54
+ path.parent.mkdir(parents=True, exist_ok=True)
55
+ path.write_text(manifest.model_dump_json(indent=2))
56
+ logger.info(f"Wrote video manifest to {pa
--- a/video_processor/output_structure.py
+++ b/video_processor/output_structure.py
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/video_processor/output_structure.py
+++ b/video_processor/output_structure.py
@@ -0,0 +1,56 @@
1 """Standardized output directory structure and manifest I/O for PlanOpticon."""
2
3 import json
4 import logging
5 from pathlib import Path
6 from typing import Dict
7
8 from video_processor.models import BatchManifest, VideoManifest
9
10 logger = logging.getLogger(__name__)
11
12
13 def create_video_output_dirs(output_dir: str | Path, video_name: str) -> Dict[str, Path]:
14 """
15 Create standardized single-video output directory structure.
16
17 Layout:
18 output_dir/
19 manifest.json
20 transcript/
21 transcript.json, .txt, .srt
22 frames/
23 frame_0000.jpg ...
24 diagrams/
25 diagram_0.json, .jpg, .mermaid, .svg, .png
26 captures/
27 capture_0.jpg, capture_0.json
28 results/
29 analysis.md, .html, .pdf
30 knowledge_graph.ry structure and mani"s/
31 video_1/manifest.json
32 video_2/manifest.json
33 ...
34
35 Returns dict mapping directory names to Path objects.
36 """
37 base = Path(output_dir)
38 dirs = {
39 "root": base,
40 "videos": base / "videos",
41 }
42 for d in dirs.values():
43 d.mkdir(parents=True, exist_ok=True)
44 logger.info(f"Created batch output structure for '{batch_name}' at {base}")
45 return dirs
46
47
48 # --- Manifest I/O ---
49
50
51 def write_video_manifest(manifest: VideoManifest, output_dir: str | Path) -> Path:
52 """Write a VideoManifest to output_dir/manifest.json."""
53 path = Path(output_dir) / "manifest.json"
54 path.parent.mkdir(parents=True, exist_ok=True)
55 path.write_text(manifest.model_dump_json(indent=2))
56 logger.info(f"Wrote video manifest to {pa

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button