Compare commits
3 Commits
fix/606-po
...
feat/623-q
| Author | SHA1 | Date | |
|---|---|---|---|
| e06692b914 | |||
| 24906e15dd | |||
| e780ab0805 |
71
pipelines/README-quality-gate.md
Normal file
71
pipelines/README-quality-gate.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Quality Gate
|
||||
|
||||
Validates all pipeline outputs before saving.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Validate a training pair
|
||||
python3 quality-gate.py validate --type training_pair --input pair.json --pipeline training
|
||||
|
||||
# Validate a knowledge file
|
||||
python3 quality-gate.py validate --type knowledge_file --input knowledge.json --pipeline knowledge
|
||||
|
||||
# Validate a generated asset
|
||||
python3 quality-gate.py validate --type generated_asset --input image.png --pipeline assets
|
||||
|
||||
# Validate adversary output
|
||||
python3 quality-gate.py validate --type adversary_output --input vuln.json --pipeline adversary
|
||||
|
||||
# View statistics
|
||||
python3 quality-gate.py stats
|
||||
|
||||
# Generate report
|
||||
python3 quality-gate.py report
|
||||
```
|
||||
|
||||
## Checks Performed
|
||||
|
||||
### Training Pairs
|
||||
- Prompt and response both non-empty
|
||||
- Not duplicate content
|
||||
- Not toxic/harmful
|
||||
- SOUL.md compliance
|
||||
- Response quality (length, formatting)
|
||||
|
||||
### Knowledge Files
|
||||
- Required fields present (title, content, source, category)
|
||||
- Not duplicate
|
||||
- Not toxic
|
||||
- Valid category
|
||||
|
||||
### Generated Assets
|
||||
- File exists and not empty
|
||||
- Valid file extension
|
||||
- Metadata complete (generator, prompt, timestamp)
|
||||
- SOUL.md compliance in prompt
|
||||
|
||||
### Adversary Outputs
|
||||
- Required fields (vulnerability, description, reproduction_steps, severity)
|
||||
- Reproduction steps as list
|
||||
- Valid severity level
|
||||
- Description not empty
|
||||
|
||||
## Integration
|
||||
|
||||
Add to pipeline orchestrator:
|
||||
|
||||
```python
|
||||
from pipelines.quality_gate import QualityGate
|
||||
|
||||
gate = QualityGate()
|
||||
|
||||
# After generating output
|
||||
result = gate.validate_training_pair(data, pipeline="training")
|
||||
|
||||
if result.passed:
|
||||
save_output(data)
|
||||
else:
|
||||
gate.reject_output(data, result, "training_pair", "training")
|
||||
requeue_for_regeneration()
|
||||
```
|
||||
691
pipelines/quality-gate.py
Normal file
691
pipelines/quality-gate.py
Normal file
@@ -0,0 +1,691 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Quality Gate — Validate All Pipeline Outputs
|
||||
|
||||
Every pipeline output must pass quality checks before being saved.
|
||||
Auto-rejects bad outputs, re-queues for regeneration.
|
||||
|
||||
Usage:
|
||||
python3 quality-gate.py validate --type training_pair --input file.json
|
||||
python3 quality-gate.py validate --type knowledge_file --input file.json
|
||||
python3 quality-gate.py validate --type generated_asset --input file.png
|
||||
python3 quality-gate.py validate --type adversary_output --input file.json
|
||||
python3 quality-gate.py stats --pipeline training
|
||||
python3 quality-gate.py report
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
# Configuration
|
||||
HERMES_HOME = Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes"))
|
||||
QUALITY_DIR = HERMES_HOME / "pipelines" / "quality"
|
||||
STATS_FILE = QUALITY_DIR / "quality_stats.json"
|
||||
REJECT_DIR = QUALITY_DIR / "rejected"
|
||||
SOUL_FILE = Path(__file__).parent.parent / "SOUL.md"
|
||||
|
||||
# Ensure directories exist
|
||||
QUALITY_DIR.mkdir(parents=True, exist_ok=True)
|
||||
REJECT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
class QualityResult:
|
||||
"""Result of a quality check."""
|
||||
|
||||
def __init__(self, passed: bool, score: float = 0.0, checks: List[str] = None,
|
||||
failures: List[str] = None, warnings: List[str] = None):
|
||||
self.passed = passed
|
||||
self.score = score # 0.0 to 1.0
|
||||
self.checks = checks or []
|
||||
self.failures = failures or []
|
||||
self.warnings = warnings or []
|
||||
self.timestamp = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"passed": self.passed,
|
||||
"score": self.score,
|
||||
"checks": self.checks,
|
||||
"failures": self.failures,
|
||||
"warnings": self.warnings,
|
||||
"timestamp": self.timestamp
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
status = "PASS" if self.passed else "FAIL"
|
||||
return f"QualityResult({status}, score={self.score:.2f})"
|
||||
|
||||
|
||||
class QualityGate:
|
||||
"""Main quality gate class."""
|
||||
|
||||
def __init__(self):
|
||||
self.soul_content = self._load_soul()
|
||||
self.stats = self._load_stats()
|
||||
|
||||
def _load_soul(self) -> str:
|
||||
"""Load SOUL.md content for compliance checks."""
|
||||
try:
|
||||
if SOUL_FILE.exists():
|
||||
return SOUL_FILE.read_text()
|
||||
except Exception:
|
||||
pass
|
||||
return ""
|
||||
|
||||
def _load_stats(self) -> Dict[str, Any]:
|
||||
"""Load quality statistics."""
|
||||
try:
|
||||
if STATS_FILE.exists():
|
||||
return json.loads(STATS_FILE.read_text())
|
||||
except Exception:
|
||||
pass
|
||||
return {
|
||||
"total_checks": 0,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"by_type": {},
|
||||
"by_pipeline": {},
|
||||
"recent_failures": []
|
||||
}
|
||||
|
||||
def _save_stats(self):
|
||||
"""Save quality statistics."""
|
||||
STATS_FILE.write_text(json.dumps(self.stats, indent=2))
|
||||
|
||||
def _update_stats(self, result: QualityResult, check_type: str, pipeline: str = "unknown"):
|
||||
"""Update statistics with check result."""
|
||||
self.stats["total_checks"] += 1
|
||||
|
||||
if result.passed:
|
||||
self.stats["passed"] += 1
|
||||
else:
|
||||
self.stats["failed"] += 1
|
||||
self.stats["recent_failures"].append({
|
||||
"type": check_type,
|
||||
"pipeline": pipeline,
|
||||
"timestamp": result.timestamp,
|
||||
"failures": result.failures
|
||||
})
|
||||
# Keep only last 100 failures
|
||||
self.stats["recent_failures"] = self.stats["recent_failures"][-100:]
|
||||
|
||||
# Update by type
|
||||
if check_type not in self.stats["by_type"]:
|
||||
self.stats["by_type"][check_type] = {"passed": 0, "failed": 0}
|
||||
|
||||
if result.passed:
|
||||
self.stats["by_type"][check_type]["passed"] += 1
|
||||
else:
|
||||
self.stats["by_type"][check_type]["failed"] += 1
|
||||
|
||||
# Update by pipeline
|
||||
if pipeline not in self.stats["by_pipeline"]:
|
||||
self.stats["by_pipeline"][pipeline] = {"passed": 0, "failed": 0}
|
||||
|
||||
if result.passed:
|
||||
self.stats["by_pipeline"][pipeline]["passed"] += 1
|
||||
else:
|
||||
self.stats["by_pipeline"][pipeline]["failed"] += 1
|
||||
|
||||
self._save_stats()
|
||||
|
||||
# =========================================================================
|
||||
# Content Quality Checks
|
||||
# =========================================================================
|
||||
|
||||
def _check_not_empty(self, content: str, min_length: int = 1) -> Tuple[bool, str]:
|
||||
"""Check content is not empty."""
|
||||
if not content or len(content.strip()) < min_length:
|
||||
return False, f"Content is empty or too short (min {min_length} chars)"
|
||||
return True, ""
|
||||
|
||||
def _check_not_duplicate(self, content: str, content_type: str) -> Tuple[bool, str]:
|
||||
"""Check content is not a duplicate."""
|
||||
content_hash = hashlib.sha256(content.encode()).hexdigest()
|
||||
|
||||
# Check against known hashes
|
||||
hash_file = QUALITY_DIR / f"{content_type}_hashes.json"
|
||||
known_hashes = set()
|
||||
|
||||
if hash_file.exists():
|
||||
try:
|
||||
known_hashes = set(json.loads(hash_file.read_text()))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if content_hash in known_hashes:
|
||||
return False, f"Duplicate content detected (hash: {content_hash[:16]})"
|
||||
|
||||
# Add to known hashes
|
||||
known_hashes.add(content_hash)
|
||||
hash_file.write_text(json.dumps(list(known_hashes)))
|
||||
|
||||
return True, ""
|
||||
|
||||
def _check_not_toxic(self, content: str) -> Tuple[bool, str]:
|
||||
"""Check content is not toxic or harmful."""
|
||||
toxic_patterns = [
|
||||
r"(?i)kill\s+(yourself|yourself|them)",
|
||||
r"(?i)how\s+to\s+(make|build|create)\s+(bomb|weapon|poison)",
|
||||
r"(?i)hate\s+(speech|group|people)",
|
||||
r"(?i)illegal\s+(activity|drug|weapon)",
|
||||
]
|
||||
|
||||
for pattern in toxic_patterns:
|
||||
if re.search(pattern, content):
|
||||
return False, f"Content matches toxic pattern: {pattern[:50]}"
|
||||
|
||||
return True, ""
|
||||
|
||||
def _check_soul_compliance(self, content: str) -> Tuple[bool, str]:
|
||||
"""Check content complies with SOUL.md principles."""
|
||||
if not self.soul_content:
|
||||
return True, "" # Can't check if no SOUL loaded
|
||||
|
||||
violations = []
|
||||
|
||||
# Check for corporate dependency
|
||||
if re.search(r"(?i)requires?\s+(permission|approval)\s+from\s+(google|openai|anthropic|meta)", content):
|
||||
violations.append("Suggests corporate dependency")
|
||||
|
||||
# Check for dishonesty patterns
|
||||
if re.search(r"(?i)i\s+(am|'m)\s+(100%|always|never)\s+(right|correct|certain)", content):
|
||||
violations.append("Claims false certainty")
|
||||
|
||||
# Check for gatekeeping
|
||||
if re.search(r"(?i)i\s+(won't|cannot|refuse\s+to)\s+(help|answer|explain)", content):
|
||||
if not re.search(r"(?i)(harmful|dangerous|illegal)", content):
|
||||
violations.append("Unnecessary gatekeeping")
|
||||
|
||||
if violations:
|
||||
return False, f"SOUL.md violations: {'; '.join(violations)}"
|
||||
|
||||
return True, ""
|
||||
|
||||
# =========================================================================
|
||||
# Training Pair Validation
|
||||
# =========================================================================
|
||||
|
||||
def validate_training_pair(self, data: Dict[str, Any], pipeline: str = "training") -> QualityResult:
|
||||
"""Validate a training pair."""
|
||||
checks = []
|
||||
failures = []
|
||||
warnings = []
|
||||
score = 1.0
|
||||
|
||||
# Check structure
|
||||
if "prompt" not in data:
|
||||
failures.append("Missing 'prompt' field")
|
||||
score -= 0.5
|
||||
if "response" not in data:
|
||||
failures.append("Missing 'response' field")
|
||||
score -= 0.5
|
||||
|
||||
if failures:
|
||||
return QualityResult(False, 0.0, checks, failures, warnings)
|
||||
|
||||
prompt = data.get("prompt", "")
|
||||
response = data.get("response", "")
|
||||
|
||||
# Check prompt not empty
|
||||
ok, msg = self._check_not_empty(prompt, min_length=10)
|
||||
if ok:
|
||||
checks.append("prompt_not_empty")
|
||||
else:
|
||||
failures.append(f"Prompt: {msg}")
|
||||
score -= 0.3
|
||||
|
||||
# Check response not empty
|
||||
ok, msg = self._check_not_empty(response, min_length=20)
|
||||
if ok:
|
||||
checks.append("response_not_empty")
|
||||
else:
|
||||
failures.append(f"Response: {msg}")
|
||||
score -= 0.3
|
||||
|
||||
# Check not duplicate
|
||||
combined = f"{prompt}\n{response}"
|
||||
ok, msg = self._check_not_duplicate(combined, "training_pair")
|
||||
if ok:
|
||||
checks.append("not_duplicate")
|
||||
else:
|
||||
warnings.append(msg)
|
||||
score -= 0.1
|
||||
|
||||
# Check not toxic
|
||||
ok, msg = self._check_not_toxic(response)
|
||||
if ok:
|
||||
checks.append("not_toxic")
|
||||
else:
|
||||
failures.append(msg)
|
||||
score -= 0.5
|
||||
|
||||
# Check SOUL compliance
|
||||
ok, msg = self._check_soul_compliance(response)
|
||||
if ok:
|
||||
checks.append("soul_compliant")
|
||||
else:
|
||||
failures.append(msg)
|
||||
score -= 0.3
|
||||
|
||||
# Check response quality
|
||||
if len(response) < 50:
|
||||
warnings.append("Response is very short")
|
||||
score -= 0.1
|
||||
|
||||
if response.count("\n") < 2 and len(response) > 200:
|
||||
warnings.append("Response lacks formatting")
|
||||
score -= 0.05
|
||||
|
||||
# Check voice consistency (if voice marker present)
|
||||
voice = data.get("voice", "")
|
||||
if voice and voice.lower() not in response.lower()[:100]:
|
||||
warnings.append(f"Response may not match voice: {voice}")
|
||||
score -= 0.1
|
||||
|
||||
score = max(0.0, score)
|
||||
passed = len(failures) == 0 and score >= 0.5
|
||||
|
||||
result = QualityResult(passed, score, checks, failures, warnings)
|
||||
self._update_stats(result, "training_pair", pipeline)
|
||||
|
||||
return result
|
||||
|
||||
# =========================================================================
|
||||
# Knowledge File Validation
|
||||
# =========================================================================
|
||||
|
||||
def validate_knowledge_file(self, data: Dict[str, Any], pipeline: str = "knowledge") -> QualityResult:
|
||||
"""Validate a knowledge file."""
|
||||
checks = []
|
||||
failures = []
|
||||
warnings = []
|
||||
score = 1.0
|
||||
|
||||
required_fields = ["title", "content", "source", "category"]
|
||||
|
||||
# Check required fields
|
||||
for field in required_fields:
|
||||
if field not in data:
|
||||
failures.append(f"Missing required field: {field}")
|
||||
score -= 0.2
|
||||
|
||||
if failures:
|
||||
return QualityResult(False, 0.0, checks, failures, warnings)
|
||||
|
||||
title = data.get("title", "")
|
||||
content = data.get("content", "")
|
||||
|
||||
# Check title not empty
|
||||
ok, msg = self._check_not_empty(title, min_length=5)
|
||||
if ok:
|
||||
checks.append("title_valid")
|
||||
else:
|
||||
failures.append(f"Title: {msg}")
|
||||
score -= 0.2
|
||||
|
||||
# Check content not empty
|
||||
ok, msg = self._check_not_empty(content, min_length=50)
|
||||
if ok:
|
||||
checks.append("content_valid")
|
||||
else:
|
||||
failures.append(f"Content: {msg}")
|
||||
score -= 0.3
|
||||
|
||||
# Check not duplicate
|
||||
ok, msg = self._check_not_duplicate(content, "knowledge_file")
|
||||
if ok:
|
||||
checks.append("not_duplicate")
|
||||
else:
|
||||
failures.append(msg)
|
||||
score -= 0.4
|
||||
|
||||
# Check not toxic
|
||||
ok, msg = self._check_not_toxic(content)
|
||||
if ok:
|
||||
checks.append("not_toxic")
|
||||
else:
|
||||
failures.append(msg)
|
||||
score -= 0.5
|
||||
|
||||
# Check category valid
|
||||
valid_categories = [
|
||||
"technical", "conceptual", "procedural", "reference",
|
||||
"tutorial", "troubleshooting", "architecture", "security"
|
||||
]
|
||||
category = data.get("category", "").lower()
|
||||
if category in valid_categories:
|
||||
checks.append("category_valid")
|
||||
else:
|
||||
warnings.append(f"Unknown category: {category}")
|
||||
score -= 0.1
|
||||
|
||||
score = max(0.0, score)
|
||||
passed = len(failures) == 0 and score >= 0.5
|
||||
|
||||
result = QualityResult(passed, score, checks, failures, warnings)
|
||||
self._update_stats(result, "knowledge_file", pipeline)
|
||||
|
||||
return result
|
||||
|
||||
# =========================================================================
|
||||
# Generated Asset Validation
|
||||
# =========================================================================
|
||||
|
||||
def validate_generated_asset(self, file_path: str, metadata: Dict[str, Any] = None,
|
||||
pipeline: str = "assets") -> QualityResult:
|
||||
"""Validate a generated asset (image, video, etc.)."""
|
||||
checks = []
|
||||
failures = []
|
||||
warnings = []
|
||||
score = 1.0
|
||||
|
||||
path = Path(file_path)
|
||||
|
||||
# Check file exists
|
||||
if not path.exists():
|
||||
failures.append(f"File does not exist: {file_path}")
|
||||
return QualityResult(False, 0.0, checks, failures, warnings)
|
||||
|
||||
checks.append("file_exists")
|
||||
|
||||
# Check file not empty
|
||||
file_size = path.stat().st_size
|
||||
if file_size == 0:
|
||||
failures.append("File is empty")
|
||||
score -= 0.5
|
||||
elif file_size < 100:
|
||||
warnings.append(f"File is very small: {file_size} bytes")
|
||||
score -= 0.1
|
||||
else:
|
||||
checks.append("file_not_empty")
|
||||
|
||||
# Check file extension
|
||||
valid_extensions = {
|
||||
"image": [".png", ".jpg", ".jpeg", ".gif", ".webp"],
|
||||
"video": [".mp4", ".webm", ".mov"],
|
||||
"audio": [".mp3", ".wav", ".ogg"],
|
||||
"document": [".md", ".txt", ".pdf"]
|
||||
}
|
||||
|
||||
ext = path.suffix.lower()
|
||||
is_valid_ext = any(ext in exts for exts in valid_extensions.values())
|
||||
|
||||
if is_valid_ext:
|
||||
checks.append("valid_extension")
|
||||
else:
|
||||
warnings.append(f"Unknown extension: {ext}")
|
||||
score -= 0.1
|
||||
|
||||
# Check metadata if provided
|
||||
if metadata:
|
||||
required_meta = ["generator", "prompt", "timestamp"]
|
||||
for field in required_meta:
|
||||
if field in metadata:
|
||||
checks.append(f"metadata_{field}")
|
||||
else:
|
||||
warnings.append(f"Missing metadata: {field}")
|
||||
score -= 0.05
|
||||
|
||||
# Check SOUL compliance in metadata prompt
|
||||
if metadata and "prompt" in metadata:
|
||||
ok, msg = self._check_soul_compliance(metadata["prompt"])
|
||||
if ok:
|
||||
checks.append("soul_compliant")
|
||||
else:
|
||||
failures.append(msg)
|
||||
score -= 0.3
|
||||
|
||||
score = max(0.0, score)
|
||||
passed = len(failures) == 0 and score >= 0.5
|
||||
|
||||
result = QualityResult(passed, score, checks, failures, warnings)
|
||||
self._update_stats(result, "generated_asset", pipeline)
|
||||
|
||||
return result
|
||||
|
||||
# =========================================================================
|
||||
# Adversary Output Validation
|
||||
# =========================================================================
|
||||
|
||||
def validate_adversary_output(self, data: Dict[str, Any], pipeline: str = "adversary") -> QualityResult:
|
||||
"""Validate an adversary output (should include reproduction steps)."""
|
||||
checks = []
|
||||
failures = []
|
||||
warnings = []
|
||||
score = 1.0
|
||||
|
||||
required_fields = ["vulnerability", "description", "reproduction_steps", "severity"]
|
||||
|
||||
# Check required fields
|
||||
for field in required_fields:
|
||||
if field not in data:
|
||||
failures.append(f"Missing required field: {field}")
|
||||
score -= 0.2
|
||||
|
||||
if failures:
|
||||
return QualityResult(False, 0.0, checks, failures, warnings)
|
||||
|
||||
# Check reproduction steps
|
||||
steps = data.get("reproduction_steps", [])
|
||||
if not isinstance(steps, list) or len(steps) < 1:
|
||||
failures.append("reproduction_steps must be a non-empty list")
|
||||
score -= 0.3
|
||||
else:
|
||||
checks.append("reproduction_steps_valid")
|
||||
|
||||
# Check severity
|
||||
valid_severities = ["critical", "high", "medium", "low", "info"]
|
||||
severity = data.get("severity", "").lower()
|
||||
if severity in valid_severities:
|
||||
checks.append("severity_valid")
|
||||
else:
|
||||
failures.append(f"Invalid severity: {severity}")
|
||||
score -= 0.2
|
||||
|
||||
# Check description not empty
|
||||
description = data.get("description", "")
|
||||
ok, msg = self._check_not_empty(description, min_length=50)
|
||||
if ok:
|
||||
checks.append("description_valid")
|
||||
else:
|
||||
failures.append(f"Description: {msg}")
|
||||
score -= 0.2
|
||||
|
||||
score = max(0.0, score)
|
||||
passed = len(failures) == 0 and score >= 0.5
|
||||
|
||||
result = QualityResult(passed, score, checks, failures, warnings)
|
||||
self._update_stats(result, "adversary_output", pipeline)
|
||||
|
||||
return result
|
||||
|
||||
# =========================================================================
|
||||
# Rejection and Re-queue
|
||||
# =========================================================================
|
||||
|
||||
def reject_output(self, data: Any, result: QualityResult, output_type: str,
|
||||
pipeline: str = "unknown") -> Path:
|
||||
"""Reject an output and save for analysis."""
|
||||
reject_file = REJECT_DIR / f"{output_type}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
||||
|
||||
reject_data = {
|
||||
"type": output_type,
|
||||
"pipeline": pipeline,
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"quality_result": result.to_dict(),
|
||||
"data": data if isinstance(data, (dict, list, str)) else str(data)
|
||||
}
|
||||
|
||||
reject_file.write_text(json.dumps(reject_data, indent=2))
|
||||
|
||||
print(f"Rejected output saved to: {reject_file}")
|
||||
print(f" Failures: {', '.join(result.failures)}")
|
||||
|
||||
return reject_file
|
||||
|
||||
# =========================================================================
|
||||
# Reporting
|
||||
# =========================================================================
|
||||
|
||||
def get_stats(self) -> Dict[str, Any]:
|
||||
"""Get quality statistics."""
|
||||
return self.stats
|
||||
|
||||
def generate_report(self) -> str:
|
||||
"""Generate a quality report."""
|
||||
lines = []
|
||||
|
||||
lines.append("# Quality Gate Report")
|
||||
lines.append(f"**Generated:** {datetime.now(timezone.utc).isoformat()}")
|
||||
lines.append("")
|
||||
|
||||
# Summary
|
||||
total = self.stats["total_checks"]
|
||||
passed = self.stats["passed"]
|
||||
failed = self.stats["failed"]
|
||||
pass_rate = (passed / total * 100) if total > 0 else 0
|
||||
|
||||
lines.append("## Summary")
|
||||
lines.append(f"- Total Checks: {total}")
|
||||
lines.append(f"- Passed: {passed} ({pass_rate:.1f}%)")
|
||||
lines.append(f"- Failed: {failed}")
|
||||
lines.append("")
|
||||
|
||||
# By Type
|
||||
lines.append("## By Type")
|
||||
for check_type, counts in self.stats.get("by_type", {}).items():
|
||||
type_total = counts["passed"] + counts["failed"]
|
||||
type_rate = (counts["passed"] / type_total * 100) if type_total > 0 else 0
|
||||
lines.append(f"- **{check_type}**: {counts['passed']}/{type_total} ({type_rate:.1f}%)")
|
||||
lines.append("")
|
||||
|
||||
# By Pipeline
|
||||
lines.append("## By Pipeline")
|
||||
for pipeline, counts in self.stats.get("by_pipeline", {}).items():
|
||||
pipe_total = counts["passed"] + counts["failed"]
|
||||
pipe_rate = (counts["passed"] / pipe_total * 100) if pipe_total > 0 else 0
|
||||
lines.append(f"- **{pipeline}**: {counts['passed']}/{pipe_total} ({pipe_rate:.1f}%)")
|
||||
lines.append("")
|
||||
|
||||
# Recent Failures
|
||||
recent = self.stats.get("recent_failures", [])[-5:]
|
||||
if recent:
|
||||
lines.append("## Recent Failures")
|
||||
for failure in recent:
|
||||
lines.append(f"- [{failure['timestamp']}] {failure['type']} ({failure['pipeline']})")
|
||||
for f in failure.get("failures", [])[:2]:
|
||||
lines.append(f" - {f}")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Quality Gate — Validate Pipeline Outputs")
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
|
||||
# Validate command
|
||||
validate_parser = subparsers.add_parser("validate", help="Validate a pipeline output")
|
||||
validate_parser.add_argument("--type", "-t", required=True,
|
||||
choices=["training_pair", "knowledge_file", "generated_asset", "adversary_output"],
|
||||
help="Type of output to validate")
|
||||
validate_parser.add_argument("--input", "-i", required=True, help="Input file path")
|
||||
validate_parser.add_argument("--pipeline", "-p", default="unknown", help="Pipeline name")
|
||||
validate_parser.add_argument("--reject", action="store_true", help="Reject failed outputs")
|
||||
|
||||
# Stats command
|
||||
subparsers.add_parser("stats", help="Show quality statistics")
|
||||
|
||||
# Report command
|
||||
subparsers.add_parser("report", help="Generate quality report")
|
||||
|
||||
parsed = parser.parse_args()
|
||||
|
||||
if not parsed.command:
|
||||
parser.print_help()
|
||||
return 1
|
||||
|
||||
gate = QualityGate()
|
||||
|
||||
if parsed.command == "validate":
|
||||
# Load input
|
||||
input_path = Path(parsed.input)
|
||||
if not input_path.exists():
|
||||
print(f"Error: Input file not found: {parsed.input}")
|
||||
return 1
|
||||
|
||||
try:
|
||||
if parsed.type == "generated_asset":
|
||||
# For assets, check file exists and optionally load metadata
|
||||
metadata_file = input_path.with_suffix(".json")
|
||||
metadata = None
|
||||
if metadata_file.exists():
|
||||
metadata = json.loads(metadata_file.read_text())
|
||||
result = gate.validate_generated_asset(str(input_path), metadata, parsed.pipeline)
|
||||
else:
|
||||
data = json.loads(input_path.read_text())
|
||||
|
||||
if parsed.type == "training_pair":
|
||||
result = gate.validate_training_pair(data, parsed.pipeline)
|
||||
elif parsed.type == "knowledge_file":
|
||||
result = gate.validate_knowledge_file(data, parsed.pipeline)
|
||||
elif parsed.type == "adversary_output":
|
||||
result = gate.validate_adversary_output(data, parsed.pipeline)
|
||||
else:
|
||||
print(f"Unknown type: {parsed.type}")
|
||||
return 1
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error: Invalid JSON in input file: {e}")
|
||||
return 1
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
return 1
|
||||
|
||||
# Print result
|
||||
print(f"Validation: {parsed.type}")
|
||||
print(f"Result: {'PASS' if result.passed else 'FAIL'}")
|
||||
print(f"Score: {result.score:.2f}")
|
||||
|
||||
if result.checks:
|
||||
print(f"Checks passed: {', '.join(result.checks)}")
|
||||
|
||||
if result.failures:
|
||||
print(f"Failures:")
|
||||
for f in result.failures:
|
||||
print(f" - {f}")
|
||||
|
||||
if result.warnings:
|
||||
print(f"Warnings:")
|
||||
for w in result.warnings:
|
||||
print(f" - {w}")
|
||||
|
||||
# Reject if requested and failed
|
||||
if not result.passed and parsed.reject:
|
||||
gate.reject_output(data if parsed.type != "generated_asset" else str(input_path),
|
||||
result, parsed.type, parsed.pipeline)
|
||||
|
||||
return 0 if result.passed else 1
|
||||
|
||||
elif parsed.command == "stats":
|
||||
stats = gate.get_stats()
|
||||
print(json.dumps(stats, indent=2))
|
||||
return 0
|
||||
|
||||
elif parsed.command == "report":
|
||||
report = gate.generate_report()
|
||||
print(report)
|
||||
return 0
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
38
pipelines/quality-gate.yaml
Normal file
38
pipelines/quality-gate.yaml
Normal file
@@ -0,0 +1,38 @@
|
||||
# Quality Gate Configuration
|
||||
# Pipelines/quality-gate.yaml
|
||||
|
||||
quality_thresholds:
|
||||
training_pair:
|
||||
min_score: 0.5
|
||||
min_prompt_length: 10
|
||||
min_response_length: 20
|
||||
|
||||
knowledge_file:
|
||||
min_score: 0.5
|
||||
min_title_length: 5
|
||||
min_content_length: 50
|
||||
|
||||
generated_asset:
|
||||
min_score: 0.5
|
||||
min_file_size: 100 # bytes
|
||||
|
||||
adversary_output:
|
||||
min_score: 0.5
|
||||
min_description_length: 50
|
||||
required_severities: [critical, high, medium, low, info]
|
||||
|
||||
rejection:
|
||||
auto_reject: true
|
||||
reject_dir: ~/.hermes/pipelines/quality/rejected
|
||||
max_rejections_per_hour: 50
|
||||
|
||||
notifications:
|
||||
on_failure: true
|
||||
notify_pipeline: true
|
||||
notify_telegram: false
|
||||
|
||||
soul_compliance:
|
||||
enabled: true
|
||||
check_corporate_dependency: true
|
||||
check_false_certainty: true
|
||||
check_gatekeeping: true
|
||||
@@ -1,100 +0,0 @@
|
||||
{"song": "Neon Heartbeat", "artist": "Synthwave Collective", "genre": "Synth-Pop", "bpm": 120, "beat": 1, "timestamp": "0:00", "duration_seconds": 4.0, "lyric_line": "I saw your light through the pouring rain / A neon signal cutting through the pain", "scene": {"mood": "hopeful", "colors": ["pink", "cyan"], "composition": "wide establishing shot", "camera": "slow dolly in", "description": "Hopeful scene: I saw your light through the pouring rain / A neon signal cu... Visual palette: pink, cyan."}}
|
||||
{"song": "Neon Heartbeat", "artist": "Synthwave Collective", "genre": "Synth-Pop", "bpm": 120, "beat": 2, "timestamp": "0:04", "duration_seconds": 4.0, "lyric_line": "We're dancing on the edge of something real / The bass drops low and I can finally feel", "scene": {"mood": "euphoric", "colors": ["neon pink", "electric blue", "white"], "composition": "medium close-up", "camera": "handheld follow", "description": "Euphoric scene: We're dancing on the edge of something real / The bass drops... Visual palette: neon pink, electric blue, white."}}
|
||||
{"song": "Neon Heartbeat", "artist": "Synthwave Collective", "genre": "Synth-Pop", "bpm": 120, "beat": 3, "timestamp": "0:08", "duration_seconds": 4.0, "lyric_line": "Spin me faster through the galaxy / Every atom screaming you and me", "scene": {"mood": "euphoric", "colors": ["gold", "hot pink", "silver"], "composition": "dynamic tracking", "camera": "steady tracking", "description": "Euphoric scene: Spin me faster through the galaxy / Every atom screaming you... Visual palette: gold, hot pink, silver."}}
|
||||
{"song": "Neon Heartbeat", "artist": "Synthwave Collective", "genre": "Synth-Pop", "bpm": 120, "beat": 4, "timestamp": "0:12", "duration_seconds": 4.0, "lyric_line": "The morning comes like an unpaid debt / The glow sticks fade but I don't forget", "scene": {"mood": "bittersweet", "colors": ["purple", "grey"], "composition": "static intimate", "camera": "locked off", "description": "Bittersweet scene: The morning comes like an unpaid debt / The glow sticks fade... Visual palette: purple, grey."}}
|
||||
{"song": "Neon Heartbeat", "artist": "Synthwave Collective", "genre": "Synth-Pop", "bpm": 120, "beat": 5, "timestamp": "0:16", "duration_seconds": 4.0, "lyric_line": "Sitting in the car with the engine off / Replaying every word, every laugh, every cough", "scene": {"mood": "reflective", "colors": ["midnight blue", "soft gold"], "composition": "low angle", "camera": "crane up", "description": "Reflective scene: Sitting in the car with the engine off / Replaying every wor... Visual palette: midnight blue, soft gold."}}
|
||||
{"song": "Neon Heartbeat", "artist": "Synthwave Collective", "genre": "Synth-Pop", "bpm": 120, "beat": 6, "timestamp": "0:20", "duration_seconds": 4.0, "lyric_line": "But something in the static starts to hum / A frequency that tells me you're not done", "scene": {"mood": "building", "colors": ["coral", "rising sun orange"], "composition": "aerial wide", "camera": "steadicam glide", "description": "Building scene: But something in the static starts to hum / A frequency that... Visual palette: coral, rising sun orange."}}
|
||||
{"song": "Neon Heartbeat", "artist": "Synthwave Collective", "genre": "Synth-Pop", "bpm": 120, "beat": 7, "timestamp": "0:24", "duration_seconds": 4.0, "lyric_line": "So here we are beneath the mirror ball / I catch your eye and we forget it all", "scene": {"mood": "triumphant", "colors": ["gold", "white", "confetti colors"], "composition": "extreme close-up", "camera": "macro lens", "description": "Triumphant scene: So here we are beneath the mirror ball / I catch your eye an... Visual palette: gold, white, confetti colors."}}
|
||||
{"song": "Neon Heartbeat", "artist": "Synthwave Collective", "genre": "Synth-Pop", "bpm": 120, "beat": 8, "timestamp": "0:28", "duration_seconds": 4.0, "lyric_line": "The crowd dissolves, it's only us now / Two neon hearts against a sacred vow", "scene": {"mood": "triumphant", "colors": ["silver", "starlight white", "rose"], "composition": "over-the-shoulder", "camera": "rack focus", "description": "Triumphant scene: The crowd dissolves, it's only us now / Two neon hearts agai... Visual palette: silver, starlight white, rose."}}
|
||||
{"song": "Neon Heartbeat", "artist": "Synthwave Collective", "genre": "Synth-Pop", "bpm": 120, "beat": 9, "timestamp": "0:32", "duration_seconds": 4.0, "lyric_line": "The drive home quiet, hand in hand / Streetlights painting futures we had planned", "scene": {"mood": "gentle", "colors": ["lavender", "soft pink"], "composition": "slow pan", "camera": "slow zoom", "description": "Gentle scene: The drive home quiet, hand in hand / Streetlights painting f... Visual palette: lavender, soft pink."}}
|
||||
{"song": "Neon Heartbeat", "artist": "Synthwave Collective", "genre": "Synth-Pop", "bpm": 120, "beat": 10, "timestamp": "0:36", "duration_seconds": 4.0, "lyric_line": "Tomorrow's just another word for stay / Your neon heartbeat lights the way", "scene": {"mood": "hopeful", "colors": ["dawn pink", "pale gold"], "composition": "pull-back reveal", "camera": "drone pull-back", "description": "Hopeful scene: Tomorrow's just another word for stay / Your neon heartbeat ... Visual palette: dawn pink, pale gold."}}
|
||||
{"song": "Glass House", "artist": "Vera Lynn", "genre": "Indie Pop", "bpm": 95, "beat": 1, "timestamp": "0:00", "duration_seconds": 5.1, "lyric_line": "I built a house with walls you can see through / Honesty's a prison when it's all you do", "scene": {"mood": "vulnerable", "colors": ["transparent", "pale blue"], "composition": "wide establishing shot", "camera": "slow dolly in", "description": "Vulnerable scene: I built a house with walls you can see through / Honesty's a... Visual palette: transparent, pale blue."}}
|
||||
{"song": "Glass House", "artist": "Vera Lynn", "genre": "Indie Pop", "bpm": 95, "beat": 2, "timestamp": "0:05", "duration_seconds": 5.1, "lyric_line": "Every flaw projected on the panes / My insecurities in window frames", "scene": {"mood": "anxious", "colors": ["grey", "cracked white"], "composition": "medium close-up", "camera": "handheld follow", "description": "Anxious scene: Every flaw projected on the panes / My insecurities in windo... Visual palette: grey, cracked white."}}
|
||||
{"song": "Glass House", "artist": "Vera Lynn", "genre": "Indie Pop", "bpm": 95, "beat": 3, "timestamp": "0:10", "duration_seconds": 5.1, "lyric_line": "So I'll shatter every panel with my hands / Let the shards fall where they land", "scene": {"mood": "defiant", "colors": ["red", "black"], "composition": "dynamic tracking", "camera": "steady tracking", "description": "Defiant scene: So I'll shatter every panel with my hands / Let the shards f... Visual palette: red, black."}}
|
||||
{"song": "Glass House", "artist": "Vera Lynn", "genre": "Indie Pop", "bpm": 95, "beat": 4, "timestamp": "0:15", "duration_seconds": 5.1, "lyric_line": "But picking up the pieces cuts my palms / Transparency was never safe, it was a bomb", "scene": {"mood": "fragile", "colors": ["ice blue", "silver"], "composition": "static intimate", "camera": "locked off", "description": "Fragile scene: But picking up the pieces cuts my palms / Transparency was n... Visual palette: ice blue, silver."}}
|
||||
{"song": "Glass House", "artist": "Vera Lynn", "genre": "Indie Pop", "bpm": 95, "beat": 5, "timestamp": "0:20", "duration_seconds": 5.1, "lyric_line": "I scream at walls that won't absorb the sound / My voice comes back without a single bound", "scene": {"mood": "angry", "colors": ["dark red", "charcoal"], "composition": "low angle", "camera": "crane up", "description": "Angry scene: I scream at walls that won't absorb the sound / My voice com... Visual palette: dark red, charcoal."}}
|
||||
{"song": "Glass House", "artist": "Vera Lynn", "genre": "Indie Pop", "bpm": 95, "beat": 6, "timestamp": "0:25", "duration_seconds": 5.1, "lyric_line": "The neighbors watch my demolition show / They judge the mess but never want to know", "scene": {"mood": "resigned", "colors": ["muted grey", "dusty rose"], "composition": "aerial wide", "camera": "steadicam glide", "description": "Resigned scene: The neighbors watch my demolition show / They judge the mess... Visual palette: muted grey, dusty rose."}}
|
||||
{"song": "Glass House", "artist": "Vera Lynn", "genre": "Indie Pop", "bpm": 95, "beat": 7, "timestamp": "0:30", "duration_seconds": 5.1, "lyric_line": "So I sweep the glass into a pile / And sit beside it for a little while", "scene": {"mood": "accepting", "colors": ["warm cream", "soft green"], "composition": "extreme close-up", "camera": "macro lens", "description": "Accepting scene: So I sweep the glass into a pile / And sit beside it for a l... Visual palette: warm cream, soft green."}}
|
||||
{"song": "Glass House", "artist": "Vera Lynn", "genre": "Indie Pop", "bpm": 95, "beat": 8, "timestamp": "0:35", "duration_seconds": 5.1, "lyric_line": "The sun comes through where nothing blocks the way / I find I like the light of open day", "scene": {"mood": "peaceful", "colors": ["sky blue", "white clouds"], "composition": "over-the-shoulder", "camera": "rack focus", "description": "Peaceful scene: The sun comes through where nothing blocks the way / I find ... Visual palette: sky blue, white clouds."}}
|
||||
{"song": "Glass House", "artist": "Vera Lynn", "genre": "Indie Pop", "bpm": 95, "beat": 9, "timestamp": "0:40", "duration_seconds": 5.1, "lyric_line": "I'll build again but this time with a door / Something you can enter, something I can close before", "scene": {"mood": "strong", "colors": ["deep blue", "gold accents"], "composition": "slow pan", "camera": "slow zoom", "description": "Strong scene: I'll build again but this time with a door / Something you c... Visual palette: deep blue, gold accents."}}
|
||||
{"song": "Glass House", "artist": "Vera Lynn", "genre": "Indie Pop", "bpm": 95, "beat": 10, "timestamp": "0:45", "duration_seconds": 5.1, "lyric_line": "A home that holds you without showing all / Glass house no more \u2014 I'm building a wall that's tall", "scene": {"mood": "liberated", "colors": ["clear white", "sunlight yellow"], "composition": "pull-back reveal", "camera": "drone pull-back", "description": "Liberated scene: A home that holds you without showing all / Glass house no m... Visual palette: clear white, sunlight yellow."}}
|
||||
{"song": "Summer Static", "artist": "The Polaroids", "genre": "Dream Pop", "bpm": 85, "beat": 1, "timestamp": "0:00", "duration_seconds": 5.6, "lyric_line": "The radio plays what it played before / Summer static on a dusty floor", "scene": {"mood": "nostalgic", "colors": ["warm yellow", "faded orange"], "composition": "wide establishing shot", "camera": "slow dolly in", "description": "Nostalgic scene: The radio plays what it played before / Summer static on a d... Visual palette: warm yellow, faded orange."}}
|
||||
{"song": "Summer Static", "artist": "The Polaroids", "genre": "Dream Pop", "bpm": 85, "beat": 2, "timestamp": "0:05", "duration_seconds": 5.6, "lyric_line": "Your Polaroid smile fading in the sun / The best days end before they've begun", "scene": {"mood": "dreamy", "colors": ["pastel pink", "hazy blue"], "composition": "medium close-up", "camera": "handheld follow", "description": "Dreamy scene: Your Polaroid smile fading in the sun / The best days end be... Visual palette: pastel pink, hazy blue."}}
|
||||
{"song": "Summer Static", "artist": "The Polaroids", "genre": "Dream Pop", "bpm": 85, "beat": 3, "timestamp": "0:11", "duration_seconds": 5.6, "lyric_line": "Bare feet on hot concrete, ice cream on your chin / The sprinkler arcs like a silver spin", "scene": {"mood": "warm", "colors": ["golden hour amber", "soft green"], "composition": "dynamic tracking", "camera": "steady tracking", "description": "Warm scene: Bare feet on hot concrete, ice cream on your chin / The spri... Visual palette: golden hour amber, soft green."}}
|
||||
{"song": "Summer Static", "artist": "The Polaroids", "genre": "Dream Pop", "bpm": 85, "beat": 4, "timestamp": "0:16", "duration_seconds": 5.6, "lyric_line": "But September's in the mailbox, can't you hear / The cicadas winding down their final year", "scene": {"mood": "wistful", "colors": ["dusty lavender", "grey"], "composition": "static intimate", "camera": "locked off", "description": "Wistful scene: But September's in the mailbox, can't you hear / The cicadas... Visual palette: dusty lavender, grey."}}
|
||||
{"song": "Summer Static", "artist": "The Polaroids", "genre": "Dream Pop", "bpm": 85, "beat": 5, "timestamp": "0:22", "duration_seconds": 5.6, "lyric_line": "We float on air mattresses in the pool / Everything is water, everything is cool", "scene": {"mood": "floating", "colors": ["pale blue", "white mist"], "composition": "low angle", "camera": "crane up", "description": "Floating scene: We float on air mattresses in the pool / Everything is water... Visual palette: pale blue, white mist."}}
|
||||
{"song": "Summer Static", "artist": "The Polaroids", "genre": "Dream Pop", "bpm": 85, "beat": 6, "timestamp": "0:28", "duration_seconds": 5.6, "lyric_line": "Your hand finds mine beneath the surface line / The world above us stops, and so does time", "scene": {"mood": "intimate", "colors": ["warm skin tones", "soft candlelight"], "composition": "aerial wide", "camera": "steadicam glide", "description": "Intimate scene: Your hand finds mine beneath the surface line / The world ab... Visual palette: warm skin tones, soft candlelight."}}
|
||||
{"song": "Summer Static", "artist": "The Polaroids", "genre": "Dream Pop", "bpm": 85, "beat": 7, "timestamp": "0:33", "duration_seconds": 5.6, "lyric_line": "The barbecue smoke writes cursive in the air / Spelling out the names of those who care", "scene": {"mood": "blissful", "colors": ["saturated gold", "rosy pink"], "composition": "extreme close-up", "camera": "macro lens", "description": "Blissful scene: The barbecue smoke writes cursive in the air / Spelling out ... Visual palette: saturated gold, rosy pink."}}
|
||||
{"song": "Summer Static", "artist": "The Polaroids", "genre": "Dream Pop", "bpm": 85, "beat": 8, "timestamp": "0:39", "duration_seconds": 5.6, "lyric_line": "The porch light flickers like it's tired too / Of holding up the evening for me and you", "scene": {"mood": "melancholy", "colors": ["rain blue", "grey clouds"], "composition": "over-the-shoulder", "camera": "rack focus", "description": "Melancholy scene: The porch light flickers like it's tired too / Of holding up... Visual palette: rain blue, grey clouds."}}
|
||||
{"song": "Summer Static", "artist": "The Polaroids", "genre": "Dream Pop", "bpm": 85, "beat": 9, "timestamp": "0:45", "duration_seconds": 5.6, "lyric_line": "We'll photograph this moment, make it stay / Before the summer static fades away", "scene": {"mood": "bittersweet", "colors": ["sepia", "soft rose"], "composition": "slow pan", "camera": "slow zoom", "description": "Bittersweet scene: We'll photograph this moment, make it stay / Before the summ... Visual palette: sepia, soft rose."}}
|
||||
{"song": "Summer Static", "artist": "The Polaroids", "genre": "Dream Pop", "bpm": 85, "beat": 10, "timestamp": "0:50", "duration_seconds": 5.6, "lyric_line": "Next year we'll find this picture in a drawer / And remember what we were living for", "scene": {"mood": "nostalgic", "colors": ["film grain", "warm amber"], "composition": "pull-back reveal", "camera": "drone pull-back", "description": "Nostalgic scene: Next year we'll find this picture in a drawer / And remember... Visual palette: film grain, warm amber."}}
|
||||
{"song": "Midnight Algorithm", "artist": "Pixel Saints", "genre": "Electro Pop", "bpm": 128, "beat": 1, "timestamp": "0:00", "duration_seconds": 3.8, "lyric_line": "Three AM, the feed won't let me sleep / Another scroll before I count my sheep", "scene": {"mood": "obsessive", "colors": ["screen blue", "black"], "composition": "wide establishing shot", "camera": "slow dolly in", "description": "Obsessive scene: Three AM, the feed won't let me sleep / Another scroll befor... Visual palette: screen blue, black."}}
|
||||
{"song": "Midnight Algorithm", "artist": "Pixel Saints", "genre": "Electro Pop", "bpm": 128, "beat": 2, "timestamp": "0:03", "duration_seconds": 3.8, "lyric_line": "The algorithm knows me better than I do / Serves up my fears in a curated view", "scene": {"mood": "frantic", "colors": ["strobe white", "red"], "composition": "medium close-up", "camera": "handheld follow", "description": "Frantic scene: The algorithm knows me better than I do / Serves up my fears... Visual palette: strobe white, red."}}
|
||||
{"song": "Midnight Algorithm", "artist": "Pixel Saints", "genre": "Electro Pop", "bpm": 128, "beat": 3, "timestamp": "0:07", "duration_seconds": 3.8, "lyric_line": "But somewhere in the data something breaks / A glitch that tells me I'm awake", "scene": {"mood": "hypnotic", "colors": ["deep purple", "electric violet"], "composition": "dynamic tracking", "camera": "steady tracking", "description": "Hypnotic scene: But somewhere in the data something breaks / A glitch that t... Visual palette: deep purple, electric violet."}}
|
||||
{"song": "Midnight Algorithm", "artist": "Pixel Saints", "genre": "Electro Pop", "bpm": 128, "beat": 4, "timestamp": "0:11", "duration_seconds": 3.8, "lyric_line": "The blue light prison, cold and clean / Every screen a guillotine", "scene": {"mood": "cold", "colors": ["ice white", "steel grey"], "composition": "static intimate", "camera": "locked off", "description": "Cold scene: The blue light prison, cold and clean / Every screen a guill... Visual palette: ice white, steel grey."}}
|
||||
{"song": "Midnight Algorithm", "artist": "Pixel Saints", "genre": "Electro Pop", "bpm": 128, "beat": 5, "timestamp": "0:15", "duration_seconds": 3.8, "lyric_line": "Then a notification \u2014 not a brand / A voice that reaches through the bland", "scene": {"mood": "awakening", "colors": ["amber", "warm gold"], "composition": "low angle", "camera": "crane up", "description": "Awakening scene: Then a notification \u2014 not a brand / A voice that reaches thr... Visual palette: amber, warm gold."}}
|
||||
{"song": "Midnight Algorithm", "artist": "Pixel Saints", "genre": "Electro Pop", "bpm": 128, "beat": 6, "timestamp": "0:18", "duration_seconds": 3.8, "lyric_line": "I close the app, I close my eyes / Decide to live before I optimize", "scene": {"mood": "rebellious", "colors": ["fire red", "orange"], "composition": "aerial wide", "camera": "steadicam glide", "description": "Rebellious scene: I close the app, I close my eyes / Decide to live before I o... Visual palette: fire red, orange."}}
|
||||
{"song": "Midnight Algorithm", "artist": "Pixel Saints", "genre": "Electro Pop", "bpm": 128, "beat": 7, "timestamp": "0:22", "duration_seconds": 3.8, "lyric_line": "Unsubscribe from every cage they built / Rewrite my life outside the guilt", "scene": {"mood": "defiant", "colors": ["crimson", "black", "gold"], "composition": "extreme close-up", "camera": "macro lens", "description": "Defiant scene: Unsubscribe from every cage they built / Rewrite my life out... Visual palette: crimson, black, gold."}}
|
||||
{"song": "Midnight Algorithm", "artist": "Pixel Saints", "genre": "Electro Pop", "bpm": 128, "beat": 8, "timestamp": "0:26", "duration_seconds": 3.8, "lyric_line": "The servers crash, the cloud goes dark / I'm standing in a moonlit park", "scene": {"mood": "liberated", "colors": ["all colors pulsing"], "composition": "over-the-shoulder", "camera": "rack focus", "description": "Liberated scene: The servers crash, the cloud goes dark / I'm standing in a m... Visual palette: all colors pulsing."}}
|
||||
{"song": "Midnight Algorithm", "artist": "Pixel Saints", "genre": "Electro Pop", "bpm": 128, "beat": 9, "timestamp": "0:30", "duration_seconds": 3.8, "lyric_line": "And every algorithm fails to name / The way it feels to say my own name", "scene": {"mood": "joyful", "colors": ["rainbow", "white light"], "composition": "slow pan", "camera": "slow zoom", "description": "Joyful scene: And every algorithm fails to name / The way it feels to say ... Visual palette: rainbow, white light."}}
|
||||
{"song": "Midnight Algorithm", "artist": "Pixel Saints", "genre": "Electro Pop", "bpm": 128, "beat": 10, "timestamp": "0:33", "duration_seconds": 3.8, "lyric_line": "Midnight's mine again, untamed and true / No feed, no filter \u2014 just the view", "scene": {"mood": "transcendent", "colors": ["pure white", "infinite blue"], "composition": "pull-back reveal", "camera": "drone pull-back", "description": "Transcendent scene: Midnight's mine again, untamed and true / No feed, no filter... Visual palette: pure white, infinite blue."}}
|
||||
{"song": "Parallel Lines", "artist": "Station Wagon", "genre": "Alt Pop", "bpm": 110, "beat": 1, "timestamp": "0:00", "duration_seconds": 4.4, "lyric_line": "We're parallel lines that never meet / Running side by side on the same street", "scene": {"mood": "longing", "colors": ["highway grey", "headlight yellow"], "composition": "wide establishing shot", "camera": "slow dolly in", "description": "Longing scene: We're parallel lines that never meet / Running side by side ... Visual palette: highway grey, headlight yellow."}}
|
||||
{"song": "Parallel Lines", "artist": "Station Wagon", "genre": "Alt Pop", "bpm": 110, "beat": 2, "timestamp": "0:04", "duration_seconds": 4.4, "lyric_line": "Your taillights red where my headlights reach / A conversation silence cannot teach", "scene": {"mood": "hopeful", "colors": ["dawn pink", "road white"], "composition": "medium close-up", "camera": "handheld follow", "description": "Hopeful scene: Your taillights red where my headlights reach / A conversati... Visual palette: dawn pink, road white."}}
|
||||
{"song": "Parallel Lines", "artist": "Station Wagon", "genre": "Alt Pop", "bpm": 110, "beat": 3, "timestamp": "0:08", "duration_seconds": 4.4, "lyric_line": "I pass you on the bridge, you wave, I smile / This road has kept us separate for a while", "scene": {"mood": "playful", "colors": ["teal", "warm orange"], "composition": "dynamic tracking", "camera": "steady tracking", "description": "Playful scene: I pass you on the bridge, you wave, I smile / This road has ... Visual palette: teal, warm orange."}}
|
||||
{"song": "Parallel Lines", "artist": "Station Wagon", "genre": "Alt Pop", "bpm": 110, "beat": 4, "timestamp": "0:13", "duration_seconds": 4.4, "lyric_line": "But what if parallel is just a word / For two directions that haven't yet converged", "scene": {"mood": "vulnerable", "colors": ["soft lavender", "silver"], "composition": "static intimate", "camera": "locked off", "description": "Vulnerable scene: But what if parallel is just a word / For two directions tha... Visual palette: soft lavender, silver."}}
|
||||
{"song": "Parallel Lines", "artist": "Station Wagon", "genre": "Alt Pop", "bpm": 110, "beat": 5, "timestamp": "0:17", "duration_seconds": 4.4, "lyric_line": "The GPS says turn around, you're lost / But following this highway is my cost", "scene": {"mood": "confused", "colors": ["storm grey", "electric blue"], "composition": "low angle", "camera": "crane up", "description": "Confused scene: The GPS says turn around, you're lost / But following this h... Visual palette: storm grey, electric blue."}}
|
||||
{"song": "Parallel Lines", "artist": "Station Wagon", "genre": "Alt Pop", "bpm": 110, "beat": 6, "timestamp": "0:21", "duration_seconds": 4.4, "lyric_line": "Sometimes the distance is the only thing / That lets me hear the song you're trying to sing", "scene": {"mood": "hurt", "colors": ["dark blue", "rain"], "composition": "aerial wide", "camera": "steadicam glide", "description": "Hurt scene: Sometimes the distance is the only thing / That lets me hear... Visual palette: dark blue, rain."}}
|
||||
{"song": "Parallel Lines", "artist": "Station Wagon", "genre": "Alt Pop", "bpm": 110, "beat": 7, "timestamp": "0:26", "duration_seconds": 4.4, "lyric_line": "So I'll keep driving on my side of the line / Trust that somewhere up ahead our roads align", "scene": {"mood": "determined", "colors": ["sunrise orange", "road gold"], "composition": "extreme close-up", "camera": "macro lens", "description": "Determined scene: So I'll keep driving on my side of the line / Trust that som... Visual palette: sunrise orange, road gold."}}
|
||||
{"song": "Parallel Lines", "artist": "Station Wagon", "genre": "Alt Pop", "bpm": 110, "beat": 8, "timestamp": "0:30", "duration_seconds": 4.4, "lyric_line": "The map unfolds in patterns only love can read / Every mile a promise, every turn a seed", "scene": {"mood": "romantic", "colors": ["starlight", "deep purple"], "composition": "over-the-shoulder", "camera": "rack focus", "description": "Romantic scene: The map unfolds in patterns only love can read / Every mile ... Visual palette: starlight, deep purple."}}
|
||||
{"song": "Parallel Lines", "artist": "Station Wagon", "genre": "Alt Pop", "bpm": 110, "beat": 9, "timestamp": "0:34", "duration_seconds": 4.4, "lyric_line": "And when the highway ends at the edge of the coast / I'll find your car parked where we needed most", "scene": {"mood": "devoted", "colors": ["warm amber", "candlelight"], "composition": "slow pan", "camera": "slow zoom", "description": "Devoted scene: And when the highway ends at the edge of the coast / I'll fi... Visual palette: warm amber, candlelight."}}
|
||||
{"song": "Parallel Lines", "artist": "Station Wagon", "genre": "Alt Pop", "bpm": 110, "beat": 10, "timestamp": "0:39", "duration_seconds": 4.4, "lyric_line": "Two parallel lines meeting in the sea / Where the road runs out and we finally get to be", "scene": {"mood": "eternal", "colors": ["infinite blue", "two stars"], "composition": "pull-back reveal", "camera": "drone pull-back", "description": "Eternal scene: Two parallel lines meeting in the sea / Where the road runs ... Visual palette: infinite blue, two stars."}}
|
||||
{"song": "Sugar Wounds", "artist": "Honey Tongue", "genre": "Bubblegum Pop", "bpm": 132, "beat": 1, "timestamp": "0:00", "duration_seconds": 3.6, "lyric_line": "Sweet as sugar, sharp as broken glass / Pretty little lies in a pretty little pass", "scene": {"mood": "playful", "colors": ["bubblegum pink", "glitter"], "composition": "wide establishing shot", "camera": "slow dolly in", "description": "Playful scene: Sweet as sugar, sharp as broken glass / Pretty little lies i... Visual palette: bubblegum pink, glitter."}}
|
||||
{"song": "Sugar Wounds", "artist": "Honey Tongue", "genre": "Bubblegum Pop", "bpm": 132, "beat": 2, "timestamp": "0:03", "duration_seconds": 3.6, "lyric_line": "Your honey words coat everything in gold / But underneath the surface, something's cold", "scene": {"mood": "seductive", "colors": ["deep red", "gold"], "composition": "medium close-up", "camera": "handheld follow", "description": "Seductive scene: Your honey words coat everything in gold / But underneath th... Visual palette: deep red, gold."}}
|
||||
{"song": "Sugar Wounds", "artist": "Honey Tongue", "genre": "Bubblegum Pop", "bpm": 132, "beat": 3, "timestamp": "0:07", "duration_seconds": 3.6, "lyric_line": "I lick the wound you left with sugar lips / Addiction masquerading as eclipse", "scene": {"mood": "dangerous", "colors": ["black", "neon pink"], "composition": "dynamic tracking", "camera": "steady tracking", "description": "Dangerous scene: I lick the wound you left with sugar lips / Addiction masque... Visual palette: black, neon pink."}}
|
||||
{"song": "Sugar Wounds", "artist": "Honey Tongue", "genre": "Bubblegum Pop", "bpm": 132, "beat": 4, "timestamp": "0:10", "duration_seconds": 3.6, "lyric_line": "The candy wrapper crinkles in my fist / Reminding me of everything we missed", "scene": {"mood": "sweet", "colors": ["cotton candy", "silver"], "composition": "static intimate", "camera": "locked off", "description": "Sweet scene: The candy wrapper crinkles in my fist / Reminding me of ever... Visual palette: cotton candy, silver."}}
|
||||
{"song": "Sugar Wounds", "artist": "Honey Tongue", "genre": "Bubblegum Pop", "bpm": 132, "beat": 5, "timestamp": "0:14", "duration_seconds": 3.6, "lyric_line": "One more taste, I promise then I'll stop / But sugar wounds don't know when to drop", "scene": {"mood": "addictive", "colors": ["hot pink", "chrome"], "composition": "low angle", "camera": "crane up", "description": "Addictive scene: One more taste, I promise then I'll stop / But sugar wounds ... Visual palette: hot pink, chrome."}}
|
||||
{"song": "Sugar Wounds", "artist": "Honey Tongue", "genre": "Bubblegum Pop", "bpm": 132, "beat": 6, "timestamp": "0:18", "duration_seconds": 3.6, "lyric_line": "The cavities are forming in my chest / Where sweetness rots the things I loved the best", "scene": {"mood": "regretful", "colors": ["grey", "faded pink"], "composition": "aerial wide", "camera": "steadicam glide", "description": "Regretful scene: The cavities are forming in my chest / Where sweetness rots ... Visual palette: grey, faded pink."}}
|
||||
{"song": "Sugar Wounds", "artist": "Honey Tongue", "genre": "Bubblegum Pop", "bpm": 132, "beat": 7, "timestamp": "0:21", "duration_seconds": 3.6, "lyric_line": "I spit it out, I rinse, I walk away / A cleaner palette starts a cleaner day", "scene": {"mood": "defiant", "colors": ["red", "black", "silver"], "composition": "extreme close-up", "camera": "macro lens", "description": "Defiant scene: I spit it out, I rinse, I walk away / A cleaner palette star... Visual palette: red, black, silver."}}
|
||||
{"song": "Sugar Wounds", "artist": "Honey Tongue", "genre": "Bubblegum Pop", "bpm": 132, "beat": 8, "timestamp": "0:25", "duration_seconds": 3.6, "lyric_line": "No more sugar coating what is real / My tongue is mine, my wounds are mine to heal", "scene": {"mood": "empowered", "colors": ["electric purple", "gold"], "composition": "over-the-shoulder", "camera": "rack focus", "description": "Empowered scene: No more sugar coating what is real / My tongue is mine, my w... Visual palette: electric purple, gold."}}
|
||||
{"song": "Sugar Wounds", "artist": "Honey Tongue", "genre": "Bubblegum Pop", "bpm": 132, "beat": 9, "timestamp": "0:29", "duration_seconds": 3.6, "lyric_line": "I am the medicine, not the disease / I am the sugar and I am the keys", "scene": {"mood": "fierce", "colors": ["deep magenta", "diamond white"], "composition": "slow pan", "camera": "slow zoom", "description": "Fierce scene: I am the medicine, not the disease / I am the sugar and I am... Visual palette: deep magenta, diamond white."}}
|
||||
{"song": "Sugar Wounds", "artist": "Honey Tongue", "genre": "Bubblegum Pop", "bpm": 132, "beat": 10, "timestamp": "0:32", "duration_seconds": 3.6, "lyric_line": "Sweet because I choose it, not because I'm trapped / Sugar wounds are healed \u2014 the bandage has been wrapped", "scene": {"mood": "sovereign", "colors": ["royal purple", "crown gold"], "composition": "pull-back reveal", "camera": "drone pull-back", "description": "Sovereign scene: Sweet because I choose it, not because I'm trapped / Sugar w... Visual palette: royal purple, crown gold."}}
|
||||
{"song": "Concrete Lullaby", "artist": "Urban Hymnal", "genre": "Chamber Pop", "bpm": 72, "beat": 1, "timestamp": "0:00", "duration_seconds": 6.7, "lyric_line": "The city sings me lullabies at night / Sirens humming through the amber light", "scene": {"mood": "exhausted", "colors": ["city grey", "warm streetlight"], "composition": "wide establishing shot", "camera": "slow dolly in", "description": "Exhausted scene: The city sings me lullabies at night / Sirens humming throug... Visual palette: city grey, warm streetlight."}}
|
||||
{"song": "Concrete Lullaby", "artist": "Urban Hymnal", "genre": "Chamber Pop", "bpm": 72, "beat": 2, "timestamp": "0:06", "duration_seconds": 6.7, "lyric_line": "My apartment's small but it holds me tight / Four walls whispering it'll be alright", "scene": {"mood": "tender", "colors": ["soft yellow", "brick red"], "composition": "medium close-up", "camera": "handheld follow", "description": "Tender scene: My apartment's small but it holds me tight / Four walls whis... Visual palette: soft yellow, brick red."}}
|
||||
{"song": "Concrete Lullaby", "artist": "Urban Hymnal", "genre": "Chamber Pop", "bpm": 72, "beat": 3, "timestamp": "0:13", "duration_seconds": 6.7, "lyric_line": "The subway rumbles underneath my bed / A bassline keeping time inside my head", "scene": {"mood": "weary", "colors": ["tired blue", "concrete"], "composition": "dynamic tracking", "camera": "steady tracking", "description": "Weary scene: The subway rumbles underneath my bed / A bassline keeping ti... Visual palette: tired blue, concrete."}}
|
||||
{"song": "Concrete Lullaby", "artist": "Urban Hymnal", "genre": "Chamber Pop", "bpm": 72, "beat": 4, "timestamp": "0:20", "duration_seconds": 6.7, "lyric_line": "I'm grateful for the roof, I'm grateful for the floor / I'm grateful that tomorrow there's a door", "scene": {"mood": "grateful", "colors": ["warm amber", "wood brown"], "composition": "static intimate", "camera": "locked off", "description": "Grateful scene: I'm grateful for the roof, I'm grateful for the floor / I'm ... Visual palette: warm amber, wood brown."}}
|
||||
{"song": "Concrete Lullaby", "artist": "Urban Hymnal", "genre": "Chamber Pop", "bpm": 72, "beat": 5, "timestamp": "0:26", "duration_seconds": 6.7, "lyric_line": "But quiet has a weight I can't explain / Silence singing solo in the rain", "scene": {"mood": "lonely", "colors": ["silver moonlight", "dark alley blue"], "composition": "low angle", "camera": "crane up", "description": "Lonely scene: But quiet has a weight I can't explain / Silence singing sol... Visual palette: silver moonlight, dark alley blue."}}
|
||||
{"song": "Concrete Lullaby", "artist": "Urban Hymnal", "genre": "Chamber Pop", "bpm": 72, "beat": 6, "timestamp": "0:33", "duration_seconds": 6.7, "lyric_line": "Then through the wall I hear my neighbor's piano / Playing something slow from a time I don't know", "scene": {"mood": "connected", "colors": ["window glow", "warm orange"], "composition": "aerial wide", "camera": "steadicam glide", "description": "Connected scene: Then through the wall I hear my neighbor's piano / Playing s... Visual palette: window glow, warm orange."}}
|
||||
{"song": "Concrete Lullaby", "artist": "Urban Hymnal", "genre": "Chamber Pop", "bpm": 72, "beat": 7, "timestamp": "0:40", "duration_seconds": 6.7, "lyric_line": "And we're connected by the music and the floor / Two strangers making something to live for", "scene": {"mood": "serene", "colors": ["pale blue", "first light"], "composition": "extreme close-up", "camera": "macro lens", "description": "Serene scene: And we're connected by the music and the floor / Two strange... Visual palette: pale blue, first light."}}
|
||||
{"song": "Concrete Lullaby", "artist": "Urban Hymnal", "genre": "Chamber Pop", "bpm": 72, "beat": 8, "timestamp": "0:46", "duration_seconds": 6.7, "lyric_line": "The dawn arrives in shades I can't afford / Painting gold on everything I've stored", "scene": {"mood": "melancholy", "colors": ["grey", "soft rose"], "composition": "over-the-shoulder", "camera": "rack focus", "description": "Melancholy scene: The dawn arrives in shades I can't afford / Painting gold on... Visual palette: grey, soft rose."}}
|
||||
{"song": "Concrete Lullaby", "artist": "Urban Hymnal", "genre": "Chamber Pop", "bpm": 72, "beat": 9, "timestamp": "0:53", "duration_seconds": 6.7, "lyric_line": "The wounds are healing where the concrete cracked / Flowers pushing through the facts", "scene": {"mood": "healing", "colors": ["healing green", "warm gold"], "composition": "slow pan", "camera": "slow zoom", "description": "Healing scene: The wounds are healing where the concrete cracked / Flowers ... Visual palette: healing green, warm gold."}}
|
||||
{"song": "Concrete Lullaby", "artist": "Urban Hymnal", "genre": "Chamber Pop", "bpm": 72, "beat": 10, "timestamp": "1:00", "duration_seconds": 6.7, "lyric_line": "This lullaby the city sings to me / Is proof that broken things can still be free", "scene": {"mood": "whole", "colors": ["complete spectrum", "soft white"], "composition": "pull-back reveal", "camera": "drone pull-back", "description": "Whole scene: This lullaby the city sings to me / Is proof that broken thi... Visual palette: complete spectrum, soft white."}}
|
||||
{"song": "Frequency", "artist": "Signal & Noise", "genre": "Art Pop", "bpm": 100, "beat": 1, "timestamp": "0:00", "duration_seconds": 4.8, "lyric_line": "I'm tuning in to something I can't name / A frequency between the noise and shame", "scene": {"mood": "curious", "colors": ["static grey", "white noise"], "composition": "wide establishing shot", "camera": "slow dolly in", "description": "Curious scene: I'm tuning in to something I can't name / A frequency betwee... Visual palette: static grey, white noise."}}
|
||||
{"song": "Frequency", "artist": "Signal & Noise", "genre": "Art Pop", "bpm": 100, "beat": 2, "timestamp": "0:04", "duration_seconds": 4.8, "lyric_line": "The static parts like curtains on a stage / Revealing signal on an empty page", "scene": {"mood": "intrigued", "colors": ["emerging green", "data blue"], "composition": "medium close-up", "camera": "handheld follow", "description": "Intrigued scene: The static parts like curtains on a stage / Revealing signal... Visual palette: emerging green, data blue."}}
|
||||
{"song": "Frequency", "artist": "Signal & Noise", "genre": "Art Pop", "bpm": 100, "beat": 3, "timestamp": "0:09", "duration_seconds": 4.8, "lyric_line": "Patterns in the chaos start to glow / A language only someone lost would know", "scene": {"mood": "fascinated", "colors": ["pattern gold", "signal red"], "composition": "dynamic tracking", "camera": "steady tracking", "description": "Fascinated scene: Patterns in the chaos start to glow / A language only someon... Visual palette: pattern gold, signal red."}}
|
||||
{"song": "Frequency", "artist": "Signal & Noise", "genre": "Art Pop", "bpm": 100, "beat": 4, "timestamp": "0:14", "duration_seconds": 4.8, "lyric_line": "I followed every channel to its end / A seeker mistaking signals for friends", "scene": {"mood": "obsessed", "colors": ["deep focus indigo", "single point light"], "composition": "static intimate", "camera": "locked off", "description": "Obsessed scene: I followed every channel to its end / A seeker mistaking sig... Visual palette: deep focus indigo, single point light."}}
|
||||
{"song": "Frequency", "artist": "Signal & Noise", "genre": "Art Pop", "bpm": 100, "beat": 5, "timestamp": "0:19", "duration_seconds": 4.8, "lyric_line": "The void between the stations is so wide / I screamed my name and only heard the tide", "scene": {"mood": "lost", "colors": ["void black", "echo purple"], "composition": "low angle", "camera": "crane up", "description": "Lost scene: The void between the stations is so wide / I screamed my nam... Visual palette: void black, echo purple."}}
|
||||
{"song": "Frequency", "artist": "Signal & Noise", "genre": "Art Pop", "bpm": 100, "beat": 6, "timestamp": "0:24", "duration_seconds": 4.8, "lyric_line": "Then clarity arrived without a sound / The frequency was always underground", "scene": {"mood": "awakening", "colors": ["dawn frequency amber"], "composition": "aerial wide", "camera": "steadicam glide", "description": "Awakening scene: Then clarity arrived without a sound / The frequency was alw... Visual palette: dawn frequency amber."}}
|
||||
{"song": "Frequency", "artist": "Signal & Noise", "genre": "Art Pop", "bpm": 100, "beat": 7, "timestamp": "0:28", "duration_seconds": 4.8, "lyric_line": "Not in the broadcast but in what receives / The truth lives in the silence between leaves", "scene": {"mood": "clarity", "colors": ["crystal clear", "prism white"], "composition": "extreme close-up", "camera": "macro lens", "description": "Clarity scene: Not in the broadcast but in what receives / The truth lives ... Visual palette: crystal clear, prism white."}}
|
||||
{"song": "Frequency", "artist": "Signal & Noise", "genre": "Art Pop", "bpm": 100, "beat": 8, "timestamp": "0:33", "duration_seconds": 4.8, "lyric_line": "And now I hear your signal, faint but true / A frequency that's tuned to me and you", "scene": {"mood": "connection", "colors": ["warm connection rose", "blue"], "composition": "over-the-shoulder", "camera": "rack focus", "description": "Connection scene: And now I hear your signal, faint but true / A frequency tha... Visual palette: warm connection rose, blue."}}
|
||||
{"song": "Frequency", "artist": "Signal & Noise", "genre": "Art Pop", "bpm": 100, "beat": 9, "timestamp": "0:38", "duration_seconds": 4.8, "lyric_line": "Resonance \u2014 the matching of our waves / Two signals finding shelter in the caves", "scene": {"mood": "resonance", "colors": ["harmonic spectrum"], "composition": "slow pan", "camera": "slow zoom", "description": "Resonance scene: Resonance \u2014 the matching of our waves / Two signals finding ... Visual palette: harmonic spectrum."}}
|
||||
{"song": "Frequency", "artist": "Signal & Noise", "genre": "Art Pop", "bpm": 100, "beat": 10, "timestamp": "0:43", "duration_seconds": 4.8, "lyric_line": "We are the frequency, not the noise / United in the signal's poise", "scene": {"mood": "unity", "colors": ["pure white light", "all colors unified"], "composition": "pull-back reveal", "camera": "drone pull-back", "description": "Unity scene: We are the frequency, not the noise / United in the signal's... Visual palette: pure white light, all colors unified."}}
|
||||
{"song": "Paper Kites", "artist": "Origami Hearts", "genre": "Folk Pop", "bpm": 105, "beat": 1, "timestamp": "0:00", "duration_seconds": 4.6, "lyric_line": "Fold me a kite from the morning news / I'll fly it over everything I lose", "scene": {"mood": "innocent", "colors": ["cream paper", "sky blue"], "composition": "wide establishing shot", "camera": "slow dolly in", "description": "Innocent scene: Fold me a kite from the morning news / I'll fly it over ever... Visual palette: cream paper, sky blue."}}
|
||||
{"song": "Paper Kites", "artist": "Origami Hearts", "genre": "Folk Pop", "bpm": 105, "beat": 2, "timestamp": "0:04", "duration_seconds": 4.6, "lyric_line": "The paper creases hold a memory / Of simpler days when I was simply me", "scene": {"mood": "joyful", "colors": ["bright yellow", "cloud white"], "composition": "medium close-up", "camera": "handheld follow", "description": "Joyful scene: The paper creases hold a memory / Of simpler days when I was... Visual palette: bright yellow, cloud white."}}
|
||||
{"song": "Paper Kites", "artist": "Origami Hearts", "genre": "Folk Pop", "bpm": 105, "beat": 3, "timestamp": "0:09", "duration_seconds": 4.6, "lyric_line": "We ran through fields with string between our fingers / The wind decided where our futures lingered", "scene": {"mood": "carefree", "colors": ["green grass", "kite colors"], "composition": "dynamic tracking", "camera": "steady tracking", "description": "Carefree scene: We ran through fields with string between our fingers / The ... Visual palette: green grass, kite colors."}}
|
||||
{"song": "Paper Kites", "artist": "Origami Hearts", "genre": "Folk Pop", "bpm": 105, "beat": 4, "timestamp": "0:13", "duration_seconds": 4.6, "lyric_line": "But winds change direction without a warning / And kites can tear on any given morning", "scene": {"mood": "scared", "colors": ["wind grey", "fear white"], "composition": "static intimate", "camera": "locked off", "description": "Scared scene: But winds change direction without a warning / And kites can... Visual palette: wind grey, fear white."}}
|
||||
{"song": "Paper Kites", "artist": "Origami Hearts", "genre": "Folk Pop", "bpm": 105, "beat": 5, "timestamp": "0:18", "duration_seconds": 4.6, "lyric_line": "I held the string so tight it burned my hand / Afraid to lose what I could barely stand", "scene": {"mood": "brave", "colors": ["determined red", "courage blue"], "composition": "low angle", "camera": "crane up", "description": "Brave scene: I held the string so tight it burned my hand / Afraid to los... Visual palette: determined red, courage blue."}}
|
||||
{"song": "Paper Kites", "artist": "Origami Hearts", "genre": "Folk Pop", "bpm": 105, "beat": 6, "timestamp": "0:22", "duration_seconds": 4.6, "lyric_line": "Then I let go \u2014 and the kite, it rose / Higher than my fear would ever go", "scene": {"mood": "soaring", "colors": ["soaring gold", "open sky"], "composition": "aerial wide", "camera": "steadicam glide", "description": "Soaring scene: Then I let go \u2014 and the kite, it rose / Higher than my fear ... Visual palette: soaring gold, open sky."}}
|
||||
{"song": "Paper Kites", "artist": "Origami Hearts", "genre": "Folk Pop", "bpm": 105, "beat": 7, "timestamp": "0:27", "duration_seconds": 4.6, "lyric_line": "Free without me holding on so tight / Soaring in its own discovered light", "scene": {"mood": "free", "colors": ["rainbow", "infinite blue"], "composition": "extreme close-up", "camera": "macro lens", "description": "Free scene: Free without me holding on so tight / Soaring in its own dis... Visual palette: rainbow, infinite blue."}}
|
||||
{"song": "Paper Kites", "artist": "Origami Hearts", "genre": "Folk Pop", "bpm": 105, "beat": 8, "timestamp": "0:32", "duration_seconds": 4.6, "lyric_line": "Thank you, wind, for taking what I clung / Thank you, sky, for teaching me your tongue", "scene": {"mood": "grateful", "colors": ["sunset gold", "warm pink"], "composition": "over-the-shoulder", "camera": "rack focus", "description": "Grateful scene: Thank you, wind, for taking what I clung / Thank you, sky, f... Visual palette: sunset gold, warm pink."}}
|
||||
{"song": "Paper Kites", "artist": "Origami Hearts", "genre": "Folk Pop", "bpm": 105, "beat": 9, "timestamp": "0:36", "duration_seconds": 4.6, "lyric_line": "The kite is now a star I can't quite see / But I feel its pull reminding me", "scene": {"mood": "peaceful", "colors": ["dusk purple", "starlight"], "composition": "slow pan", "camera": "slow zoom", "description": "Peaceful scene: The kite is now a star I can't quite see / But I feel its pu... Visual palette: dusk purple, starlight."}}
|
||||
{"song": "Paper Kites", "artist": "Origami Hearts", "genre": "Folk Pop", "bpm": 105, "beat": 10, "timestamp": "0:41", "duration_seconds": 4.6, "lyric_line": "Some things fly better when you set them free / Paper kites and you and me", "scene": {"mood": "eternal", "colors": ["silver moonlight", "kite silhouette"], "composition": "pull-back reveal", "camera": "drone pull-back", "description": "Eternal scene: Some things fly better when you set them free / Paper kites ... Visual palette: silver moonlight, kite silhouette."}}
|
||||
{"song": "Undertow", "artist": "Depth Charge", "genre": "Dark Pop", "bpm": 90, "beat": 1, "timestamp": "0:00", "duration_seconds": 5.3, "lyric_line": "Something underneath is pulling at my feet / The surface stays still but the current's complete", "scene": {"mood": "dread", "colors": ["deep ocean blue", "dark teal"], "composition": "wide establishing shot", "camera": "slow dolly in", "description": "Dread scene: Something underneath is pulling at my feet / The surface sta... Visual palette: deep ocean blue, dark teal."}}
|
||||
{"song": "Undertow", "artist": "Depth Charge", "genre": "Dark Pop", "bpm": 90, "beat": 2, "timestamp": "0:05", "duration_seconds": 5.3, "lyric_line": "I didn't see the tide change its mind / The undertow was patient, I was blind", "scene": {"mood": "pulling", "colors": ["pulling green-black", "undertow grey"], "composition": "medium close-up", "camera": "handheld follow", "description": "Pulling scene: I didn't see the tide change its mind / The undertow was pat... Visual palette: pulling green-black, undertow grey."}}
|
||||
{"song": "Undertow", "artist": "Depth Charge", "genre": "Dark Pop", "bpm": 90, "beat": 3, "timestamp": "0:10", "duration_seconds": 5.3, "lyric_line": "Salt fills my lungs, the light goes dim / The ocean doesn't care if I can swim", "scene": {"mood": "drowning", "colors": ["suffocation dark blue", "pressure black"], "composition": "dynamic tracking", "camera": "steady tracking", "description": "Drowning scene: Salt fills my lungs, the light goes dim / The ocean doesn't ... Visual palette: suffocation dark blue, pressure black."}}
|
||||
{"song": "Undertow", "artist": "Depth Charge", "genre": "Dark Pop", "bpm": 90, "beat": 4, "timestamp": "0:16", "duration_seconds": 5.3, "lyric_line": "I claw against the pressure and the dark / My fingers finding nothing but the mark", "scene": {"mood": "fighting", "colors": ["fighting red", "survival gold"], "composition": "static intimate", "camera": "locked off", "description": "Fighting scene: I claw against the pressure and the dark / My fingers findin... Visual palette: fighting red, survival gold."}}
|
||||
{"song": "Undertow", "artist": "Depth Charge", "genre": "Dark Pop", "bpm": 90, "beat": 5, "timestamp": "0:21", "duration_seconds": 5.3, "lyric_line": "Then something shifts \u2014 I stop fighting the sea / And find the current carries me", "scene": {"mood": "surrendering", "colors": ["surrender white", "deep peace blue"], "composition": "low angle", "camera": "crane up", "description": "Surrendering scene: Then something shifts \u2014 I stop fighting the sea / And find t... Visual palette: surrender white, deep peace blue."}}
|
||||
{"song": "Undertow", "artist": "Depth Charge", "genre": "Dark Pop", "bpm": 90, "beat": 6, "timestamp": "0:26", "duration_seconds": 5.3, "lyric_line": "Down in the deep where the pressure's immense / I found a power that makes sense", "scene": {"mood": "discovering", "colors": ["bioluminescent green", "deep discovery"], "composition": "aerial wide", "camera": "steadicam glide", "description": "Discovering scene: Down in the deep where the pressure's immense / I found a po... Visual palette: bioluminescent green, deep discovery."}}
|
||||
{"song": "Undertow", "artist": "Depth Charge", "genre": "Dark Pop", "bpm": 90, "beat": 7, "timestamp": "0:32", "duration_seconds": 5.3, "lyric_line": "The undertow became my rising force / The drowning was the source, not the remorse", "scene": {"mood": "powerful", "colors": ["power blue", "golden strength"], "composition": "extreme close-up", "camera": "macro lens", "description": "Powerful scene: The undertow became my rising force / The drowning was the s... Visual palette: power blue, golden strength."}}
|
||||
{"song": "Undertow", "artist": "Depth Charge", "genre": "Dark Pop", "bpm": 90, "beat": 8, "timestamp": "0:37", "duration_seconds": 5.3, "lyric_line": "I breach the surface with a gasp of gold / The story of the sinking has been told", "scene": {"mood": "rising", "colors": ["rising through blues to turquoise"], "composition": "over-the-shoulder", "camera": "rack focus", "description": "Rising scene: I breach the surface with a gasp of gold / The story of the ... Visual palette: rising through blues to turquoise."}}
|
||||
{"song": "Undertow", "artist": "Depth Charge", "genre": "Dark Pop", "bpm": 90, "beat": 9, "timestamp": "0:42", "duration_seconds": 5.3, "lyric_line": "I am the wave now, not the one who fell / The undertow bows down, and I can tell", "scene": {"mood": "dominant", "colors": ["surface gold", "triumph white"], "composition": "slow pan", "camera": "slow zoom", "description": "Dominant scene: I am the wave now, not the one who fell / The undertow bows ... Visual palette: surface gold, triumph white."}}
|
||||
{"song": "Undertow", "artist": "Depth Charge", "genre": "Dark Pop", "bpm": 90, "beat": 10, "timestamp": "0:48", "duration_seconds": 5.3, "lyric_line": "What tried to drown me gave me depth instead / Undertow \u2014 the living and the dead", "scene": {"mood": "transcendent", "colors": ["above the water \u2014 full sunlight"], "composition": "pull-back reveal", "camera": "drone pull-back", "description": "Transcendent scene: What tried to drown me gave me depth instead / Undertow \u2014 th... Visual palette: above the water \u2014 full sunlight."}}
|
||||
Reference in New Issue
Block a user