videonut 1.2.7 → 1.3.0
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.
- package/README.md +272 -272
- package/USER_GUIDE.md +90 -90
- package/agents/core/eic.md +771 -771
- package/agents/creative/director.md +246 -246
- package/agents/creative/scriptwriter.md +207 -207
- package/agents/research/investigator.md +394 -394
- package/agents/technical/archivist.md +288 -288
- package/agents/technical/scavenger.md +247 -247
- package/bin/videonut.js +37 -21
- package/config.yaml +61 -61
- package/docs/scriptwriter.md +42 -42
- package/file_validator.py +186 -186
- package/memory/short_term/asset_manifest.md +64 -64
- package/memory/short_term/investigation_dossier.md +31 -31
- package/memory/short_term/master_script.md +51 -51
- package/package.json +61 -64
- package/requirements.txt +8 -8
- package/setup.js +33 -15
- package/tools/check_env.py +76 -76
- package/tools/downloaders/caption_reader.py +237 -237
- package/tools/downloaders/clip_grabber.py +82 -82
- package/tools/downloaders/image_grabber.py +105 -105
- package/tools/downloaders/pdf_reader.py +163 -163
- package/tools/downloaders/screenshotter.py +58 -58
- package/tools/downloaders/web_reader.py +69 -69
- package/tools/validators/link_checker.py +45 -45
- package/workflow_orchestrator.py +336 -336
- package/.claude/commands/archivist.toml +0 -12
- package/.claude/commands/director.toml +0 -12
- package/.claude/commands/eic.toml +0 -12
- package/.claude/commands/investigator.toml +0 -12
- package/.claude/commands/prompt.toml +0 -12
- package/.claude/commands/scavenger.toml +0 -12
- package/.claude/commands/scout.toml +0 -12
- package/.claude/commands/scriptwriter.toml +0 -12
- package/.claude/commands/seo.toml +0 -12
- package/.claude/commands/thumbnail.toml +0 -12
- package/.claude/commands/topic_scout.toml +0 -12
- package/.gemini/commands/archivist.toml +0 -12
- package/.gemini/commands/director.toml +0 -12
- package/.gemini/commands/eic.toml +0 -12
- package/.gemini/commands/investigator.toml +0 -12
- package/.gemini/commands/prompt.toml +0 -12
- package/.gemini/commands/scavenger.toml +0 -12
- package/.gemini/commands/scout.toml +0 -12
- package/.gemini/commands/scriptwriter.toml +0 -12
- package/.gemini/commands/seo.toml +0 -12
- package/.gemini/commands/thumbnail.toml +0 -12
- package/.gemini/commands/topic_scout.toml +0 -12
- package/.qwen/commands/archivist.toml +0 -12
- package/.qwen/commands/director.toml +0 -12
- package/.qwen/commands/eic.toml +0 -12
- package/.qwen/commands/investigator.toml +0 -12
- package/.qwen/commands/prompt.toml +0 -12
- package/.qwen/commands/scavenger.toml +0 -12
- package/.qwen/commands/scout.toml +0 -12
- package/.qwen/commands/scriptwriter.toml +0 -12
- package/.qwen/commands/seo.toml +0 -12
- package/.qwen/commands/thumbnail.toml +0 -12
- package/.qwen/commands/topic_scout.toml +0 -12
package/config.yaml
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
# VideoNut Configuration
|
|
2
|
-
# This file is managed by Topic Scout agent. All other agents READ ONLY.
|
|
3
|
-
|
|
4
|
-
# ═══════════════════════════════════════════════════════════════════
|
|
5
|
-
# USER SETTINGS
|
|
6
|
-
# ═══════════════════════════════════════════════════════════════════
|
|
7
|
-
user_name: "Producer"
|
|
8
|
-
communication_language: "Telugu"
|
|
9
|
-
|
|
10
|
-
# ═══════════════════════════════════════════════════════════════════
|
|
11
|
-
# PROJECT SETTINGS (Set by Topic Scout)
|
|
12
|
-
# ═══════════════════════════════════════════════════════════════════
|
|
13
|
-
projects_folder: "./Projects"
|
|
14
|
-
current_project: "gemini_2025-12-30_SEBI-Hindenburg_004"
|
|
15
|
-
|
|
16
|
-
# ═══════════════════════════════════════════════════════════════════
|
|
17
|
-
# VIDEO PRODUCTION SETTINGS (Set by Topic Scout)
|
|
18
|
-
# ═══════════════════════════════════════════════════════════════════
|
|
19
|
-
video_format: "Investigative Documentary"
|
|
20
|
-
target_duration: 20
|
|
21
|
-
target_line_count: 2700
|
|
22
|
-
audio_language: "Telugu"
|
|
23
|
-
|
|
24
|
-
# ═══════════════════════════════════════════════════════════════════
|
|
25
|
-
# SCOPE & REGION (Set by Topic Scout - User Selected)
|
|
26
|
-
# ═══════════════════════════════════════════════════════════════════
|
|
27
|
-
# scope: international | national | regional
|
|
28
|
-
scope: "national"
|
|
29
|
-
|
|
30
|
-
# country: Only set if scope is "national" (e.g., India, USA, UK)
|
|
31
|
-
country: "India"
|
|
32
|
-
|
|
33
|
-
# region: User selected region (NOT auto-derived from language)
|
|
34
|
-
# Examples: "Telangana", "Andhra Pradesh", "Maharashtra", "Tamil Nadu", "Pan-India"
|
|
35
|
-
region: "Pan-India"
|
|
36
|
-
|
|
37
|
-
# ═══════════════════════════════════════════════════════════════════
|
|
38
|
-
# INDUSTRY TAG (Set by Topic Scout)
|
|
39
|
-
# ═══════════════════════════════════════════════════════════════════
|
|
40
|
-
# Helps agents stay in context and use appropriate sources
|
|
41
|
-
# Options: Finance, Stock Market, Political, Crime, Social Awareness,
|
|
42
|
-
# Technology, Entertainment, Sports, Health, Environment, Business, Other
|
|
43
|
-
industry_tag: "Political"
|
|
44
|
-
|
|
45
|
-
# Industry-specific sources (auto-populated based on industry_tag)
|
|
46
|
-
# Finance: RBI, SEBI, Economic Times, Mint
|
|
47
|
-
# Stock Market: NSE, BSE, MoneyControl, TradingView
|
|
48
|
-
# Political: Election Commission, PRS Legislative, Parliament videos
|
|
49
|
-
# Crime: Court records, Police statements, NCRB data
|
|
50
|
-
# Social Awareness: NGO reports, Government schemes, RTI data
|
|
51
|
-
|
|
52
|
-
# ═══════════════════════════════════════════════════════════════════
|
|
53
|
-
# EIC CORRECTION TRACKING (Set by EIC after review)
|
|
54
|
-
# ═══════════════════════════════════════════════════════════════════
|
|
55
|
-
# correction_log: Path to the correction log file (relative to project folder)
|
|
56
|
-
# Status: pending_review | corrections_needed | approved
|
|
57
|
-
correction_log: ""
|
|
58
|
-
correction_status: "pending_review"
|
|
59
|
-
|
|
60
|
-
# Agents with pending corrections (comma-separated)
|
|
61
|
-
# Example: "investigator,scriptwriter,director"
|
|
1
|
+
# VideoNut Configuration
|
|
2
|
+
# This file is managed by Topic Scout agent. All other agents READ ONLY.
|
|
3
|
+
|
|
4
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
5
|
+
# USER SETTINGS
|
|
6
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
7
|
+
user_name: "Producer"
|
|
8
|
+
communication_language: "Telugu"
|
|
9
|
+
|
|
10
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
11
|
+
# PROJECT SETTINGS (Set by Topic Scout)
|
|
12
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
13
|
+
projects_folder: "./Projects"
|
|
14
|
+
current_project: "gemini_2025-12-30_SEBI-Hindenburg_004"
|
|
15
|
+
|
|
16
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
17
|
+
# VIDEO PRODUCTION SETTINGS (Set by Topic Scout)
|
|
18
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
19
|
+
video_format: "Investigative Documentary"
|
|
20
|
+
target_duration: 20
|
|
21
|
+
target_line_count: 2700
|
|
22
|
+
audio_language: "Telugu"
|
|
23
|
+
|
|
24
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
25
|
+
# SCOPE & REGION (Set by Topic Scout - User Selected)
|
|
26
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
27
|
+
# scope: international | national | regional
|
|
28
|
+
scope: "national"
|
|
29
|
+
|
|
30
|
+
# country: Only set if scope is "national" (e.g., India, USA, UK)
|
|
31
|
+
country: "India"
|
|
32
|
+
|
|
33
|
+
# region: User selected region (NOT auto-derived from language)
|
|
34
|
+
# Examples: "Telangana", "Andhra Pradesh", "Maharashtra", "Tamil Nadu", "Pan-India"
|
|
35
|
+
region: "Pan-India"
|
|
36
|
+
|
|
37
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
38
|
+
# INDUSTRY TAG (Set by Topic Scout)
|
|
39
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
40
|
+
# Helps agents stay in context and use appropriate sources
|
|
41
|
+
# Options: Finance, Stock Market, Political, Crime, Social Awareness,
|
|
42
|
+
# Technology, Entertainment, Sports, Health, Environment, Business, Other
|
|
43
|
+
industry_tag: "Political"
|
|
44
|
+
|
|
45
|
+
# Industry-specific sources (auto-populated based on industry_tag)
|
|
46
|
+
# Finance: RBI, SEBI, Economic Times, Mint
|
|
47
|
+
# Stock Market: NSE, BSE, MoneyControl, TradingView
|
|
48
|
+
# Political: Election Commission, PRS Legislative, Parliament videos
|
|
49
|
+
# Crime: Court records, Police statements, NCRB data
|
|
50
|
+
# Social Awareness: NGO reports, Government schemes, RTI data
|
|
51
|
+
|
|
52
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
53
|
+
# EIC CORRECTION TRACKING (Set by EIC after review)
|
|
54
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
55
|
+
# correction_log: Path to the correction log file (relative to project folder)
|
|
56
|
+
# Status: pending_review | corrections_needed | approved
|
|
57
|
+
correction_log: ""
|
|
58
|
+
correction_status: "pending_review"
|
|
59
|
+
|
|
60
|
+
# Agents with pending corrections (comma-separated)
|
|
61
|
+
# Example: "investigator,scriptwriter,director"
|
|
62
62
|
agents_with_errors: ""
|
package/docs/scriptwriter.md
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
# ✍️ Scriptwriter Agent: Sorkin
|
|
2
|
-
|
|
3
|
-
The Scriptwriter is a specialized creative agent designed to bridge the gap between raw investigative data and cinematic visual direction. It focuses exclusively on the **human element**, **narrative flow**, and **viewer retention**.
|
|
4
|
-
|
|
5
|
-
## 🎯 Purpose
|
|
6
|
-
The primary goal of the Scriptwriter is to humanize the findings of the Investigator. While the Investigator provides the "Truth," the Scriptwriter provides the "Soul." It ensures that all 360-degree perspectives (victims, authorities, systems) are represented with emotional depth and persuasive rhetoric.
|
|
7
|
-
|
|
8
|
-
## 🛠️ Setup & Dependencies
|
|
9
|
-
The Scriptwriter operates within the VideoNut ecosystem and requires the following:
|
|
10
|
-
- **Project Structure:** Must have access to the active project's `truth_dossier.md`.
|
|
11
|
-
- **Dependencies:**
|
|
12
|
-
- Python 3.8+
|
|
13
|
-
- VideoNut Core Framework (for file I/O)
|
|
14
|
-
- `config.yaml` with an active `current_project` path.
|
|
15
|
-
|
|
16
|
-
## 🚀 Usage
|
|
17
|
-
|
|
18
|
-
### CLI Integration
|
|
19
|
-
You can invoke the Scriptwriter directly from the Gemini CLI:
|
|
20
|
-
```bash
|
|
21
|
-
/scriptwriter
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### Workflow Execution
|
|
25
|
-
1. **Load Agent:** Use `/scriptwriter`.
|
|
26
|
-
2. **Execute Command:** Select `[WS] Write Script`.
|
|
27
|
-
3. **Input:** Automatically reads the `truth_dossier.md` from the active project.
|
|
28
|
-
4. **Output:** Generates `narrative_script.md` in the project folder.
|
|
29
|
-
|
|
30
|
-
## 🧠 Core Logic: The 360-Degree Perspective
|
|
31
|
-
The agent uses a **Recursive Narrative Audit** to ensure:
|
|
32
|
-
- **The Hook:** A 45-second high-retention opening.
|
|
33
|
-
- **Stakeholder Inclusion:** Dedicated emotional beats for victims found by the "Bloodhound" protocol.
|
|
34
|
-
- **Rhetorical Sharpness:** Criticism of authority and systemic issues where supported by facts.
|
|
35
|
-
|
|
36
|
-
## 📝 Configuration (TOML)
|
|
37
|
-
The agent is registered in the CLI via the following TOML structure:
|
|
38
|
-
```toml
|
|
39
|
-
[scriptwriter]
|
|
40
|
-
name = "scriptwriter"
|
|
41
|
-
description = "Invoke the Scriptwriter agent (Sorkin)"
|
|
42
|
-
agent_path = "_video_nut/agents/creative/scriptwriter.md"
|
|
1
|
+
# ✍️ Scriptwriter Agent: Sorkin
|
|
2
|
+
|
|
3
|
+
The Scriptwriter is a specialized creative agent designed to bridge the gap between raw investigative data and cinematic visual direction. It focuses exclusively on the **human element**, **narrative flow**, and **viewer retention**.
|
|
4
|
+
|
|
5
|
+
## 🎯 Purpose
|
|
6
|
+
The primary goal of the Scriptwriter is to humanize the findings of the Investigator. While the Investigator provides the "Truth," the Scriptwriter provides the "Soul." It ensures that all 360-degree perspectives (victims, authorities, systems) are represented with emotional depth and persuasive rhetoric.
|
|
7
|
+
|
|
8
|
+
## 🛠️ Setup & Dependencies
|
|
9
|
+
The Scriptwriter operates within the VideoNut ecosystem and requires the following:
|
|
10
|
+
- **Project Structure:** Must have access to the active project's `truth_dossier.md`.
|
|
11
|
+
- **Dependencies:**
|
|
12
|
+
- Python 3.8+
|
|
13
|
+
- VideoNut Core Framework (for file I/O)
|
|
14
|
+
- `config.yaml` with an active `current_project` path.
|
|
15
|
+
|
|
16
|
+
## 🚀 Usage
|
|
17
|
+
|
|
18
|
+
### CLI Integration
|
|
19
|
+
You can invoke the Scriptwriter directly from the Gemini CLI:
|
|
20
|
+
```bash
|
|
21
|
+
/scriptwriter
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Workflow Execution
|
|
25
|
+
1. **Load Agent:** Use `/scriptwriter`.
|
|
26
|
+
2. **Execute Command:** Select `[WS] Write Script`.
|
|
27
|
+
3. **Input:** Automatically reads the `truth_dossier.md` from the active project.
|
|
28
|
+
4. **Output:** Generates `narrative_script.md` in the project folder.
|
|
29
|
+
|
|
30
|
+
## 🧠 Core Logic: The 360-Degree Perspective
|
|
31
|
+
The agent uses a **Recursive Narrative Audit** to ensure:
|
|
32
|
+
- **The Hook:** A 45-second high-retention opening.
|
|
33
|
+
- **Stakeholder Inclusion:** Dedicated emotional beats for victims found by the "Bloodhound" protocol.
|
|
34
|
+
- **Rhetorical Sharpness:** Criticism of authority and systemic issues where supported by facts.
|
|
35
|
+
|
|
36
|
+
## 📝 Configuration (TOML)
|
|
37
|
+
The agent is registered in the CLI via the following TOML structure:
|
|
38
|
+
```toml
|
|
39
|
+
[scriptwriter]
|
|
40
|
+
name = "scriptwriter"
|
|
41
|
+
description = "Invoke the Scriptwriter agent (Sorkin)"
|
|
42
|
+
agent_path = "_video_nut/agents/creative/scriptwriter.md"
|
|
43
43
|
```
|
package/file_validator.py
CHANGED
|
@@ -1,187 +1,187 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
VideoNut File Validator
|
|
4
|
-
Validates the integrity and format of files generated by the VideoNut agents
|
|
5
|
-
"""
|
|
6
|
-
import os
|
|
7
|
-
import sys
|
|
8
|
-
import re
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
|
|
11
|
-
def validate_truth_dossier(file_path):
|
|
12
|
-
"""Validate the truth_dossier.md file"""
|
|
13
|
-
if not os.path.exists(file_path):
|
|
14
|
-
return False, f"File does not exist: {file_path}"
|
|
15
|
-
|
|
16
|
-
with open(file_path, 'r', encoding='utf-8') as f:
|
|
17
|
-
content = f.read()
|
|
18
|
-
|
|
19
|
-
# Check for essential sections
|
|
20
|
-
required_sections = ['The Investigation Blueprint', 'The Findings', 'The Angle', 'The Conflict']
|
|
21
|
-
missing_sections = []
|
|
22
|
-
|
|
23
|
-
for section in required_sections:
|
|
24
|
-
if f'# {section}' not in content and f'## {section}' not in content:
|
|
25
|
-
missing_sections.append(section)
|
|
26
|
-
|
|
27
|
-
if missing_sections:
|
|
28
|
-
return False, f"Missing required sections: {missing_sections}"
|
|
29
|
-
|
|
30
|
-
# Check for minimum content length
|
|
31
|
-
if len(content.strip()) < 100:
|
|
32
|
-
return False, "File content too short, may be incomplete"
|
|
33
|
-
|
|
34
|
-
return True, "Truth Dossier validation passed"
|
|
35
|
-
|
|
36
|
-
def validate_narrative_script(file_path):
|
|
37
|
-
"""Validate the narrative_script.md file"""
|
|
38
|
-
if not os.path.exists(file_path):
|
|
39
|
-
return False, f"File does not exist: {file_path}"
|
|
40
|
-
|
|
41
|
-
with open(file_path, 'r', encoding='utf-8') as f:
|
|
42
|
-
content = f.read()
|
|
43
|
-
|
|
44
|
-
# Check for essential narrative elements
|
|
45
|
-
required_elements = ['The Hook', 'The Bridge', 'The Meat', 'The Verdict']
|
|
46
|
-
missing_elements = []
|
|
47
|
-
|
|
48
|
-
for element in required_elements:
|
|
49
|
-
if element not in content:
|
|
50
|
-
missing_elements.append(element)
|
|
51
|
-
|
|
52
|
-
if missing_elements:
|
|
53
|
-
return False, f"Missing required narrative elements: {missing_elements}"
|
|
54
|
-
|
|
55
|
-
# Check for minimum content length
|
|
56
|
-
if len(content.strip()) < 100:
|
|
57
|
-
return False, "File content too short, may be incomplete"
|
|
58
|
-
|
|
59
|
-
return True, "Narrative Script validation passed"
|
|
60
|
-
|
|
61
|
-
def validate_master_script(file_path):
|
|
62
|
-
"""Validate the master_script.md file"""
|
|
63
|
-
if not os.path.exists(file_path):
|
|
64
|
-
return False, f"File does not exist: {file_path}"
|
|
65
|
-
|
|
66
|
-
with open(file_path, 'r', encoding='utf-8') as f:
|
|
67
|
-
content = f.read()
|
|
68
|
-
|
|
69
|
-
# Look for the expected format: [NARRATION: "..."] [VISUAL: Description. [Source: URL or MANUAL]]
|
|
70
|
-
narration_pattern = r'\[NARRATION:.*?\]'
|
|
71
|
-
visual_pattern = r'\[VISUAL:.*?\]'
|
|
72
|
-
|
|
73
|
-
narration_matches = re.findall(narration_pattern, content)
|
|
74
|
-
visual_matches = re.findall(visual_pattern, content)
|
|
75
|
-
|
|
76
|
-
if len(narration_matches) == 0:
|
|
77
|
-
return False, "No narration blocks found in expected format [NARRATION: \"...\"]"
|
|
78
|
-
|
|
79
|
-
if len(visual_matches) == 0:
|
|
80
|
-
return False, "No visual blocks found in expected format [VISUAL: ...]"
|
|
81
|
-
|
|
82
|
-
if len(content.strip()) < 100:
|
|
83
|
-
return False, "File content too short, may be incomplete"
|
|
84
|
-
|
|
85
|
-
return True, f"Master Script validation passed ({len(narration_matches)} narration blocks, {len(visual_matches)} visual blocks)"
|
|
86
|
-
|
|
87
|
-
def validate_asset_manifest(file_path):
|
|
88
|
-
"""Validate the asset_manifest.md file"""
|
|
89
|
-
if not os.path.exists(file_path):
|
|
90
|
-
return False, f"File does not exist: {file_path}"
|
|
91
|
-
|
|
92
|
-
with open(file_path, 'r', encoding='utf-8') as f:
|
|
93
|
-
content = f.read()
|
|
94
|
-
|
|
95
|
-
# Look for asset entries (should contain URLs or file references)
|
|
96
|
-
url_pattern = r'https?://[^\s<>"{}|\\^`\[\]]+'
|
|
97
|
-
asset_pattern = r'(\[.*?\])|(\*\*.*?\*\*)' # Markdown links or bold text
|
|
98
|
-
|
|
99
|
-
url_matches = re.findall(url_pattern, content)
|
|
100
|
-
asset_matches = re.findall(asset_pattern, content)
|
|
101
|
-
|
|
102
|
-
if len(url_matches) == 0 and len(asset_matches) == 0:
|
|
103
|
-
return False, "No assets or URLs found in manifest"
|
|
104
|
-
|
|
105
|
-
if len(content.strip()) < 10:
|
|
106
|
-
return False, "File content too short, may be incomplete"
|
|
107
|
-
|
|
108
|
-
return True, f"Asset Manifest validation passed ({len(url_matches)} URLs found)"
|
|
109
|
-
|
|
110
|
-
def validate_project_directory(project_dir):
|
|
111
|
-
"""Validate the entire project directory structure"""
|
|
112
|
-
results = {}
|
|
113
|
-
|
|
114
|
-
# Define expected files
|
|
115
|
-
expected_files = {
|
|
116
|
-
'truth_dossier.md': validate_truth_dossier,
|
|
117
|
-
'narrative_script.md': validate_narrative_script,
|
|
118
|
-
'master_script.md': validate_master_script,
|
|
119
|
-
'asset_manifest.md': validate_asset_manifest
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
for filename, validator in expected_files.items():
|
|
123
|
-
file_path = os.path.join(project_dir, filename)
|
|
124
|
-
is_valid, message = validator(file_path)
|
|
125
|
-
results[filename] = (is_valid, message)
|
|
126
|
-
|
|
127
|
-
return results
|
|
128
|
-
|
|
129
|
-
def main():
|
|
130
|
-
if len(sys.argv) < 2:
|
|
131
|
-
print("Usage: python file_validator.py <project_directory> [specific_file]")
|
|
132
|
-
print("Example: python file_validator.py ./Projects/my_project")
|
|
133
|
-
print("Example: python file_validator.py ./Projects/my_project truth_dossier.md")
|
|
134
|
-
sys.exit(1)
|
|
135
|
-
|
|
136
|
-
project_dir = sys.argv[1]
|
|
137
|
-
specific_file = sys.argv[2] if len(sys.argv) > 2 else None
|
|
138
|
-
|
|
139
|
-
if not os.path.exists(project_dir):
|
|
140
|
-
print(f"❌ Project directory does not exist: {project_dir}")
|
|
141
|
-
sys.exit(1)
|
|
142
|
-
|
|
143
|
-
print(f"🔍 Validating VideoNut project: {project_dir}")
|
|
144
|
-
print("-" * 50)
|
|
145
|
-
|
|
146
|
-
if specific_file:
|
|
147
|
-
# Validate specific file
|
|
148
|
-
validators = {
|
|
149
|
-
'truth_dossier.md': validate_truth_dossier,
|
|
150
|
-
'narrative_script.md': validate_narrative_script,
|
|
151
|
-
'master_script.md': validate_master_script,
|
|
152
|
-
'asset_manifest.md': validate_asset_manifest
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if specific_file in validators:
|
|
156
|
-
file_path = os.path.join(project_dir, specific_file)
|
|
157
|
-
is_valid, message = validators[specific_file](file_path)
|
|
158
|
-
|
|
159
|
-
status = "✅" if is_valid else "❌"
|
|
160
|
-
print(f"{status} {specific_file}: {message}")
|
|
161
|
-
|
|
162
|
-
if not is_valid:
|
|
163
|
-
sys.exit(1)
|
|
164
|
-
else:
|
|
165
|
-
print(f"❌ Unknown file type: {specific_file}")
|
|
166
|
-
print("Supported files: truth_dossier.md, narrative_script.md, master_script.md, asset_manifest.md")
|
|
167
|
-
sys.exit(1)
|
|
168
|
-
else:
|
|
169
|
-
# Validate entire project
|
|
170
|
-
results = validate_project_directory(project_dir)
|
|
171
|
-
|
|
172
|
-
all_valid = True
|
|
173
|
-
for filename, (is_valid, message) in results.items():
|
|
174
|
-
status = "✅" if is_valid else "❌"
|
|
175
|
-
print(f"{status} {filename}: {message}")
|
|
176
|
-
if not is_valid:
|
|
177
|
-
all_valid = False
|
|
178
|
-
|
|
179
|
-
print("-" * 50)
|
|
180
|
-
if all_valid:
|
|
181
|
-
print("🎉 All files validated successfully!")
|
|
182
|
-
else:
|
|
183
|
-
print("❌ Some files failed validation")
|
|
184
|
-
sys.exit(1)
|
|
185
|
-
|
|
186
|
-
if __name__ == "__main__":
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
VideoNut File Validator
|
|
4
|
+
Validates the integrity and format of files generated by the VideoNut agents
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import re
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
def validate_truth_dossier(file_path):
|
|
12
|
+
"""Validate the truth_dossier.md file"""
|
|
13
|
+
if not os.path.exists(file_path):
|
|
14
|
+
return False, f"File does not exist: {file_path}"
|
|
15
|
+
|
|
16
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
17
|
+
content = f.read()
|
|
18
|
+
|
|
19
|
+
# Check for essential sections
|
|
20
|
+
required_sections = ['The Investigation Blueprint', 'The Findings', 'The Angle', 'The Conflict']
|
|
21
|
+
missing_sections = []
|
|
22
|
+
|
|
23
|
+
for section in required_sections:
|
|
24
|
+
if f'# {section}' not in content and f'## {section}' not in content:
|
|
25
|
+
missing_sections.append(section)
|
|
26
|
+
|
|
27
|
+
if missing_sections:
|
|
28
|
+
return False, f"Missing required sections: {missing_sections}"
|
|
29
|
+
|
|
30
|
+
# Check for minimum content length
|
|
31
|
+
if len(content.strip()) < 100:
|
|
32
|
+
return False, "File content too short, may be incomplete"
|
|
33
|
+
|
|
34
|
+
return True, "Truth Dossier validation passed"
|
|
35
|
+
|
|
36
|
+
def validate_narrative_script(file_path):
|
|
37
|
+
"""Validate the narrative_script.md file"""
|
|
38
|
+
if not os.path.exists(file_path):
|
|
39
|
+
return False, f"File does not exist: {file_path}"
|
|
40
|
+
|
|
41
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
42
|
+
content = f.read()
|
|
43
|
+
|
|
44
|
+
# Check for essential narrative elements
|
|
45
|
+
required_elements = ['The Hook', 'The Bridge', 'The Meat', 'The Verdict']
|
|
46
|
+
missing_elements = []
|
|
47
|
+
|
|
48
|
+
for element in required_elements:
|
|
49
|
+
if element not in content:
|
|
50
|
+
missing_elements.append(element)
|
|
51
|
+
|
|
52
|
+
if missing_elements:
|
|
53
|
+
return False, f"Missing required narrative elements: {missing_elements}"
|
|
54
|
+
|
|
55
|
+
# Check for minimum content length
|
|
56
|
+
if len(content.strip()) < 100:
|
|
57
|
+
return False, "File content too short, may be incomplete"
|
|
58
|
+
|
|
59
|
+
return True, "Narrative Script validation passed"
|
|
60
|
+
|
|
61
|
+
def validate_master_script(file_path):
|
|
62
|
+
"""Validate the master_script.md file"""
|
|
63
|
+
if not os.path.exists(file_path):
|
|
64
|
+
return False, f"File does not exist: {file_path}"
|
|
65
|
+
|
|
66
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
67
|
+
content = f.read()
|
|
68
|
+
|
|
69
|
+
# Look for the expected format: [NARRATION: "..."] [VISUAL: Description. [Source: URL or MANUAL]]
|
|
70
|
+
narration_pattern = r'\[NARRATION:.*?\]'
|
|
71
|
+
visual_pattern = r'\[VISUAL:.*?\]'
|
|
72
|
+
|
|
73
|
+
narration_matches = re.findall(narration_pattern, content)
|
|
74
|
+
visual_matches = re.findall(visual_pattern, content)
|
|
75
|
+
|
|
76
|
+
if len(narration_matches) == 0:
|
|
77
|
+
return False, "No narration blocks found in expected format [NARRATION: \"...\"]"
|
|
78
|
+
|
|
79
|
+
if len(visual_matches) == 0:
|
|
80
|
+
return False, "No visual blocks found in expected format [VISUAL: ...]"
|
|
81
|
+
|
|
82
|
+
if len(content.strip()) < 100:
|
|
83
|
+
return False, "File content too short, may be incomplete"
|
|
84
|
+
|
|
85
|
+
return True, f"Master Script validation passed ({len(narration_matches)} narration blocks, {len(visual_matches)} visual blocks)"
|
|
86
|
+
|
|
87
|
+
def validate_asset_manifest(file_path):
|
|
88
|
+
"""Validate the asset_manifest.md file"""
|
|
89
|
+
if not os.path.exists(file_path):
|
|
90
|
+
return False, f"File does not exist: {file_path}"
|
|
91
|
+
|
|
92
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
93
|
+
content = f.read()
|
|
94
|
+
|
|
95
|
+
# Look for asset entries (should contain URLs or file references)
|
|
96
|
+
url_pattern = r'https?://[^\s<>"{}|\\^`\[\]]+'
|
|
97
|
+
asset_pattern = r'(\[.*?\])|(\*\*.*?\*\*)' # Markdown links or bold text
|
|
98
|
+
|
|
99
|
+
url_matches = re.findall(url_pattern, content)
|
|
100
|
+
asset_matches = re.findall(asset_pattern, content)
|
|
101
|
+
|
|
102
|
+
if len(url_matches) == 0 and len(asset_matches) == 0:
|
|
103
|
+
return False, "No assets or URLs found in manifest"
|
|
104
|
+
|
|
105
|
+
if len(content.strip()) < 10:
|
|
106
|
+
return False, "File content too short, may be incomplete"
|
|
107
|
+
|
|
108
|
+
return True, f"Asset Manifest validation passed ({len(url_matches)} URLs found)"
|
|
109
|
+
|
|
110
|
+
def validate_project_directory(project_dir):
|
|
111
|
+
"""Validate the entire project directory structure"""
|
|
112
|
+
results = {}
|
|
113
|
+
|
|
114
|
+
# Define expected files
|
|
115
|
+
expected_files = {
|
|
116
|
+
'truth_dossier.md': validate_truth_dossier,
|
|
117
|
+
'narrative_script.md': validate_narrative_script,
|
|
118
|
+
'master_script.md': validate_master_script,
|
|
119
|
+
'asset_manifest.md': validate_asset_manifest
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for filename, validator in expected_files.items():
|
|
123
|
+
file_path = os.path.join(project_dir, filename)
|
|
124
|
+
is_valid, message = validator(file_path)
|
|
125
|
+
results[filename] = (is_valid, message)
|
|
126
|
+
|
|
127
|
+
return results
|
|
128
|
+
|
|
129
|
+
def main():
|
|
130
|
+
if len(sys.argv) < 2:
|
|
131
|
+
print("Usage: python file_validator.py <project_directory> [specific_file]")
|
|
132
|
+
print("Example: python file_validator.py ./Projects/my_project")
|
|
133
|
+
print("Example: python file_validator.py ./Projects/my_project truth_dossier.md")
|
|
134
|
+
sys.exit(1)
|
|
135
|
+
|
|
136
|
+
project_dir = sys.argv[1]
|
|
137
|
+
specific_file = sys.argv[2] if len(sys.argv) > 2 else None
|
|
138
|
+
|
|
139
|
+
if not os.path.exists(project_dir):
|
|
140
|
+
print(f"❌ Project directory does not exist: {project_dir}")
|
|
141
|
+
sys.exit(1)
|
|
142
|
+
|
|
143
|
+
print(f"🔍 Validating VideoNut project: {project_dir}")
|
|
144
|
+
print("-" * 50)
|
|
145
|
+
|
|
146
|
+
if specific_file:
|
|
147
|
+
# Validate specific file
|
|
148
|
+
validators = {
|
|
149
|
+
'truth_dossier.md': validate_truth_dossier,
|
|
150
|
+
'narrative_script.md': validate_narrative_script,
|
|
151
|
+
'master_script.md': validate_master_script,
|
|
152
|
+
'asset_manifest.md': validate_asset_manifest
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if specific_file in validators:
|
|
156
|
+
file_path = os.path.join(project_dir, specific_file)
|
|
157
|
+
is_valid, message = validators[specific_file](file_path)
|
|
158
|
+
|
|
159
|
+
status = "✅" if is_valid else "❌"
|
|
160
|
+
print(f"{status} {specific_file}: {message}")
|
|
161
|
+
|
|
162
|
+
if not is_valid:
|
|
163
|
+
sys.exit(1)
|
|
164
|
+
else:
|
|
165
|
+
print(f"❌ Unknown file type: {specific_file}")
|
|
166
|
+
print("Supported files: truth_dossier.md, narrative_script.md, master_script.md, asset_manifest.md")
|
|
167
|
+
sys.exit(1)
|
|
168
|
+
else:
|
|
169
|
+
# Validate entire project
|
|
170
|
+
results = validate_project_directory(project_dir)
|
|
171
|
+
|
|
172
|
+
all_valid = True
|
|
173
|
+
for filename, (is_valid, message) in results.items():
|
|
174
|
+
status = "✅" if is_valid else "❌"
|
|
175
|
+
print(f"{status} {filename}: {message}")
|
|
176
|
+
if not is_valid:
|
|
177
|
+
all_valid = False
|
|
178
|
+
|
|
179
|
+
print("-" * 50)
|
|
180
|
+
if all_valid:
|
|
181
|
+
print("🎉 All files validated successfully!")
|
|
182
|
+
else:
|
|
183
|
+
print("❌ Some files failed validation")
|
|
184
|
+
sys.exit(1)
|
|
185
|
+
|
|
186
|
+
if __name__ == "__main__":
|
|
187
187
|
main()
|