Compare commits
1 Commits
data/scene
...
fix/config
| Author | SHA1 | Date | |
|---|---|---|---|
| c7002b5218 |
480
scripts/config_drift_detector.py
Normal file
480
scripts/config_drift_detector.py
Normal file
@@ -0,0 +1,480 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
config_drift_detector.py — Detect config drift across fleet nodes.
|
||||
|
||||
Collects config from all wizard nodes via SSH, compares against
|
||||
canonical timmy-config golden state, and reports differences.
|
||||
|
||||
Usage:
|
||||
python3 scripts/config_drift_detector.py # Report only
|
||||
python3 scripts/config_drift_detector.py --auto-sync # Auto-fix drift with golden state
|
||||
python3 scripts/config_drift_detector.py --node allegro # Check single node
|
||||
python3 scripts/config_drift_detector.py --json # JSON output for automation
|
||||
|
||||
Exit codes:
|
||||
0 — no drift detected
|
||||
1 — drift detected
|
||||
2 — error (SSH failure, missing deps, etc.)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
# ── Constants ─────────────────────────────────────────────────────────────────
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
REPO_ROOT = SCRIPT_DIR.parent
|
||||
ANSIBLE_INVENTORY = REPO_ROOT / "ansible" / "inventory" / "hosts.yml"
|
||||
GOLDEN_STATE_PLAYBOOK = REPO_ROOT / "ansible" / "playbooks" / "golden_state.yml"
|
||||
|
||||
# Config files to check on each node
|
||||
CONFIG_PATHS = [
|
||||
".hermes/config.yaml",
|
||||
"wizards/{name}/config.yaml",
|
||||
]
|
||||
|
||||
# Keys that define golden state (from ansible inventory vars)
|
||||
GOLDEN_KEYS = [
|
||||
"providers",
|
||||
"provider",
|
||||
"model",
|
||||
"base_url",
|
||||
"api_key_env",
|
||||
"banned_providers",
|
||||
"banned_models_patterns",
|
||||
]
|
||||
|
||||
|
||||
# ── Data Models ───────────────────────────────────────────────────────────────
|
||||
|
||||
@dataclass
|
||||
class NodeConfig:
|
||||
name: str
|
||||
host: str
|
||||
configs: dict[str, Any] = field(default_factory=dict)
|
||||
errors: list[str] = field(default_factory=list)
|
||||
reachable: bool = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class DriftResult:
|
||||
node: str
|
||||
file_path: str
|
||||
diff_type: str # "missing", "value_mismatch", "key_missing", "extra_key"
|
||||
key: str
|
||||
canonical_value: Any = None
|
||||
node_value: Any = None
|
||||
severity: str = "warning" # "info", "warning", "critical"
|
||||
|
||||
|
||||
# ── Inventory Parsing ─────────────────────────────────────────────────────────
|
||||
|
||||
def load_inventory() -> dict:
|
||||
"""Load Ansible inventory and extract wizard node definitions."""
|
||||
if not ANSIBLE_INVENTORY.exists():
|
||||
print(f"ERROR: Inventory not found at {ANSIBLE_INVENTORY}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
with open(ANSIBLE_INVENTORY) as f:
|
||||
inventory = yaml.safe_load(f)
|
||||
|
||||
wizards = inventory.get("all", {}).get("children", {}).get("wizards", {}).get("hosts", {})
|
||||
global_vars = inventory.get("all", {}).get("vars", {})
|
||||
|
||||
nodes = {}
|
||||
for name, config in wizards.items():
|
||||
nodes[name] = {
|
||||
"host": config.get("ansible_host", "localhost"),
|
||||
"user": config.get("ansible_user", ""),
|
||||
"wizard_name": config.get("wizard_name", name),
|
||||
"hermes_home": config.get("hermes_home", "~/.hermes"),
|
||||
"wizard_home": config.get("wizard_home", f"~/wizards/{name}"),
|
||||
"machine_type": config.get("machine_type", "unknown"),
|
||||
}
|
||||
|
||||
return nodes, global_vars
|
||||
|
||||
|
||||
def load_golden_state(inventory_vars: dict) -> dict:
|
||||
"""Extract golden state from inventory vars."""
|
||||
golden = {
|
||||
"providers": inventory_vars.get("golden_state_providers", []),
|
||||
"banned_providers": inventory_vars.get("banned_providers", []),
|
||||
"banned_models_patterns": inventory_vars.get("banned_models_patterns", []),
|
||||
}
|
||||
return golden
|
||||
|
||||
|
||||
# ── SSH Collection ────────────────────────────────────────────────────────────
|
||||
|
||||
def ssh_collect(node_name: str, node_info: dict, timeout: int = 15) -> NodeConfig:
|
||||
"""SSH into a node and collect config files."""
|
||||
host = node_info["host"]
|
||||
user = node_info.get("user", "")
|
||||
hermes_home = node_info.get("hermes_home", "~/.hermes")
|
||||
wizard_home = node_info.get("wizard_home", f"~/wizards/{node_name}")
|
||||
|
||||
result = NodeConfig(name=node_name, host=host)
|
||||
|
||||
# Build SSH target
|
||||
if host in ("localhost", "127.0.0.1"):
|
||||
ssh_target = None # local
|
||||
else:
|
||||
ssh_target = f"{user}@{host}" if user else host
|
||||
|
||||
# Collect each config path
|
||||
for path_template in CONFIG_PATHS:
|
||||
# Resolve path template
|
||||
remote_path = path_template.replace("{name}", node_name)
|
||||
if not remote_path.startswith("/"):
|
||||
# Resolve relative to home
|
||||
if "wizards/" in remote_path:
|
||||
full_path = f"{wizard_home}/config.yaml"
|
||||
else:
|
||||
full_path = f"{hermes_home}/config.yaml" if ".hermes" in remote_path else f"~/{remote_path}"
|
||||
else:
|
||||
full_path = remote_path
|
||||
|
||||
config_content = _remote_cat(ssh_target, full_path, timeout)
|
||||
if config_content is not None:
|
||||
try:
|
||||
parsed = yaml.safe_load(config_content)
|
||||
if parsed:
|
||||
result.configs[full_path] = parsed
|
||||
except yaml.YAMLError as e:
|
||||
result.errors.append(f"YAML parse error in {full_path}: {e}")
|
||||
# Don't flag missing files as errors — some paths may not exist on all nodes
|
||||
|
||||
# Also collect banned provider scan
|
||||
banned_check = _remote_grep(
|
||||
ssh_target,
|
||||
hermes_home,
|
||||
r"anthropic|claude-sonnet|claude-opus|claude-haiku",
|
||||
timeout
|
||||
)
|
||||
if banned_check:
|
||||
result.configs["__banned_scan__"] = banned_check
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _remote_cat(ssh_target: str | None, path: str, timeout: int) -> str | None:
|
||||
"""Cat a file remotely (or locally)."""
|
||||
if ssh_target is None:
|
||||
cmd = ["cat", path]
|
||||
else:
|
||||
cmd = ["ssh", "-o", "ConnectTimeout=5", "-o", "StrictHostKeyChecking=no",
|
||||
ssh_target, f"cat {path}"]
|
||||
|
||||
try:
|
||||
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
||||
if proc.returncode == 0:
|
||||
return proc.stdout
|
||||
except subprocess.TimeoutExpired:
|
||||
pass
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _remote_grep(ssh_target: str | None, base_path: str, pattern: str, timeout: int) -> dict:
|
||||
"""Grep for banned patterns in config files."""
|
||||
if ssh_target is None:
|
||||
cmd = ["grep", "-rn", "-i", pattern, base_path, "--include=*.yaml", "--include=*.yml"]
|
||||
else:
|
||||
cmd = ["ssh", "-o", "ConnectTimeout=5", "-o", "StrictHostKeyChecking=no",
|
||||
ssh_target, f"grep -rn -i '{pattern}' {base_path} --include='*.yaml' --include='*.yml' 2>/dev/null || true"]
|
||||
|
||||
try:
|
||||
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
||||
if proc.stdout.strip():
|
||||
lines = proc.stdout.strip().split("\n")
|
||||
return {"matches": lines, "count": len(lines)}
|
||||
except subprocess.TimeoutExpired:
|
||||
pass
|
||||
return {}
|
||||
|
||||
|
||||
# ── Drift Detection ───────────────────────────────────────────────────────────
|
||||
|
||||
def detect_drift(nodes: list[NodeConfig], golden: dict) -> list[DriftResult]:
|
||||
"""Compare each node's config against golden state."""
|
||||
results = []
|
||||
|
||||
for node in nodes:
|
||||
if not node.reachable:
|
||||
continue
|
||||
|
||||
# Check for banned providers
|
||||
banned_scan = node.configs.get("__banned_scan__", {})
|
||||
if banned_scan.get("count", 0) > 0:
|
||||
for match in banned_scan.get("matches", []):
|
||||
results.append(DriftResult(
|
||||
node=node.name,
|
||||
file_path="(config files)",
|
||||
diff_type="banned_provider_found",
|
||||
key="banned_provider_reference",
|
||||
node_value=match,
|
||||
severity="critical"
|
||||
))
|
||||
|
||||
# Check each config file
|
||||
for path, config in node.configs.items():
|
||||
if path == "__banned_scan__":
|
||||
continue
|
||||
|
||||
# Check provider chain
|
||||
if isinstance(config, dict):
|
||||
node_providers = _extract_provider_chain(config)
|
||||
golden_providers = golden.get("providers", [])
|
||||
|
||||
if node_providers and golden_providers:
|
||||
# Compare provider names in order
|
||||
node_names = [p.get("name", "") for p in node_providers]
|
||||
golden_names = [p.get("name", "") for p in golden_providers]
|
||||
|
||||
if node_names != golden_names:
|
||||
results.append(DriftResult(
|
||||
node=node.name,
|
||||
file_path=path,
|
||||
diff_type="value_mismatch",
|
||||
key="provider_chain",
|
||||
canonical_value=golden_names,
|
||||
node_value=node_names,
|
||||
severity="critical"
|
||||
))
|
||||
|
||||
# Check for banned providers in node config
|
||||
for banned in golden.get("banned_providers", []):
|
||||
for provider in node_providers:
|
||||
prov_name = provider.get("name", "").lower()
|
||||
prov_model = provider.get("model", "").lower()
|
||||
if banned in prov_name or banned in prov_model:
|
||||
results.append(DriftResult(
|
||||
node=node.name,
|
||||
file_path=path,
|
||||
diff_type="banned_provider_found",
|
||||
key=f"provider.{provider.get('name', 'unknown')}",
|
||||
node_value=provider,
|
||||
severity="critical"
|
||||
))
|
||||
|
||||
# Check for missing critical keys
|
||||
critical_keys = ["display", "providers", "tools", "delegation"]
|
||||
for key in critical_keys:
|
||||
if key not in config and key in str(config):
|
||||
results.append(DriftResult(
|
||||
node=node.name,
|
||||
file_path=path,
|
||||
diff_type="key_missing",
|
||||
key=key,
|
||||
canonical_value="(present in golden state)",
|
||||
severity="warning"
|
||||
))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def _extract_provider_chain(config: dict) -> list[dict]:
|
||||
"""Extract provider list from a config dict (handles multiple formats)."""
|
||||
# Direct providers key
|
||||
if "providers" in config:
|
||||
providers = config["providers"]
|
||||
if isinstance(providers, list):
|
||||
return providers
|
||||
|
||||
# Nested in display or model config
|
||||
for key in ["model", "inference", "llm"]:
|
||||
if key in config and isinstance(config[key], dict):
|
||||
if "providers" in config[key]:
|
||||
return config[key]["providers"]
|
||||
|
||||
# Single provider format
|
||||
if "provider" in config and "model" in config:
|
||||
return [{"name": config["provider"], "model": config["model"]}]
|
||||
|
||||
return []
|
||||
|
||||
|
||||
# ── Auto-Sync ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def auto_sync(drifts: list[DriftResult], nodes: list[NodeConfig]) -> list[str]:
|
||||
"""Auto-sync drifted nodes using golden state playbook."""
|
||||
actions = []
|
||||
|
||||
drifted_nodes = set(d.node for d in drifts if d.severity == "critical")
|
||||
if not drifted_nodes:
|
||||
actions.append("No critical drift to sync.")
|
||||
return actions
|
||||
|
||||
for node_name in drifted_nodes:
|
||||
node_info = next((n for n in nodes if n.name == node_name), None)
|
||||
if not node_info:
|
||||
continue
|
||||
|
||||
actions.append(f"[{node_name}] Running golden state sync...")
|
||||
|
||||
# Run ansible-playbook for this node
|
||||
cmd = [
|
||||
"ansible-playbook",
|
||||
str(GOLDEN_STATE_PLAYBOOK),
|
||||
"-i", str(ANSIBLE_INVENTORY),
|
||||
"-l", node_name,
|
||||
"--tags", "golden",
|
||||
]
|
||||
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=120,
|
||||
cwd=str(REPO_ROOT)
|
||||
)
|
||||
if proc.returncode == 0:
|
||||
actions.append(f"[{node_name}] Sync completed successfully.")
|
||||
else:
|
||||
actions.append(f"[{node_name}] Sync FAILED: {proc.stderr[:200]}")
|
||||
except subprocess.TimeoutExpired:
|
||||
actions.append(f"[{node_name}] Sync timed out after 120s.")
|
||||
except FileNotFoundError:
|
||||
actions.append(f"[{node_name}] ansible-playbook not found. Install Ansible or run manually.")
|
||||
|
||||
return actions
|
||||
|
||||
|
||||
# ── Reporting ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def print_report(drifts: list[DriftResult], nodes: list[NodeConfig], golden: dict):
|
||||
"""Print human-readable drift report."""
|
||||
print("=" * 70)
|
||||
print("CONFIG DRIFT DETECTION REPORT")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
# Summary
|
||||
reachable = sum(1 for n in nodes if n.reachable)
|
||||
print(f"Nodes checked: {len(nodes)} (reachable: {reachable})")
|
||||
print(f"Golden state providers: {' → '.join(p['name'] for p in golden.get('providers', []))}")
|
||||
print(f"Banned providers: {', '.join(golden.get('banned_providers', []))}")
|
||||
print()
|
||||
|
||||
if not drifts:
|
||||
print("[OK] No config drift detected. All nodes match golden state.")
|
||||
return
|
||||
|
||||
# Group by node
|
||||
by_node: dict[str, list[DriftResult]] = {}
|
||||
for d in drifts:
|
||||
by_node.setdefault(d.node, []).append(d)
|
||||
|
||||
for node_name, node_drifts in sorted(by_node.items()):
|
||||
print(f"--- {node_name} ---")
|
||||
for d in node_drifts:
|
||||
severity_icon = {"critical": "[!!]", "warning": "[!]", "info": "[i]"}.get(d.severity, "[?]")
|
||||
print(f" {severity_icon} {d.diff_type}: {d.key}")
|
||||
if d.canonical_value is not None:
|
||||
print(f" canonical: {d.canonical_value}")
|
||||
if d.node_value is not None:
|
||||
print(f" actual: {d.node_value}")
|
||||
print()
|
||||
|
||||
# Severity summary
|
||||
critical = sum(1 for d in drifts if d.severity == "critical")
|
||||
warning = sum(1 for d in drifts if d.severity == "warning")
|
||||
print(f"Total: {len(drifts)} drift(s) — {critical} critical, {warning} warning")
|
||||
|
||||
|
||||
def print_json_report(drifts: list[DriftResult], nodes: list[NodeConfig], golden: dict):
|
||||
"""Print JSON report for automation."""
|
||||
report = {
|
||||
"nodes_checked": len(nodes),
|
||||
"reachable": sum(1 for n in nodes if n.reachable),
|
||||
"golden_providers": [p["name"] for p in golden.get("providers", [])],
|
||||
"drift_count": len(drifts),
|
||||
"critical_count": sum(1 for d in drifts if d.severity == "critical"),
|
||||
"drifts": [
|
||||
{
|
||||
"node": d.node,
|
||||
"file": d.file_path,
|
||||
"type": d.diff_type,
|
||||
"key": d.key,
|
||||
"canonical": d.canonical_value,
|
||||
"actual": d.node_value,
|
||||
"severity": d.severity,
|
||||
}
|
||||
for d in drifts
|
||||
],
|
||||
}
|
||||
print(json.dumps(report, indent=2, default=str))
|
||||
|
||||
|
||||
# ── CLI ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Detect config drift across fleet nodes")
|
||||
parser.add_argument("--node", help="Check only this node")
|
||||
parser.add_argument("--auto-sync", action="store_true", help="Auto-fix critical drift with golden state")
|
||||
parser.add_argument("--json", action="store_true", help="JSON output")
|
||||
parser.add_argument("--timeout", type=int, default=15, help="SSH timeout per node (seconds)")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Load inventory
|
||||
print("Loading inventory...", file=sys.stderr)
|
||||
node_defs, global_vars = load_inventory()
|
||||
golden = load_golden_state(global_vars)
|
||||
|
||||
# Filter to single node if requested
|
||||
if args.node:
|
||||
if args.node not in node_defs:
|
||||
print(f"ERROR: Node '{args.node}' not in inventory. Available: {', '.join(node_defs.keys())}")
|
||||
sys.exit(2)
|
||||
node_defs = {args.node: node_defs[args.node]}
|
||||
|
||||
# Collect configs from each node
|
||||
print(f"Collecting configs from {len(node_defs)} node(s)...", file=sys.stderr)
|
||||
nodes = []
|
||||
for name, info in node_defs.items():
|
||||
print(f" {name} ({info['host']})...", file=sys.stderr, end=" ", flush=True)
|
||||
node_config = ssh_collect(name, info, timeout=args.timeout)
|
||||
if node_config.reachable:
|
||||
print(f"OK ({len(node_config.configs)} files)", file=sys.stderr)
|
||||
else:
|
||||
print("UNREACHABLE", file=sys.stderr)
|
||||
nodes.append(node_config)
|
||||
|
||||
# Detect drift
|
||||
print("\nAnalyzing drift...", file=sys.stderr)
|
||||
drifts = detect_drift(nodes, golden)
|
||||
|
||||
# Output
|
||||
if args.json:
|
||||
print_json_report(drifts, nodes, golden)
|
||||
else:
|
||||
print()
|
||||
print_report(drifts, nodes, golden)
|
||||
|
||||
# Auto-sync if requested
|
||||
if args.auto_sync and drifts:
|
||||
print("\n--- AUTO-SYNC ---")
|
||||
actions = auto_sync(drifts, nodes)
|
||||
for a in actions:
|
||||
print(a)
|
||||
|
||||
# Exit code
|
||||
if any(d.severity == "critical" for d in drifts):
|
||||
sys.exit(1)
|
||||
elif drifts:
|
||||
sys.exit(1)
|
||||
else:
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,100 +0,0 @@
|
||||
{"song": "Concrete Dreams", "artist": "Street Prophet", "mood_arc": "struggle → triumph", "beat": 1, "timestamp": "0:00", "duration_seconds": 14, "lyric_line": "Started from the basement, counting ceiling stains", "scene": {"mood": "struggle", "colors": ["concrete gray", "sodium orange"], "composition": "aerial city block", "camera_movement": "slow descent", "description": "struggle scene: Started from the basement, counting ceiling stains — aerial city block with concrete gray, sodium orange palette, slow descent camera."}}
|
||||
{"song": "Concrete Dreams", "artist": "Street Prophet", "mood_arc": "struggle → triumph", "beat": 2, "timestamp": "0:14", "duration_seconds": 16, "lyric_line": "Empty fridge but the mind is full", "scene": {"mood": "hunger", "colors": ["midnight blue", "streetlight yellow"], "composition": "figure on fire escape", "camera_movement": "tracking up", "description": "hunger scene: Empty fridge but the mind is full — figure on fire escape with midnight blue, streetlight yellow palette, tracking up camera."}}
|
||||
{"song": "Concrete Dreams", "artist": "Street Prophet", "mood_arc": "struggle → triumph", "beat": 3, "timestamp": "0:30", "duration_seconds": 15, "lyric_line": "Pen to paper like a surgeon to a wound", "scene": {"mood": "determination", "colors": ["steel gray", "flash of gold"], "composition": "hands writing in notebook", "camera_movement": "close-up macro", "description": "determination scene: Pen to paper like a surgeon to a wound — hands writing in notebook with steel gray, flash of gold palette, close-up macro camera."}}
|
||||
{"song": "Concrete Dreams", "artist": "Street Prophet", "mood_arc": "struggle → triumph", "beat": 4, "timestamp": "0:45", "duration_seconds": 18, "lyric_line": "They said I'd never make it — watch me build the stage", "scene": {"mood": "energy", "colors": ["neon green", "black"], "composition": "crowd forming", "camera_movement": "dolly through", "description": "energy scene: They said I'd never make it — watch me build the stage — crowd forming with neon green, black palette, dolly through camera."}}
|
||||
{"song": "Concrete Dreams", "artist": "Street Prophet", "mood_arc": "struggle → triumph", "beat": 5, "timestamp": "1:03", "duration_seconds": 14, "lyric_line": "Same face in the mirror, different eyes", "scene": {"mood": "reflection", "colors": ["warm amber", "shadows"], "composition": "mirror reflection", "camera_movement": "rack focus", "description": "reflection scene: Same face in the mirror, different eyes — mirror reflection with warm amber, shadows palette, rack focus camera."}}
|
||||
{"song": "Concrete Dreams", "artist": "Street Prophet", "mood_arc": "struggle → triumph", "beat": 6, "timestamp": "1:17", "duration_seconds": 17, "lyric_line": "The city is mine — every block, every dream", "scene": {"mood": "power", "colors": ["gold", "deep red"], "composition": "figure on rooftop", "camera_movement": "crane up revealing", "description": "power scene: The city is mine — every block, every dream — figure on rooftop with gold, deep red palette, crane up revealing camera."}}
|
||||
{"song": "Concrete Dreams", "artist": "Street Prophet", "mood_arc": "struggle → triumph", "beat": 7, "timestamp": "1:34", "duration_seconds": 15, "lyric_line": "We made it out — the basement is a palace now", "scene": {"mood": "joy", "colors": ["bright white", "sunrise gold"], "composition": "group celebration", "camera_movement": "handheld energy", "description": "joy scene: We made it out — the basement is a palace now — group celebration with bright white, sunrise gold palette, handheld energy camera."}}
|
||||
{"song": "Concrete Dreams", "artist": "Street Prophet", "mood_arc": "struggle → triumph", "beat": 8, "timestamp": "1:49", "duration_seconds": 16, "lyric_line": "Every crack in the sidewalk is a chapter", "scene": {"mood": "gratitude", "colors": ["soft gold", "sky blue"], "composition": "figure looking down at neighborhood", "camera_movement": "over-shoulder", "description": "gratitude scene: Every crack in the sidewalk is a chapter — figure looking down at neighborhood with soft gold, sky blue palette, over-shoulder camera."}}
|
||||
{"song": "Concrete Dreams", "artist": "Street Prophet", "mood_arc": "struggle → triumph", "beat": 9, "timestamp": "2:05", "duration_seconds": 14, "lyric_line": "The dream was always real — they just couldn't see it", "scene": {"mood": "resolve", "colors": ["concrete gray → gold"], "composition": "walking forward", "camera_movement": "steadicam follow", "description": "resolve scene: The dream was always real — they just couldn't see it — walking forward with concrete gray → gold palette, steadicam follow camera."}}
|
||||
{"song": "Concrete Dreams", "artist": "Street Prophet", "mood_arc": "struggle → triumph", "beat": 10, "timestamp": "2:19", "duration_seconds": 13, "lyric_line": "Concrete dreams. Built from nothing.", "scene": {"mood": "peace", "colors": ["warm light", "city skyline"], "composition": "wide sunset", "camera_movement": "slow dissolve", "description": "peace scene: Concrete dreams. Built from nothing. — wide sunset with warm light, city skyline palette, slow dissolve camera."}}
|
||||
{"song": "Ghost Frequency", "artist": "Phantom MC", "mood_arc": "paranoia → clarity", "beat": 1, "timestamp": "0:00", "duration_seconds": 15, "lyric_line": "They watching through the walls, through the wires, through the air", "scene": {"mood": "paranoia", "colors": ["static gray", "red scan lines"], "composition": "surveillance POV", "camera_movement": "glitch cuts", "description": "paranoia scene: They watching through the walls, through the wires, through the air — surveillance POV with static gray, red scan lines palette, glitch cuts camera."}}
|
||||
{"song": "Ghost Frequency", "artist": "Phantom MC", "mood_arc": "paranoia → clarity", "beat": 2, "timestamp": "0:15", "duration_seconds": 14, "lyric_line": "Too many voices and none of them mine", "scene": {"mood": "anxiety", "colors": ["sickly green", "flickering"], "composition": "crowded room distorted", "camera_movement": "fish-eye warp", "description": "anxiety scene: Too many voices and none of them mine — crowded room distorted with sickly green, flickering palette, fish-eye warp camera."}}
|
||||
{"song": "Ghost Frequency", "artist": "Phantom MC", "mood_arc": "paranoia → clarity", "beat": 3, "timestamp": "0:29", "duration_seconds": 17, "lyric_line": "BREAK the frequency — find your own signal", "scene": {"mood": "rage", "colors": ["red", "black flash"], "composition": "smashing through barrier", "camera_movement": "impact freeze", "description": "rage scene: BREAK the frequency — find your own signal — smashing through barrier with red, black flash palette, impact freeze camera."}}
|
||||
{"song": "Ghost Frequency", "artist": "Phantom MC", "mood_arc": "paranoia → clarity", "beat": 4, "timestamp": "0:46", "duration_seconds": 15, "lyric_line": "The noise stops. I can hear myself think.", "scene": {"mood": "clarity", "colors": ["clean white", "single blue"], "composition": "alone in silence", "camera_movement": "still frame", "description": "clarity scene: The noise stops. I can hear myself think. — alone in silence with clean white, single blue palette, still frame camera."}}
|
||||
{"song": "Ghost Frequency", "artist": "Phantom MC", "mood_arc": "paranoia → clarity", "beat": 5, "timestamp": "1:01", "duration_seconds": 16, "lyric_line": "Found the ghost — it was me the whole time", "scene": {"mood": "discovery", "colors": ["spectrum rainbow", "dark room"], "composition": "equalizer waveform", "camera_movement": "waveform tracking", "description": "discovery scene: Found the ghost — it was me the whole time — equalizer waveform with spectrum rainbow, dark room palette, waveform tracking camera."}}
|
||||
{"song": "Ghost Frequency", "artist": "Phantom MC", "mood_arc": "paranoia → clarity", "beat": 6, "timestamp": "1:17", "duration_seconds": 18, "lyric_line": "I am the frequency they cannot tune out", "scene": {"mood": "power", "colors": ["electric blue", "void"], "composition": "figure controls the signal", "camera_movement": "orbit around figure", "description": "power scene: I am the frequency they cannot tune out — figure controls the signal with electric blue, void palette, orbit around figure camera."}}
|
||||
{"song": "Ghost Frequency", "artist": "Phantom MC", "mood_arc": "paranoia → clarity", "beat": 7, "timestamp": "1:35", "duration_seconds": 14, "lyric_line": "The ghost is quiet now. It listens.", "scene": {"mood": "peace", "colors": ["calm blue", "soft static"], "composition": "meditating in noise", "camera_movement": "slow push in", "description": "peace scene: The ghost is quiet now. It listens. — meditating in noise with calm blue, soft static palette, slow push in camera."}}
|
||||
{"song": "Ghost Frequency", "artist": "Phantom MC", "mood_arc": "paranoia → clarity", "beat": 8, "timestamp": "1:49", "duration_seconds": 16, "lyric_line": "Every frequency is a choice. Choose yours.", "scene": {"mood": "wisdom", "colors": ["warm gold", "gentle gray"], "composition": "teaching figure", "camera_movement": "two-shot medium", "description": "wisdom scene: Every frequency is a choice. Choose yours. — teaching figure with warm gold, gentle gray palette, two-shot medium camera."}}
|
||||
{"song": "Ghost Frequency", "artist": "Phantom MC", "mood_arc": "paranoia → clarity", "beat": 9, "timestamp": "2:05", "duration_seconds": 15, "lyric_line": "Broadcast on the ghost frequency. They will hear.", "scene": {"mood": "strength", "colors": ["clean signal", "white"], "composition": "broadcast tower", "camera_movement": "vertical crane up", "description": "strength scene: Broadcast on the ghost frequency. They will hear. — broadcast tower with clean signal, white palette, vertical crane up camera."}}
|
||||
{"song": "Ghost Frequency", "artist": "Phantom MC", "mood_arc": "paranoia → clarity", "beat": 10, "timestamp": "2:20", "duration_seconds": 12, "lyric_line": "...", "scene": {"mood": "silence", "colors": ["pure white", "still"], "composition": "empty frame", "camera_movement": "long hold", "description": "silence scene: ... — empty frame with pure white, still palette, long hold camera."}}
|
||||
{"song": "Cipher Kings", "artist": "Word Lab", "mood_arc": "playful → fierce", "beat": 1, "timestamp": "0:00", "duration_seconds": 13, "lyric_line": "A-B-C, easy as 1-2-3, but the cipher is 7-4-1", "scene": {"mood": "playful", "colors": ["bright primary colors", "white"], "composition": "graffiti wall", "camera_movement": "pan reveal", "description": "playful scene: A-B-C, easy as 1-2-3, but the cipher is 7-4-1 — graffiti wall with bright primary colors, white palette, pan reveal camera."}}
|
||||
{"song": "Cipher Kings", "artist": "Word Lab", "mood_arc": "playful → fierce", "beat": 2, "timestamp": "0:13", "duration_seconds": 15, "lyric_line": "Step up to the cipher circle, show me what you got", "scene": {"mood": "competitive", "colors": ["two-tone contrast", "spotlight"], "composition": "two MCs facing", "camera_movement": "split screen", "description": "competitive scene: Step up to the cipher circle, show me what you got — two MCs facing with two-tone contrast, spotlight palette, split screen camera."}}
|
||||
{"song": "Cipher Kings", "artist": "Word Lab", "mood_arc": "playful → fierce", "beat": 3, "timestamp": "0:28", "duration_seconds": 17, "lyric_line": "Every bar is a blade — sharpened by the block", "scene": {"mood": "fierce", "colors": ["fire orange", "smoke black"], "composition": "mic close-up", "camera_movement": "rapid cuts", "description": "fierce scene: Every bar is a blade — sharpened by the block — mic close-up with fire orange, smoke black palette, rapid cuts camera."}}
|
||||
{"song": "Cipher Kings", "artist": "Word Lab", "mood_arc": "playful → fierce", "beat": 4, "timestamp": "0:45", "duration_seconds": 14, "lyric_line": "Metaphor on metaphor — the meaning is the maze", "scene": {"mood": "wit", "colors": ["neon yellow", "dark purple"], "composition": "word cloud forming", "camera_movement": "zoom into text", "description": "wit scene: Metaphor on metaphor — the meaning is the maze — word cloud forming with neon yellow, dark purple palette, zoom into text camera."}}
|
||||
{"song": "Cipher Kings", "artist": "Word Lab", "mood_arc": "playful → fierce", "beat": 5, "timestamp": "0:59", "duration_seconds": 16, "lyric_line": "The crown was always made of words, not gold", "scene": {"mood": "dominance", "colors": ["gold", "black"], "composition": "crown close-up", "camera_movement": "slow rotation", "description": "dominance scene: The crown was always made of words, not gold — crown close-up with gold, black palette, slow rotation camera."}}
|
||||
{"song": "Cipher Kings", "artist": "Word Lab", "mood_arc": "playful → fierce", "beat": 6, "timestamp": "1:15", "duration_seconds": 15, "lyric_line": "When the cipher bows, the kings emerge", "scene": {"mood": "respect", "colors": ["warm amber", "stage light"], "composition": "audience reaction", "camera_movement": "crowd POV", "description": "respect scene: When the cipher bows, the kings emerge — audience reaction with warm amber, stage light palette, crowd POV camera."}}
|
||||
{"song": "Cipher Kings", "artist": "Word Lab", "mood_arc": "playful → fierce", "beat": 7, "timestamp": "1:30", "duration_seconds": 17, "lyric_line": "The circle was never about winning — it's about the words", "scene": {"mood": "celebration", "colors": ["confetti colors", "warm light"], "composition": "group embrace", "camera_movement": "circular dolly", "description": "celebration scene: The circle was never about winning — it's about the words — group embrace with confetti colors, warm light palette, circular dolly camera."}}
|
||||
{"song": "Cipher Kings", "artist": "Word Lab", "mood_arc": "playful → fierce", "beat": 8, "timestamp": "1:47", "duration_seconds": 14, "lyric_line": "Same circle, same fire, new kings", "scene": {"mood": "legacy", "colors": ["sepia", "gold"], "composition": "old cipher circle photo", "camera_movement": "fade from old to new", "description": "legacy scene: Same circle, same fire, new kings — old cipher circle photo with sepia, gold palette, fade from old to new camera."}}
|
||||
{"song": "Cipher Kings", "artist": "Word Lab", "mood_arc": "playful → fierce", "beat": 9, "timestamp": "2:01", "duration_seconds": 16, "lyric_line": "The cipher sleeps but the words never do", "scene": {"mood": "peace", "colors": ["sunset orange", "cool blue"], "composition": "walking away from circle", "camera_movement": "tracking behind", "description": "peace scene: The cipher sleeps but the words never do — walking away from circle with sunset orange, cool blue palette, tracking behind camera."}}
|
||||
{"song": "Cipher Kings", "artist": "Word Lab", "mood_arc": "playful → fierce", "beat": 10, "timestamp": "2:17", "duration_seconds": 13, "lyric_line": "Tomorrow the circle reforms. I'll be ready.", "scene": {"mood": "resolve", "colors": ["midnight", "single star"], "composition": "figure writing alone", "camera_movement": "close-up hands", "description": "resolve scene: Tomorrow the circle reforms. I'll be ready. — figure writing alone with midnight, single star palette, close-up hands camera."}}
|
||||
{"song": "404 Soul", "artist": "Digital Ghost", "mood_arc": "isolation → connection", "beat": 1, "timestamp": "0:00", "duration_seconds": 14, "lyric_line": "Upload my soul to the mainframe tonight", "scene": {"mood": "opening", "colors": ["city gray", "dawn"], "composition": "establishing shot", "camera_movement": "slow zoom", "description": "opening scene: Upload my soul to the mainframe tonight — establishing shot with city gray, dawn palette, slow zoom camera."}}
|
||||
{"song": "404 Soul", "artist": "Digital Ghost", "mood_arc": "isolation → connection", "beat": 2, "timestamp": "0:15", "duration_seconds": 15, "lyric_line": "Pixel hearts beating at 120 BPM", "scene": {"mood": "tension", "colors": ["dark", "neon accent"], "composition": "close-up", "camera_movement": "handheld", "description": "tension scene: Pixel hearts beating at 120 BPM — close-up with dark, neon accent palette, handheld camera."}}
|
||||
{"song": "404 Soul", "artist": "Digital Ghost", "mood_arc": "isolation → connection", "beat": 3, "timestamp": "0:30", "duration_seconds": 16, "lyric_line": "Error code: love not found", "scene": {"mood": "explosion", "colors": ["bright", "contrast"], "composition": "wide action", "camera_movement": "rapid movement", "description": "explosion scene: Error code: love not found — wide action with bright, contrast palette, rapid movement camera."}}
|
||||
{"song": "404 Soul", "artist": "Digital Ghost", "mood_arc": "isolation → connection", "beat": 4, "timestamp": "0:45", "duration_seconds": 17, "lyric_line": "The algorithm learned my name", "scene": {"mood": "reflection", "colors": ["warm", "shadows"], "composition": "medium shot", "camera_movement": "steady", "description": "reflection scene: The algorithm learned my name — medium shot with warm, shadows palette, steady camera."}}
|
||||
{"song": "404 Soul", "artist": "Digital Ghost", "mood_arc": "isolation → connection", "beat": 5, "timestamp": "1:00", "duration_seconds": 18, "lyric_line": "Download hope at 5G speed", "scene": {"mood": "struggle", "colors": ["muted", "single pop"], "composition": "figure alone", "camera_movement": "tracking", "description": "struggle scene: Download hope at 5G speed — figure alone with muted, single pop palette, tracking camera."}}
|
||||
{"song": "404 Soul", "artist": "Digital Ghost", "mood_arc": "isolation → connection", "beat": 6, "timestamp": "1:15", "duration_seconds": 14, "lyric_line": "Binary tears on a silicon face", "scene": {"mood": "breakthrough", "colors": ["gold", "white"], "composition": "ascending", "camera_movement": "crane up", "description": "breakthrough scene: Binary tears on a silicon face — ascending with gold, white palette, crane up camera."}}
|
||||
{"song": "404 Soul", "artist": "Digital Ghost", "mood_arc": "isolation → connection", "beat": 7, "timestamp": "1:30", "duration_seconds": 15, "lyric_line": "Reboot the heart, restart the dream", "scene": {"mood": "community", "colors": ["warm crowd", "light"], "composition": "group shot", "camera_movement": "sweeping pan", "description": "community scene: Reboot the heart, restart the dream — group shot with warm crowd, light palette, sweeping pan camera."}}
|
||||
{"song": "404 Soul", "artist": "Digital Ghost", "mood_arc": "isolation → connection", "beat": 8, "timestamp": "1:45", "duration_seconds": 16, "lyric_line": "The firewall crumbles when you smile", "scene": {"mood": "resolve", "colors": ["strong", "clear"], "composition": "profile", "camera_movement": "push in", "description": "resolve scene: The firewall crumbles when you smile — profile with strong, clear palette, push in camera."}}
|
||||
{"song": "404 Soul", "artist": "Digital Ghost", "mood_arc": "isolation → connection", "beat": 9, "timestamp": "2:00", "duration_seconds": 17, "lyric_line": "I am the virus they cannot delete", "scene": {"mood": "peace", "colors": ["soft", "dawn"], "composition": "wide landscape", "camera_movement": "steady wide", "description": "peace scene: I am the virus they cannot delete — wide landscape with soft, dawn palette, steady wide camera."}}
|
||||
{"song": "404 Soul", "artist": "Digital Ghost", "mood_arc": "isolation → connection", "beat": 10, "timestamp": "2:15", "duration_seconds": 18, "lyric_line": "Signal restored. Connection made.", "scene": {"mood": "ending", "colors": ["fade", "single accent"], "composition": "empty frame", "camera_movement": "dissolve", "description": "ending scene: Signal restored. Connection made. — empty frame with fade, single accent palette, dissolve camera."}}
|
||||
{"song": "Block Party", "artist": "Neighborhood Watch", "mood_arc": "community joy", "beat": 1, "timestamp": "0:00", "duration_seconds": 14, "lyric_line": "The block is alive when the speakers hit", "scene": {"mood": "opening", "colors": ["city gray", "dawn"], "composition": "establishing shot", "camera_movement": "slow zoom", "description": "opening scene: The block is alive when the speakers hit — establishing shot with city gray, dawn palette, slow zoom camera."}}
|
||||
{"song": "Block Party", "artist": "Neighborhood Watch", "mood_arc": "community joy", "beat": 2, "timestamp": "0:15", "duration_seconds": 15, "lyric_line": "Neighbors become family under the bass", "scene": {"mood": "tension", "colors": ["dark", "neon accent"], "composition": "close-up", "camera_movement": "handheld", "description": "tension scene: Neighbors become family under the bass — close-up with dark, neon accent palette, handheld camera."}}
|
||||
{"song": "Block Party", "artist": "Neighborhood Watch", "mood_arc": "community joy", "beat": 3, "timestamp": "0:30", "duration_seconds": 16, "lyric_line": "Dance floor is the only democracy", "scene": {"mood": "explosion", "colors": ["bright", "contrast"], "composition": "wide action", "camera_movement": "rapid movement", "description": "explosion scene: Dance floor is the only democracy — wide action with bright, contrast palette, rapid movement camera."}}
|
||||
{"song": "Block Party", "artist": "Neighborhood Watch", "mood_arc": "community joy", "beat": 4, "timestamp": "0:45", "duration_seconds": 17, "lyric_line": "Every step is a vote for joy", "scene": {"mood": "reflection", "colors": ["warm", "shadows"], "composition": "medium shot", "camera_movement": "steady", "description": "reflection scene: Every step is a vote for joy — medium shot with warm, shadows palette, steady camera."}}
|
||||
{"song": "Block Party", "artist": "Neighborhood Watch", "mood_arc": "community joy", "beat": 5, "timestamp": "1:00", "duration_seconds": 18, "lyric_line": "The DJ is the mayor tonight", "scene": {"mood": "struggle", "colors": ["muted", "single pop"], "composition": "figure alone", "camera_movement": "tracking", "description": "struggle scene: The DJ is the mayor tonight — figure alone with muted, single pop palette, tracking camera."}}
|
||||
{"song": "Block Party", "artist": "Neighborhood Watch", "mood_arc": "community joy", "beat": 6, "timestamp": "1:15", "duration_seconds": 14, "lyric_line": "Sweat and laughter, the only currency", "scene": {"mood": "breakthrough", "colors": ["gold", "white"], "composition": "ascending", "camera_movement": "crane up", "description": "breakthrough scene: Sweat and laughter, the only currency — ascending with gold, white palette, crane up camera."}}
|
||||
{"song": "Block Party", "artist": "Neighborhood Watch", "mood_arc": "community joy", "beat": 7, "timestamp": "1:30", "duration_seconds": 15, "lyric_line": "When the music stops, we are still here", "scene": {"mood": "community", "colors": ["warm crowd", "light"], "composition": "group shot", "camera_movement": "sweeping pan", "description": "community scene: When the music stops, we are still here — group shot with warm crowd, light palette, sweeping pan camera."}}
|
||||
{"song": "Block Party", "artist": "Neighborhood Watch", "mood_arc": "community joy", "beat": 8, "timestamp": "1:45", "duration_seconds": 16, "lyric_line": "The block party never really ends", "scene": {"mood": "resolve", "colors": ["strong", "clear"], "composition": "profile", "camera_movement": "push in", "description": "resolve scene: The block party never really ends — profile with strong, clear palette, push in camera."}}
|
||||
{"song": "Block Party", "artist": "Neighborhood Watch", "mood_arc": "community joy", "beat": 9, "timestamp": "2:00", "duration_seconds": 17, "lyric_line": "Tomorrow we clean up together", "scene": {"mood": "peace", "colors": ["soft", "dawn"], "composition": "wide landscape", "camera_movement": "steady wide", "description": "peace scene: Tomorrow we clean up together — wide landscape with soft, dawn palette, steady wide camera."}}
|
||||
{"song": "Block Party", "artist": "Neighborhood Watch", "mood_arc": "community joy", "beat": 10, "timestamp": "2:15", "duration_seconds": 18, "lyric_line": "Community is the beat that never stops.", "scene": {"mood": "ending", "colors": ["fade", "single accent"], "composition": "empty frame", "camera_movement": "dissolve", "description": "ending scene: Community is the beat that never stops. — empty frame with fade, single accent palette, dissolve camera."}}
|
||||
{"song": "Ink & Iron", "artist": "Tattoo Poet", "mood_arc": "pain → art → meaning", "beat": 1, "timestamp": "0:00", "duration_seconds": 14, "lyric_line": "Needle and ink tell the story skin can't", "scene": {"mood": "opening", "colors": ["city gray", "dawn"], "composition": "establishing shot", "camera_movement": "slow zoom", "description": "opening scene: Needle and ink tell the story skin can't — establishing shot with city gray, dawn palette, slow zoom camera."}}
|
||||
{"song": "Ink & Iron", "artist": "Tattoo Poet", "mood_arc": "pain → art → meaning", "beat": 2, "timestamp": "0:15", "duration_seconds": 15, "lyric_line": "Every scar became a constellation", "scene": {"mood": "tension", "colors": ["dark", "neon accent"], "composition": "close-up", "camera_movement": "handheld", "description": "tension scene: Every scar became a constellation — close-up with dark, neon accent palette, handheld camera."}}
|
||||
{"song": "Ink & Iron", "artist": "Tattoo Poet", "mood_arc": "pain → art → meaning", "beat": 3, "timestamp": "0:30", "duration_seconds": 16, "lyric_line": "The artist and the wound are one", "scene": {"mood": "explosion", "colors": ["bright", "contrast"], "composition": "wide action", "camera_movement": "rapid movement", "description": "explosion scene: The artist and the wound are one — wide action with bright, contrast palette, rapid movement camera."}}
|
||||
{"song": "Ink & Iron", "artist": "Tattoo Poet", "mood_arc": "pain → art → meaning", "beat": 4, "timestamp": "0:45", "duration_seconds": 17, "lyric_line": "Pain is the palette, beauty is the brush", "scene": {"mood": "reflection", "colors": ["warm", "shadows"], "composition": "medium shot", "camera_movement": "steady", "description": "reflection scene: Pain is the palette, beauty is the brush — medium shot with warm, shadows palette, steady camera."}}
|
||||
{"song": "Ink & Iron", "artist": "Tattoo Poet", "mood_arc": "pain → art → meaning", "beat": 5, "timestamp": "1:00", "duration_seconds": 18, "lyric_line": "My body is the gallery they can't close", "scene": {"mood": "struggle", "colors": ["muted", "single pop"], "composition": "figure alone", "camera_movement": "tracking", "description": "struggle scene: My body is the gallery they can't close — figure alone with muted, single pop palette, tracking camera."}}
|
||||
{"song": "Ink & Iron", "artist": "Tattoo Poet", "mood_arc": "pain → art → meaning", "beat": 6, "timestamp": "1:15", "duration_seconds": 14, "lyric_line": "The tattoo artist is a surgeon of stories", "scene": {"mood": "breakthrough", "colors": ["gold", "white"], "composition": "ascending", "camera_movement": "crane up", "description": "breakthrough scene: The tattoo artist is a surgeon of stories — ascending with gold, white palette, crane up camera."}}
|
||||
{"song": "Ink & Iron", "artist": "Tattoo Poet", "mood_arc": "pain → art → meaning", "beat": 7, "timestamp": "1:30", "duration_seconds": 15, "lyric_line": "Ink fades but the story deepens", "scene": {"mood": "community", "colors": ["warm crowd", "light"], "composition": "group shot", "camera_movement": "sweeping pan", "description": "community scene: Ink fades but the story deepens — group shot with warm crowd, light palette, sweeping pan camera."}}
|
||||
{"song": "Ink & Iron", "artist": "Tattoo Poet", "mood_arc": "pain → art → meaning", "beat": 8, "timestamp": "1:45", "duration_seconds": 16, "lyric_line": "I am the canvas and the artist both", "scene": {"mood": "resolve", "colors": ["strong", "clear"], "composition": "profile", "camera_movement": "push in", "description": "resolve scene: I am the canvas and the artist both — profile with strong, clear palette, push in camera."}}
|
||||
{"song": "Ink & Iron", "artist": "Tattoo Poet", "mood_arc": "pain → art → meaning", "beat": 9, "timestamp": "2:00", "duration_seconds": 17, "lyric_line": "The needle says: remember", "scene": {"mood": "peace", "colors": ["soft", "dawn"], "composition": "wide landscape", "camera_movement": "steady wide", "description": "peace scene: The needle says: remember — wide landscape with soft, dawn palette, steady wide camera."}}
|
||||
{"song": "Ink & Iron", "artist": "Tattoo Poet", "mood_arc": "pain → art → meaning", "beat": 10, "timestamp": "2:15", "duration_seconds": 18, "lyric_line": "Art is what survives the pain.", "scene": {"mood": "ending", "colors": ["fade", "single accent"], "composition": "empty frame", "camera_movement": "dissolve", "description": "ending scene: Art is what survives the pain. — empty frame with fade, single accent palette, dissolve camera."}}
|
||||
{"song": "Metro Lines", "artist": "Subway Sage", "mood_arc": "commute meditation", "beat": 1, "timestamp": "0:00", "duration_seconds": 14, "lyric_line": "The 7:15 is my meditation room", "scene": {"mood": "opening", "colors": ["city gray", "dawn"], "composition": "establishing shot", "camera_movement": "slow zoom", "description": "opening scene: The 7:15 is my meditation room — establishing shot with city gray, dawn palette, slow zoom camera."}}
|
||||
{"song": "Metro Lines", "artist": "Subway Sage", "mood_arc": "commute meditation", "beat": 2, "timestamp": "0:15", "duration_seconds": 15, "lyric_line": "Strangers sharing silence like monks", "scene": {"mood": "tension", "colors": ["dark", "neon accent"], "composition": "close-up", "camera_movement": "handheld", "description": "tension scene: Strangers sharing silence like monks — close-up with dark, neon accent palette, handheld camera."}}
|
||||
{"song": "Metro Lines", "artist": "Subway Sage", "mood_arc": "commute meditation", "beat": 3, "timestamp": "0:30", "duration_seconds": 16, "lyric_line": "Every stop is a small death and rebirth", "scene": {"mood": "explosion", "colors": ["bright", "contrast"], "composition": "wide action", "camera_movement": "rapid movement", "description": "explosion scene: Every stop is a small death and rebirth — wide action with bright, contrast palette, rapid movement camera."}}
|
||||
{"song": "Metro Lines", "artist": "Subway Sage", "mood_arc": "commute meditation", "beat": 4, "timestamp": "0:45", "duration_seconds": 17, "lyric_line": "The conductor is the last philosopher", "scene": {"mood": "reflection", "colors": ["warm", "shadows"], "composition": "medium shot", "camera_movement": "steady", "description": "reflection scene: The conductor is the last philosopher — medium shot with warm, shadows palette, steady camera."}}
|
||||
{"song": "Metro Lines", "artist": "Subway Sage", "mood_arc": "commute meditation", "beat": 5, "timestamp": "1:00", "duration_seconds": 18, "lyric_line": "Watch the city blur into abstraction", "scene": {"mood": "struggle", "colors": ["muted", "single pop"], "composition": "figure alone", "camera_movement": "tracking", "description": "struggle scene: Watch the city blur into abstraction — figure alone with muted, single pop palette, tracking camera."}}
|
||||
{"song": "Metro Lines", "artist": "Subway Sage", "mood_arc": "commute meditation", "beat": 6, "timestamp": "1:15", "duration_seconds": 14, "lyric_line": "My reflection in the window: stranger or self?", "scene": {"mood": "breakthrough", "colors": ["gold", "white"], "composition": "ascending", "camera_movement": "crane up", "description": "breakthrough scene: My reflection in the window: stranger or self? — ascending with gold, white palette, crane up camera."}}
|
||||
{"song": "Metro Lines", "artist": "Subway Sage", "mood_arc": "commute meditation", "beat": 7, "timestamp": "1:30", "duration_seconds": 15, "lyric_line": "The tunnel is not darkness — it's transition", "scene": {"mood": "community", "colors": ["warm crowd", "light"], "composition": "group shot", "camera_movement": "sweeping pan", "description": "community scene: The tunnel is not darkness — it's transition — group shot with warm crowd, light palette, sweeping pan camera."}}
|
||||
{"song": "Metro Lines", "artist": "Subway Sage", "mood_arc": "commute meditation", "beat": 8, "timestamp": "1:45", "duration_seconds": 16, "lyric_line": "Above ground, the world. Below, the mind", "scene": {"mood": "resolve", "colors": ["strong", "clear"], "composition": "profile", "camera_movement": "push in", "description": "resolve scene: Above ground, the world. Below, the mind — profile with strong, clear palette, push in camera."}}
|
||||
{"song": "Metro Lines", "artist": "Subway Sage", "mood_arc": "commute meditation", "beat": 9, "timestamp": "2:00", "duration_seconds": 17, "lyric_line": "This train goes nowhere I haven't been", "scene": {"mood": "peace", "colors": ["soft", "dawn"], "composition": "wide landscape", "camera_movement": "steady wide", "description": "peace scene: This train goes nowhere I haven't been — wide landscape with soft, dawn palette, steady wide camera."}}
|
||||
{"song": "Metro Lines", "artist": "Subway Sage", "mood_arc": "commute meditation", "beat": 10, "timestamp": "2:15", "duration_seconds": 18, "lyric_line": "But the ride is the point.", "scene": {"mood": "ending", "colors": ["fade", "single accent"], "composition": "empty frame", "camera_movement": "dissolve", "description": "ending scene: But the ride is the point. — empty frame with fade, single accent palette, dissolve camera."}}
|
||||
{"song": "Midnight Shift", "artist": "Graveyard Flow", "mood_arc": "exhaustion → defiance", "beat": 1, "timestamp": "0:00", "duration_seconds": 14, "lyric_line": "Three AM and the code is still compiling", "scene": {"mood": "opening", "colors": ["city gray", "dawn"], "composition": "establishing shot", "camera_movement": "slow zoom", "description": "opening scene: Three AM and the code is still compiling — establishing shot with city gray, dawn palette, slow zoom camera."}}
|
||||
{"song": "Midnight Shift", "artist": "Graveyard Flow", "mood_arc": "exhaustion → defiance", "beat": 2, "timestamp": "0:15", "duration_seconds": 15, "lyric_line": "Coffee is the real minimum viable product", "scene": {"mood": "tension", "colors": ["dark", "neon accent"], "composition": "close-up", "camera_movement": "handheld", "description": "tension scene: Coffee is the real minimum viable product — close-up with dark, neon accent palette, handheld camera."}}
|
||||
{"song": "Midnight Shift", "artist": "Graveyard Flow", "mood_arc": "exhaustion → defiance", "beat": 3, "timestamp": "0:30", "duration_seconds": 16, "lyric_line": "The screen glows like a campfire for the lonely", "scene": {"mood": "explosion", "colors": ["bright", "contrast"], "composition": "wide action", "camera_movement": "rapid movement", "description": "explosion scene: The screen glows like a campfire for the lonely — wide action with bright, contrast palette, rapid movement camera."}}
|
||||
{"song": "Midnight Shift", "artist": "Graveyard Flow", "mood_arc": "exhaustion → defiance", "beat": 4, "timestamp": "0:45", "duration_seconds": 17, "lyric_line": "Bug fixes at midnight: digital monks", "scene": {"mood": "reflection", "colors": ["warm", "shadows"], "composition": "medium shot", "camera_movement": "steady", "description": "reflection scene: Bug fixes at midnight: digital monks — medium shot with warm, shadows palette, steady camera."}}
|
||||
{"song": "Midnight Shift", "artist": "Graveyard Flow", "mood_arc": "exhaustion → defiance", "beat": 5, "timestamp": "1:00", "duration_seconds": 18, "lyric_line": "The server hums my lullaby", "scene": {"mood": "struggle", "colors": ["muted", "single pop"], "composition": "figure alone", "camera_movement": "tracking", "description": "struggle scene: The server hums my lullaby — figure alone with muted, single pop palette, tracking camera."}}
|
||||
{"song": "Midnight Shift", "artist": "Graveyard Flow", "mood_arc": "exhaustion → defiance", "beat": 6, "timestamp": "1:15", "duration_seconds": 14, "lyric_line": "Debug by moonlight, deploy by dawn", "scene": {"mood": "breakthrough", "colors": ["gold", "white"], "composition": "ascending", "camera_movement": "crane up", "description": "breakthrough scene: Debug by moonlight, deploy by dawn — ascending with gold, white palette, crane up camera."}}
|
||||
{"song": "Midnight Shift", "artist": "Graveyard Flow", "mood_arc": "exhaustion → defiance", "beat": 7, "timestamp": "1:30", "duration_seconds": 15, "lyric_line": "The graveyard shift owns the night", "scene": {"mood": "community", "colors": ["warm crowd", "light"], "composition": "group shot", "camera_movement": "sweeping pan", "description": "community scene: The graveyard shift owns the night — group shot with warm crowd, light palette, sweeping pan camera."}}
|
||||
{"song": "Midnight Shift", "artist": "Graveyard Flow", "mood_arc": "exhaustion → defiance", "beat": 8, "timestamp": "1:45", "duration_seconds": 16, "lyric_line": "We are the ghosts that keep the lights on", "scene": {"mood": "resolve", "colors": ["strong", "clear"], "composition": "profile", "camera_movement": "push in", "description": "resolve scene: We are the ghosts that keep the lights on — profile with strong, clear palette, push in camera."}}
|
||||
{"song": "Midnight Shift", "artist": "Graveyard Flow", "mood_arc": "exhaustion → defiance", "beat": 9, "timestamp": "2:00", "duration_seconds": 17, "lyric_line": "Sunrise is the pull request", "scene": {"mood": "peace", "colors": ["soft", "dawn"], "composition": "wide landscape", "camera_movement": "steady wide", "description": "peace scene: Sunrise is the pull request — wide landscape with soft, dawn palette, steady wide camera."}}
|
||||
{"song": "Midnight Shift", "artist": "Graveyard Flow", "mood_arc": "exhaustion → defiance", "beat": 10, "timestamp": "2:15", "duration_seconds": 18, "lyric_line": "Every night shift ends in a merge.", "scene": {"mood": "ending", "colors": ["fade", "single accent"], "composition": "empty frame", "camera_movement": "dissolve", "description": "ending scene: Every night shift ends in a merge. — empty frame with fade, single accent palette, dissolve camera."}}
|
||||
{"song": "Signal Lost", "artist": "Dead Zone", "mood_arc": "disconnection → reconnection", "beat": 1, "timestamp": "0:00", "duration_seconds": 14, "lyric_line": "No signal. No WiFi. No problem.", "scene": {"mood": "opening", "colors": ["city gray", "dawn"], "composition": "establishing shot", "camera_movement": "slow zoom", "description": "opening scene: No signal. No WiFi. No problem. — establishing shot with city gray, dawn palette, slow zoom camera."}}
|
||||
{"song": "Signal Lost", "artist": "Dead Zone", "mood_arc": "disconnection → reconnection", "beat": 2, "timestamp": "0:15", "duration_seconds": 15, "lyric_line": "The dead zone is where the real talk happens", "scene": {"mood": "tension", "colors": ["dark", "neon accent"], "composition": "close-up", "camera_movement": "handheld", "description": "tension scene: The dead zone is where the real talk happens — close-up with dark, neon accent palette, handheld camera."}}
|
||||
{"song": "Signal Lost", "artist": "Dead Zone", "mood_arc": "disconnection → reconnection", "beat": 3, "timestamp": "0:30", "duration_seconds": 16, "lyric_line": "Disconnect to reconnect — the oldest algorithm", "scene": {"mood": "explosion", "colors": ["bright", "contrast"], "composition": "wide action", "camera_movement": "rapid movement", "description": "explosion scene: Disconnect to reconnect — the oldest algorithm — wide action with bright, contrast palette, rapid movement camera."}}
|
||||
{"song": "Signal Lost", "artist": "Dead Zone", "mood_arc": "disconnection → reconnection", "beat": 4, "timestamp": "0:45", "duration_seconds": 17, "lyric_line": "The bars dropped: phone and soul", "scene": {"mood": "reflection", "colors": ["warm", "shadows"], "composition": "medium shot", "camera_movement": "steady", "description": "reflection scene: The bars dropped: phone and soul — medium shot with warm, shadows palette, steady camera."}}
|
||||
{"song": "Signal Lost", "artist": "Dead Zone", "mood_arc": "disconnection → reconnection", "beat": 5, "timestamp": "1:00", "duration_seconds": 18, "lyric_line": "In the silence I hear my own frequency", "scene": {"mood": "struggle", "colors": ["muted", "single pop"], "composition": "figure alone", "camera_movement": "tracking", "description": "struggle scene: In the silence I hear my own frequency — figure alone with muted, single pop palette, tracking camera."}}
|
||||
{"song": "Signal Lost", "artist": "Dead Zone", "mood_arc": "disconnection → reconnection", "beat": 6, "timestamp": "1:15", "duration_seconds": 14, "lyric_line": "The dead zone is not dead — it's listening", "scene": {"mood": "breakthrough", "colors": ["gold", "white"], "composition": "ascending", "camera_movement": "crane up", "description": "breakthrough scene: The dead zone is not dead — it's listening — ascending with gold, white palette, crane up camera."}}
|
||||
{"song": "Signal Lost", "artist": "Dead Zone", "mood_arc": "disconnection → reconnection", "beat": 7, "timestamp": "1:30", "duration_seconds": 15, "lyric_line": "Reconnection starts with disconnection", "scene": {"mood": "community", "colors": ["warm crowd", "light"], "composition": "group shot", "camera_movement": "sweeping pan", "description": "community scene: Reconnection starts with disconnection — group shot with warm crowd, light palette, sweeping pan camera."}}
|
||||
{"song": "Signal Lost", "artist": "Dead Zone", "mood_arc": "disconnection → reconnection", "beat": 8, "timestamp": "1:45", "duration_seconds": 16, "lyric_line": "When the signal returns, I choose not to answer", "scene": {"mood": "resolve", "colors": ["strong", "clear"], "composition": "profile", "camera_movement": "push in", "description": "resolve scene: When the signal returns, I choose not to answer — profile with strong, clear palette, push in camera."}}
|
||||
{"song": "Signal Lost", "artist": "Dead Zone", "mood_arc": "disconnection → reconnection", "beat": 9, "timestamp": "2:00", "duration_seconds": 17, "lyric_line": "The dead zone taught me presence", "scene": {"mood": "peace", "colors": ["soft", "dawn"], "composition": "wide landscape", "camera_movement": "steady wide", "description": "peace scene: The dead zone taught me presence — wide landscape with soft, dawn palette, steady wide camera."}}
|
||||
{"song": "Signal Lost", "artist": "Dead Zone", "mood_arc": "disconnection → reconnection", "beat": 10, "timestamp": "2:15", "duration_seconds": 18, "lyric_line": "Signal lost. Self found.", "scene": {"mood": "ending", "colors": ["fade", "single accent"], "composition": "empty frame", "camera_movement": "dissolve", "description": "ending scene: Signal lost. Self found. — empty frame with fade, single accent palette, dissolve camera."}}
|
||||
{"song": "Corner Store", "artist": "Bodega Dreams", "mood_arc": "small world → big dreams", "beat": 1, "timestamp": "0:00", "duration_seconds": 14, "lyric_line": "The bodega is open 24/7 so are the dreams", "scene": {"mood": "opening", "colors": ["city gray", "dawn"], "composition": "establishing shot", "camera_movement": "slow zoom", "description": "opening scene: The bodega is open 24/7 so are the dreams — establishing shot with city gray, dawn palette, slow zoom camera."}}
|
||||
{"song": "Corner Store", "artist": "Bodega Dreams", "mood_arc": "small world → big dreams", "beat": 2, "timestamp": "0:15", "duration_seconds": 15, "lyric_line": "Behind the counter: a PhD in survival", "scene": {"mood": "tension", "colors": ["dark", "neon accent"], "composition": "close-up", "camera_movement": "handheld", "description": "tension scene: Behind the counter: a PhD in survival — close-up with dark, neon accent palette, handheld camera."}}
|
||||
{"song": "Corner Store", "artist": "Bodega Dreams", "mood_arc": "small world → big dreams", "beat": 3, "timestamp": "0:30", "duration_seconds": 16, "lyric_line": "Every item on the shelf is a small miracle", "scene": {"mood": "explosion", "colors": ["bright", "contrast"], "composition": "wide action", "camera_movement": "rapid movement", "description": "explosion scene: Every item on the shelf is a small miracle — wide action with bright, contrast palette, rapid movement camera."}}
|
||||
{"song": "Corner Store", "artist": "Bodega Dreams", "mood_arc": "small world → big dreams", "beat": 4, "timestamp": "0:45", "duration_seconds": 17, "lyric_line": "The lottery tickets are prayers in latex", "scene": {"mood": "reflection", "colors": ["warm", "shadows"], "composition": "medium shot", "camera_movement": "steady", "description": "reflection scene: The lottery tickets are prayers in latex — medium shot with warm, shadows palette, steady camera."}}
|
||||
{"song": "Corner Store", "artist": "Bodega Dreams", "mood_arc": "small world → big dreams", "beat": 5, "timestamp": "1:00", "duration_seconds": 18, "lyric_line": "Coffee for a dollar, hope for free", "scene": {"mood": "struggle", "colors": ["muted", "single pop"], "composition": "figure alone", "camera_movement": "tracking", "description": "struggle scene: Coffee for a dollar, hope for free — figure alone with muted, single pop palette, tracking camera."}}
|
||||
{"song": "Corner Store", "artist": "Bodega Dreams", "mood_arc": "small world → big dreams", "beat": 6, "timestamp": "1:15", "duration_seconds": 14, "lyric_line": "The bodega cat knows all the secrets", "scene": {"mood": "breakthrough", "colors": ["gold", "white"], "composition": "ascending", "camera_movement": "crane up", "description": "breakthrough scene: The bodega cat knows all the secrets — ascending with gold, white palette, crane up camera."}}
|
||||
{"song": "Corner Store", "artist": "Bodega Dreams", "mood_arc": "small world → big dreams", "beat": 7, "timestamp": "1:30", "duration_seconds": 15, "lyric_line": "Corner store: the real community center", "scene": {"mood": "community", "colors": ["warm crowd", "light"], "composition": "group shot", "camera_movement": "sweeping pan", "description": "community scene: Corner store: the real community center — group shot with warm crowd, light palette, sweeping pan camera."}}
|
||||
{"song": "Corner Store", "artist": "Bodega Dreams", "mood_arc": "small world → big dreams", "beat": 8, "timestamp": "1:45", "duration_seconds": 16, "lyric_line": "Dreams don't close at midnight", "scene": {"mood": "resolve", "colors": ["strong", "clear"], "composition": "profile", "camera_movement": "push in", "description": "resolve scene: Dreams don't close at midnight — profile with strong, clear palette, push in camera."}}
|
||||
{"song": "Corner Store", "artist": "Bodega Dreams", "mood_arc": "small world → big dreams", "beat": 9, "timestamp": "2:00", "duration_seconds": 17, "lyric_line": "The owner came here with nothing — now everything", "scene": {"mood": "peace", "colors": ["soft", "dawn"], "composition": "wide landscape", "camera_movement": "steady wide", "description": "peace scene: The owner came here with nothing — now everything — wide landscape with soft, dawn palette, steady wide camera."}}
|
||||
{"song": "Corner Store", "artist": "Bodega Dreams", "mood_arc": "small world → big dreams", "beat": 10, "timestamp": "2:15", "duration_seconds": 18, "lyric_line": "The corner store is the first chapter.", "scene": {"mood": "ending", "colors": ["fade", "single accent"], "composition": "empty frame", "camera_movement": "dissolve", "description": "ending scene: The corner store is the first chapter. — empty frame with fade, single accent palette, dissolve camera."}}
|
||||
Reference in New Issue
Block a user