bin/ 230 CLI tools (ask-*, br-*, agent-*, roadid, carpool) scripts/ 99 automation scripts fleet/ Node configs and deployment workers/ Cloudflare Worker sources (roadpay, road-search, squad webhooks) roadc/ RoadC programming language roadnet/ Mesh network (5 APs, WireGuard) operator/ Memory system scripts config/ System configs dotfiles/ Shell configs docs/ Documentation BlackRoad OS — Pave Tomorrow. RoadChain-SHA2048: d1a24f55318d338b RoadChain-Identity: alexa@sovereign RoadChain-Full: d1a24f55318d338b24b60bad7be39286379c76ae5470817482100cb0ddbbcb97e147d07ac7243da0a9f0363e4e5c833d612b9c0df3a3cd20802465420278ef74875a5b77f55af6fe42a931b8b635b3d0d0b6bde9abf33dc42eea52bc03c951406d8cbe49f1a3d29b26a94dade05e9477f34a7d4d4c6ec4005c3c2ac54e73a68440c512c8e83fd9b1fe234750b898ef8f4032c23db173961fe225e67a0432b5293a9714f76c5c57ed5fdf35b9fb40fd73c03ebf88b7253c6a0575f5afb6a6b49b3bda310602fb1ef676859962dad2aebbb2875814b30eee0a8ba195e482d4cbc91d8819e7f38f6db53e8063401649c77bb994371473cabfb917fb53e8cbe73d60
224 lines
8.7 KiB
Bash
Executable File
224 lines
8.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# br-video — BlackRoad Video Production System
|
|
# Usage: br-video [list|preview|record|serve] [video-name]
|
|
|
|
set -e
|
|
|
|
PINK='\033[38;5;205m'
|
|
AMBER='\033[38;5;214m'
|
|
GREEN='\033[38;5;82m'
|
|
BLUE='\033[38;5;69m'
|
|
VIOLET='\033[38;5;135m'
|
|
RESET='\033[0m'
|
|
|
|
VIDEOS_DIR="$HOME/blackroad-operator/videos"
|
|
|
|
case "${1:-list}" in
|
|
list)
|
|
echo -e "${PINK}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
echo -e "${PINK} BlackRoad Video Library${RESET}"
|
|
echo -e "${PINK}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
echo ""
|
|
for category in brand product social tutorials roadtv; do
|
|
if [[ -d "$VIDEOS_DIR/$category" ]]; then
|
|
echo -e " ${VIOLET}▸ $category${RESET}"
|
|
for f in "$VIDEOS_DIR/$category"/*.html; do
|
|
[[ -f "$f" ]] || continue
|
|
name=$(basename "$f" .html)
|
|
title=$(grep -m1 '<title>' "$f" | sed 's/.*<title>\(.*\)<\/title>.*/\1/' 2>/dev/null || echo "$name")
|
|
# Detect aspect ratio
|
|
if grep -q 'width=1080.*height=1920\|width:1080.*height:1920' "$f" 2>/dev/null; then
|
|
ratio="9:16"
|
|
else
|
|
ratio="16:9"
|
|
fi
|
|
echo -e " ${GREEN}${name}${RESET} ${BLUE}[$ratio]${RESET} $title"
|
|
done
|
|
echo ""
|
|
fi
|
|
done
|
|
;;
|
|
|
|
preview|open)
|
|
video="${2:-}"
|
|
if [[ -z "$video" ]]; then
|
|
echo -e "${AMBER}Usage: br-video preview <category/name>${RESET}"
|
|
echo -e " Example: br-video preview brand/01-manifesto"
|
|
exit 1
|
|
fi
|
|
file="$VIDEOS_DIR/${video}.html"
|
|
if [[ ! -f "$file" ]]; then
|
|
# Try finding it
|
|
file=$(find "$VIDEOS_DIR" -name "${video}*.html" 2>/dev/null | head -1)
|
|
fi
|
|
if [[ -f "$file" ]]; then
|
|
echo -e "${GREEN}Opening: $file${RESET}"
|
|
open "$file"
|
|
else
|
|
echo -e "${AMBER}Video not found: $video${RESET}"
|
|
exit 1
|
|
fi
|
|
;;
|
|
|
|
serve)
|
|
port="${2:-8888}"
|
|
echo -e "${PINK}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
echo -e "${PINK} Video Preview Server${RESET}"
|
|
echo -e "${PINK}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
echo ""
|
|
echo -e " ${GREEN}Serving at: http://localhost:${port}${RESET}"
|
|
echo -e " ${BLUE}Videos dir: $VIDEOS_DIR${RESET}"
|
|
echo ""
|
|
echo -e " ${VIOLET}Brand:${RESET}"
|
|
for f in "$VIDEOS_DIR/brand"/*.html; do
|
|
[[ -f "$f" ]] && echo -e " http://localhost:${port}/brand/$(basename "$f")"
|
|
done
|
|
echo -e " ${VIOLET}Product:${RESET}"
|
|
for f in "$VIDEOS_DIR/product"/*.html; do
|
|
[[ -f "$f" ]] && echo -e " http://localhost:${port}/product/$(basename "$f")"
|
|
done
|
|
echo -e " ${VIOLET}Social:${RESET}"
|
|
for f in "$VIDEOS_DIR/social"/*.html; do
|
|
[[ -f "$f" ]] && echo -e " http://localhost:${port}/social/$(basename "$f")"
|
|
done
|
|
echo -e " ${VIOLET}Tutorials:${RESET}"
|
|
for f in "$VIDEOS_DIR/tutorials"/*.html; do
|
|
[[ -f "$f" ]] && echo -e " http://localhost:${port}/tutorials/$(basename "$f")"
|
|
done
|
|
echo -e " ${VIOLET}RoadTV:${RESET}"
|
|
for f in "$VIDEOS_DIR/roadtv"/*.html; do
|
|
[[ -f "$f" ]] && echo -e " http://localhost:${port}/roadtv/$(basename "$f")"
|
|
done
|
|
echo ""
|
|
cd "$VIDEOS_DIR" && python3 -m http.server "$port"
|
|
;;
|
|
|
|
record)
|
|
video="${2:-}"
|
|
if [[ -z "$video" ]]; then
|
|
echo -e "${AMBER}Usage: br-video record <category/name> [duration-seconds]${RESET}"
|
|
exit 1
|
|
fi
|
|
duration="${3:-30}"
|
|
file="$VIDEOS_DIR/${video}.html"
|
|
if [[ ! -f "$file" ]]; then
|
|
file=$(find "$VIDEOS_DIR" -name "${video}*.html" 2>/dev/null | head -1)
|
|
fi
|
|
if [[ ! -f "$file" ]]; then
|
|
echo -e "${AMBER}Video not found: $video${RESET}"
|
|
exit 1
|
|
fi
|
|
|
|
output_dir="$HOME/blackroad-operator/videos/output"
|
|
mkdir -p "$output_dir"
|
|
output_name=$(basename "$file" .html)
|
|
output_file="$output_dir/${output_name}-$(date +%Y%m%d).mp4"
|
|
|
|
# Detect dimensions
|
|
if grep -q 'width=1080.*height=1920\|width:1080.*height:1920' "$file" 2>/dev/null; then
|
|
width=1080; height=1920
|
|
else
|
|
width=1920; height=1080
|
|
fi
|
|
|
|
echo -e "${PINK}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
echo -e "${PINK} Recording: $(basename "$file")${RESET}"
|
|
echo -e "${PINK}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
echo ""
|
|
echo -e " ${BLUE}Resolution:${RESET} ${width}x${height}"
|
|
echo -e " ${BLUE}Duration:${RESET} ${duration}s"
|
|
echo -e " ${BLUE}Output:${RESET} $output_file"
|
|
echo ""
|
|
|
|
# Check for puppeteer/playwright
|
|
if command -v npx &>/dev/null && npx playwright --version &>/dev/null 2>&1; then
|
|
echo -e "${GREEN}Using Playwright to record...${RESET}"
|
|
# Start local server
|
|
cd "$VIDEOS_DIR"
|
|
python3 -m http.server 9876 &
|
|
SERVER_PID=$!
|
|
sleep 1
|
|
|
|
# Record with playwright
|
|
node -e "
|
|
const { chromium } = require('playwright');
|
|
(async () => {
|
|
const browser = await chromium.launch();
|
|
const page = await browser.newPage({ viewport: { width: $width, height: $height } });
|
|
await page.goto('http://localhost:9876/$(dirname "$video")/$(basename "$file")');
|
|
|
|
// Screenshot sequence for ffmpeg
|
|
const frames = $duration * 30;
|
|
const outputDir = '$output_dir/frames-${output_name}';
|
|
require('fs').mkdirSync(outputDir, { recursive: true });
|
|
|
|
for (let i = 0; i < frames; i++) {
|
|
await page.screenshot({ path: outputDir + '/frame-' + String(i).padStart(5, '0') + '.png' });
|
|
await page.waitForTimeout(33);
|
|
}
|
|
|
|
await browser.close();
|
|
console.log('Frames captured. Converting to video...');
|
|
})();
|
|
" 2>/dev/null
|
|
|
|
kill $SERVER_PID 2>/dev/null
|
|
|
|
# Convert frames to video with ffmpeg
|
|
if command -v ffmpeg &>/dev/null; then
|
|
ffmpeg -framerate 30 -i "$output_dir/frames-${output_name}/frame-%05d.png" \
|
|
-c:v libx264 -pix_fmt yuv420p -crf 18 "$output_file" -y 2>/dev/null
|
|
rm -rf "$output_dir/frames-${output_name}"
|
|
echo -e "${GREEN}✓ Video saved: $output_file${RESET}"
|
|
else
|
|
echo -e "${AMBER}ffmpeg not found. Frames saved to: $output_dir/frames-${output_name}/${RESET}"
|
|
fi
|
|
else
|
|
echo -e "${AMBER}For automated recording, install playwright: npm i -g playwright${RESET}"
|
|
echo -e "${AMBER}Or use OBS/screen recording with: br-video preview $video${RESET}"
|
|
echo ""
|
|
echo -e "${GREEN}Opening in browser for manual recording...${RESET}"
|
|
open "$file"
|
|
fi
|
|
;;
|
|
|
|
create)
|
|
shift
|
|
exec br-video-create "$@"
|
|
;;
|
|
|
|
example)
|
|
echo -e "${GREEN}Generating example config...${RESET}"
|
|
br-video-create --example > "$VIDEOS_DIR/example-config.json"
|
|
echo -e "${GREEN}✓ Saved: $VIDEOS_DIR/example-config.json${RESET}"
|
|
echo -e "${BLUE} Edit it, then: br-video create example-config.json output.html${RESET}"
|
|
;;
|
|
|
|
types)
|
|
br-video-create --types
|
|
;;
|
|
|
|
*)
|
|
echo -e "${PINK}br-video${RESET} — BlackRoad Video Production"
|
|
echo ""
|
|
echo -e " ${GREEN}list${RESET} List all videos"
|
|
echo -e " ${GREEN}preview${RESET} <name> Open video in browser"
|
|
echo -e " ${GREEN}serve${RESET} [port] Start preview server (default: 8888)"
|
|
echo -e " ${GREEN}record${RESET} <name> [seconds] Record video to MP4"
|
|
echo -e " ${GREEN}create${RESET} <config> [output] Generate video from JSON config"
|
|
echo -e " ${GREEN}example${RESET} Generate example config file"
|
|
echo -e " ${GREEN}types${RESET} List available scene types"
|
|
echo ""
|
|
echo -e " ${VIOLET}Scene Types:${RESET}"
|
|
echo -e " title, subtitle, bigtext, quote, stats, bullets, cards,"
|
|
echo -e " timeline, terminal, comparison, code, split, flow,"
|
|
echo -e " metrics, image, logos, cta, countdown, reveal"
|
|
echo ""
|
|
echo -e " Examples:"
|
|
echo -e " br-video preview brand/01-manifesto"
|
|
echo -e " br-video serve 8888"
|
|
echo -e " br-video record social/01-30s-intro 30"
|
|
echo -e " br-video create my-config.json videos/brand/03-new.html"
|
|
;;
|
|
esac
|