feat(peertube): Add transcript extraction & AI analysis tool

New CLI: peertube-analyse
- Extract video metadata via yt-dlp
- Download existing PeerTube subtitles (VTT)
- Fallback to Whisper local transcription (medium model)
- Claude AI analysis with structured intelligence report

Features:
- POSIX-compatible (OpenWrt, Alpine, Debian)
- Modular pipeline with graceful degradation
- Colored terminal output with status indicators
- Configurable Whisper model and language
- Truncation for large transcripts (12k chars)

CLI flags:
  --url <url>         Video URL
  --no-whisper        Subtitles only
  --force-whisper     Force transcription
  --no-analyse        Skip Claude analysis
  --model <name>      Whisper model
  --lang <code>       Language code

Output structure:
  ./output/<slug>/
    ├── <slug>.meta.json
    ├── <slug>.transcript.txt
    └── <slug>.analyse.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-02-21 18:15:55 +01:00
parent df58e96a9a
commit ab49e19c32
2 changed files with 781 additions and 1 deletions

View File

@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=secubox-app-peertube PKG_NAME:=secubox-app-peertube
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_VERSION:=1.0.0 PKG_VERSION:=1.1.0
PKG_ARCH:=all PKG_ARCH:=all
PKG_MAINTAINER:=CyberMind Studio <contact@cybermind.fr> PKG_MAINTAINER:=CyberMind Studio <contact@cybermind.fr>
PKG_LICENSE:=AGPL-3.0 PKG_LICENSE:=AGPL-3.0
@ -22,6 +22,7 @@ define Package/secubox-app-peertube/description
PeerTube federated video streaming platform. PeerTube federated video streaming platform.
Runs in an LXC Debian container with PostgreSQL, Redis, and Node.js. Runs in an LXC Debian container with PostgreSQL, Redis, and Node.js.
Supports video hosting, live streaming, and ActivityPub federation. Supports video hosting, live streaming, and ActivityPub federation.
Includes peertube-analyse: transcript extraction and Claude AI analysis.
endef endef
define Package/secubox-app-peertube/conffiles define Package/secubox-app-peertube/conffiles
@ -40,6 +41,7 @@ define Package/secubox-app-peertube/install
$(INSTALL_DIR) $(1)/usr/sbin $(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_BIN) ./files/usr/sbin/peertubectl $(1)/usr/sbin/peertubectl $(INSTALL_BIN) ./files/usr/sbin/peertubectl $(1)/usr/sbin/peertubectl
$(INSTALL_BIN) ./files/usr/sbin/peertube-analyse $(1)/usr/sbin/peertube-analyse
endef endef
$(eval $(call BuildPackage,secubox-app-peertube)) $(eval $(call BuildPackage,secubox-app-peertube))

View File

@ -0,0 +1,778 @@
#!/bin/sh
# PeerTube Video Transcript Extraction & AI Analysis
# SecuBox Intelligence Module
# Compatible: OpenWrt, Alpine, Debian, Ubuntu
set -e
#=============================================================================
# CONFIGURATION
#=============================================================================
SCRIPT_VERSION="1.0.0"
PEERTUBE_INSTANCE="${PEERTUBE_INSTANCE:-tube.gk2.secubox.in}"
OUTPUT_BASE="${OUTPUT_BASE:-./output}"
WHISPER_MODEL="${WHISPER_MODEL:-medium}"
WHISPER_LANG="${WHISPER_LANG:-fr}"
CLAUDE_MODEL="${CLAUDE_MODEL:-claude-sonnet-4-6}"
MAX_TRANSCRIPT_CHARS=12000
MAX_TOKENS=2000
#=============================================================================
# COLORS & LOGGING
#=============================================================================
# Check if terminal supports colors
if [ -t 1 ] && command -v tput >/dev/null 2>&1; then
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
YELLOW=$(tput setaf 3)
CYAN=$(tput setaf 6)
BOLD=$(tput bold)
NC=$(tput sgr0)
else
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
fi
log_info() { printf "%b[INFO]%b %s\n" "$CYAN" "$NC" "$1"; }
log_ok() { printf "%b[OK]%b %s\n" "$GREEN" "$NC" "$1"; }
log_warn() { printf "%b[WARN]%b %s\n" "$YELLOW" "$NC" "$1"; }
log_error() { printf "%b[ERROR]%b %s\n" "$RED" "$NC" "$1" >&2; }
log_step() { printf "\n%b==> %s%b\n" "$BOLD$CYAN" "$1" "$NC"; }
#=============================================================================
# DEPENDENCY CHECK
#=============================================================================
check_dependencies() {
log_step "Checking dependencies"
local missing=""
local deps="yt-dlp ffmpeg jq curl"
for dep in $deps; do
if command -v "$dep" >/dev/null 2>&1; then
log_ok "$dep found: $(command -v "$dep")"
else
log_error "$dep not found"
missing="$missing $dep"
fi
done
# Whisper check (optional but warned)
if command -v whisper >/dev/null 2>&1; then
log_ok "whisper found: $(command -v whisper)"
WHISPER_CMD="whisper"
elif command -v whisper-cpp >/dev/null 2>&1; then
log_ok "whisper-cpp found: $(command -v whisper-cpp)"
WHISPER_CMD="whisper-cpp"
elif command -v main >/dev/null 2>&1 && [ -f "$(dirname "$(command -v main)")/models/ggml-medium.bin" ]; then
log_ok "whisper.cpp (main) found"
WHISPER_CMD="main"
else
log_warn "whisper not found - transcription will only work with existing subtitles"
WHISPER_CMD=""
fi
# API key check
if [ -z "$ANTHROPIC_API_KEY" ]; then
log_warn "ANTHROPIC_API_KEY not set - AI analysis disabled"
else
log_ok "ANTHROPIC_API_KEY configured"
fi
if [ -n "$missing" ]; then
log_error "Missing required dependencies:$missing"
return 1
fi
return 0
}
#=============================================================================
# UTILITY FUNCTIONS
#=============================================================================
# Extract video ID from PeerTube URL
extract_video_id() {
local url="$1"
# Handle various URL formats:
# https://instance/w/VIDEO_ID
# https://instance/videos/watch/VIDEO_ID
# https://instance/videos/watch/VIDEO_UUID
echo "$url" | sed -E 's|.*/w/([^/?]+).*|\1|; s|.*/videos/watch/([^/?]+).*|\1|'
}
# Generate slug from title
generate_slug() {
echo "$1" | tr '[:upper:]' '[:lower:]' | \
sed -E 's/[àáâãäå]/a/g; s/[èéêë]/e/g; s/[ìíîï]/i/g; s/[òóôõö]/o/g; s/[ùúûü]/u/g; s/[ç]/c/g' | \
sed -E 's/[^a-z0-9]+/-/g; s/^-+|-+$//g' | \
cut -c1-50
}
# Clean VTT to plain text
vtt_to_text() {
local vtt_file="$1"
local txt_file="$2"
# Remove VTT headers, timestamps, positioning, and duplicates
sed -E '
/^WEBVTT/d
/^Kind:/d
/^Language:/d
/^NOTE/d
/^[0-9]+$/d
/^[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}/d
/^$/d
s/<[^>]*>//g
s/&nbsp;/ /g
s/&amp;/\&/g
s/&lt;/</g
s/&gt;/>/g
' "$vtt_file" | \
awk '!seen[$0]++' | \
tr '\n' ' ' | \
sed -E 's/ +/ /g; s/^ +| +$//g' > "$txt_file"
}
# Truncate text to max chars while preserving word boundaries
truncate_text() {
local text="$1"
local max="$2"
if [ ${#text} -le "$max" ]; then
echo "$text"
else
echo "$text" | cut -c1-"$max" | sed 's/[^ ]*$//'
echo "... [TRUNCATED]"
fi
}
#=============================================================================
# 1. METADATA EXTRACTION
#=============================================================================
extract_metadata() {
local url="$1"
local output_dir="$2"
local slug="$3"
log_step "Extracting metadata"
local meta_file="$output_dir/${slug}.meta.json"
# Use yt-dlp to dump JSON metadata
if yt-dlp --dump-json --no-warnings "$url" 2>/dev/null > "$meta_file.tmp"; then
# Extract relevant fields with jq
jq '{
id: .id,
title: .title,
description: .description,
duration: .duration,
duration_string: .duration_string,
upload_date: .upload_date,
uploader: .uploader,
uploader_id: .uploader_id,
channel: .channel,
view_count: .view_count,
like_count: .like_count,
tags: .tags,
categories: .categories,
webpage_url: .webpage_url,
thumbnail: .thumbnail,
language: .language,
subtitles: (.subtitles | keys),
automatic_captions: (.automatic_captions | keys)
}' "$meta_file.tmp" > "$meta_file"
rm -f "$meta_file.tmp"
log_ok "Metadata saved to $meta_file"
# Display summary
local title=$(jq -r '.title' "$meta_file")
local duration=$(jq -r '.duration_string // .duration' "$meta_file")
local uploader=$(jq -r '.uploader // .channel // "Unknown"' "$meta_file")
printf " Title: %s\n" "$title"
printf " Duration: %s\n" "$duration"
printf " Uploader: %s\n" "$uploader"
return 0
else
log_error "Failed to extract metadata"
return 1
fi
}
#=============================================================================
# 2. SUBTITLE DOWNLOAD & CONVERSION
#=============================================================================
check_peertube_captions() {
local video_id="$1"
local instance="$2"
log_info "Checking PeerTube captions API..."
local api_url="https://${instance}/api/v1/videos/${video_id}/captions"
local response
response=$(curl -s -w "\n%{http_code}" "$api_url" 2>/dev/null)
local http_code=$(echo "$response" | tail -n1)
local body=$(echo "$response" | sed '$d')
if [ "$http_code" = "200" ]; then
local caption_count=$(echo "$body" | jq '.total // 0')
if [ "$caption_count" -gt 0 ]; then
log_ok "Found $caption_count caption(s) via API"
echo "$body" | jq -r '.data[].language.id' 2>/dev/null
return 0
fi
fi
return 1
}
download_subtitles() {
local url="$1"
local output_dir="$2"
local slug="$3"
local lang="${4:-fr}"
log_step "Downloading subtitles"
local vtt_file="$output_dir/${slug}.${lang}.vtt"
local txt_file="$output_dir/${slug}.transcript.txt"
# Try to download subtitles with yt-dlp
if yt-dlp --write-sub --write-auto-sub --sub-lang "$lang,en" \
--sub-format vtt --skip-download \
-o "$output_dir/${slug}" "$url" 2>/dev/null; then
# Find downloaded VTT file
local found_vtt=$(find "$output_dir" -name "${slug}*.vtt" -type f | head -1)
if [ -n "$found_vtt" ] && [ -f "$found_vtt" ]; then
# Rename to standard name
mv "$found_vtt" "$vtt_file" 2>/dev/null || cp "$found_vtt" "$vtt_file"
log_ok "Subtitles downloaded: $vtt_file"
# Convert to plain text
vtt_to_text "$vtt_file" "$txt_file"
log_ok "Converted to text: $txt_file"
local word_count=$(wc -w < "$txt_file" | tr -d ' ')
printf " Word count: %s\n" "$word_count"
return 0
fi
fi
log_warn "No subtitles available for download"
return 1
}
#=============================================================================
# 3. WHISPER TRANSCRIPTION
#=============================================================================
extract_audio() {
local url="$1"
local output_dir="$2"
local slug="$3"
log_info "Extracting audio..."
local audio_file="$output_dir/${slug}.audio.wav"
# Download and convert to 16kHz mono WAV (Whisper optimal format)
if yt-dlp -x --audio-format wav -o "$output_dir/${slug}.%(ext)s" "$url" 2>/dev/null; then
# Convert to Whisper-optimal format
local downloaded=$(find "$output_dir" -name "${slug}.*" -type f | grep -E '\.(wav|mp3|m4a|opus|webm)$' | head -1)
if [ -n "$downloaded" ]; then
ffmpeg -y -i "$downloaded" -vn -ac 1 -ar 16000 -f wav "$audio_file" 2>/dev/null
rm -f "$downloaded"
log_ok "Audio extracted: $audio_file"
return 0
fi
fi
log_error "Failed to extract audio"
return 1
}
run_whisper() {
local audio_file="$1"
local output_dir="$2"
local slug="$3"
local model="$4"
local lang="$5"
log_step "Running Whisper transcription"
if [ -z "$WHISPER_CMD" ]; then
log_error "Whisper not available"
return 1
fi
local txt_file="$output_dir/${slug}.transcript.txt"
log_info "Model: $model, Language: $lang"
log_info "This may take a while..."
case "$WHISPER_CMD" in
whisper)
# OpenAI Whisper Python
whisper "$audio_file" \
--model "$model" \
--language "$lang" \
--output_format txt \
--output_dir "$output_dir" \
--verbose False 2>/dev/null
# Rename output
local whisper_out="$output_dir/$(basename "$audio_file" .wav).txt"
[ -f "$whisper_out" ] && mv "$whisper_out" "$txt_file"
;;
whisper-cpp|main)
# whisper.cpp
local model_path="${WHISPER_MODELS_PATH:-$HOME/.cache/whisper}/ggml-${model}.bin"
if [ ! -f "$model_path" ]; then
log_warn "Model not found: $model_path"
log_info "Downloading model..."
# Try to download model
mkdir -p "$(dirname "$model_path")"
curl -L "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-${model}.bin" \
-o "$model_path" 2>/dev/null || {
log_error "Failed to download model"
return 1
}
fi
"$WHISPER_CMD" -m "$model_path" -l "$lang" -otxt -of "$output_dir/${slug}.transcript" "$audio_file" 2>/dev/null
;;
esac
if [ -f "$txt_file" ]; then
# Clean up the transcript
sed -i 's/\[.*\]//g; s/ */ /g' "$txt_file" 2>/dev/null || \
sed 's/\[.*\]//g; s/ */ /g' "$txt_file" > "$txt_file.tmp" && mv "$txt_file.tmp" "$txt_file"
local word_count=$(wc -w < "$txt_file" | tr -d ' ')
log_ok "Transcription complete: $word_count words"
return 0
else
log_error "Whisper transcription failed"
return 1
fi
}
#=============================================================================
# 4. CLAUDE AI ANALYSIS
#=============================================================================
analyse_with_claude() {
local meta_file="$1"
local transcript_file="$2"
local output_dir="$3"
local slug="$4"
log_step "Running Claude AI analysis"
if [ -z "$ANTHROPIC_API_KEY" ]; then
log_error "ANTHROPIC_API_KEY not set"
return 1
fi
if [ ! -f "$transcript_file" ]; then
log_error "Transcript file not found: $transcript_file"
return 1
fi
local analysis_file="$output_dir/${slug}.analyse.md"
# Read metadata
local title=$(jq -r '.title // "Unknown"' "$meta_file" 2>/dev/null)
local duration=$(jq -r '.duration_string // .duration // "Unknown"' "$meta_file" 2>/dev/null)
local uploader=$(jq -r '.uploader // .channel // "Unknown"' "$meta_file" 2>/dev/null)
local upload_date=$(jq -r '.upload_date // "Unknown"' "$meta_file" 2>/dev/null)
local tags=$(jq -r '.tags | if . then join(", ") else "None" end' "$meta_file" 2>/dev/null)
local url=$(jq -r '.webpage_url // "Unknown"' "$meta_file" 2>/dev/null)
# Read and truncate transcript
local transcript=$(cat "$transcript_file")
local transcript_len=${#transcript}
if [ "$transcript_len" -gt "$MAX_TRANSCRIPT_CHARS" ]; then
log_warn "Transcript truncated from $transcript_len to $MAX_TRANSCRIPT_CHARS chars"
transcript=$(echo "$transcript" | head -c "$MAX_TRANSCRIPT_CHARS")
transcript="${transcript}... [TRUNCATED - Original: ${transcript_len} chars]"
fi
# Escape special characters for JSON
transcript=$(echo "$transcript" | jq -Rs '.')
title=$(echo "$title" | jq -Rs '.' | sed 's/^"//;s/"$//')
log_info "Calling Claude API ($CLAUDE_MODEL)..."
# Build the API request
local system_prompt="Tu es un analyste expert en renseignement, cybersécurité et géopolitique. Tu analyses des transcripts vidéo de manière structurée et rigoureuse. Tu réponds toujours en français."
local user_prompt="Analyse le transcript vidéo suivant:
=== MÉTADONNÉES ===
Titre: ${title}
Durée: ${duration}
Auteur: ${uploader}
Date: ${upload_date}
Tags: ${tags}
URL: ${url}
=== TRANSCRIPT ===
${transcript}
=== INSTRUCTIONS ===
Produis une analyse structurée en Markdown comprenant:
1. **Résumé exécutif** (5 lignes maximum)
2. **Thèmes principaux et sous-thèmes**
3. **Acteurs / entités mentionnés** (personnes, organisations, pays)
4. **Points factuels clés et révélations notables**
5. **Angle narratif et biais éventuels**
6. **Pertinence pour un professionnel en cybersécurité et renseignement**
7. **Questions ouvertes ou points à approfondir**
Sois factuel, précis et critique dans ton analyse."
# Make API call
local response
response=$(curl -s -w "\n%{http_code}" "https://api.anthropic.com/v1/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d "$(jq -n \
--arg model "$CLAUDE_MODEL" \
--argjson max_tokens "$MAX_TOKENS" \
--arg system "$system_prompt" \
--arg user "$user_prompt" \
'{
model: $model,
max_tokens: $max_tokens,
system: $system,
messages: [
{role: "user", content: $user}
]
}')" 2>/dev/null)
local http_code=$(echo "$response" | tail -n1)
local body=$(echo "$response" | sed '$d')
if [ "$http_code" != "200" ]; then
log_error "API error (HTTP $http_code)"
echo "$body" | jq -r '.error.message // .' 2>/dev/null | head -3
return 1
fi
# Extract and save the analysis
local analysis=$(echo "$body" | jq -r '.content[0].text // empty')
if [ -z "$analysis" ]; then
log_error "Empty response from Claude"
return 1
fi
# Create Markdown file with header
cat > "$analysis_file" << EOF
# Analyse: ${title}
**Source:** ${url}
**Durée:** ${duration}
**Auteur:** ${uploader}
**Date:** ${upload_date}
**Analysé le:** $(date +"%Y-%m-%d %H:%M")
**Modèle:** ${CLAUDE_MODEL}
---
${analysis}
---
*Analyse générée automatiquement par SecuBox Intelligence Module v${SCRIPT_VERSION}*
EOF
log_ok "Analysis saved to $analysis_file"
# Display summary in terminal
printf "\n%b=== RÉSUMÉ ===%b\n" "$BOLD$GREEN" "$NC"
echo "$analysis" | sed -n '/Résumé exécutif/,/^##\|^[0-9]\./p' | head -10
return 0
}
#=============================================================================
# MAIN PIPELINE
#=============================================================================
process_video() {
local url="$1"
log_step "Processing video: $url"
# Extract video ID and create output directory
local video_id=$(extract_video_id "$url")
local instance=$(echo "$url" | sed -E 's|https?://([^/]+).*|\1|')
log_info "Video ID: $video_id"
log_info "Instance: $instance"
# Create temporary metadata to get title for slug
local temp_meta=$(mktemp)
if ! yt-dlp --dump-json --no-warnings "$url" 2>/dev/null > "$temp_meta"; then
log_error "Failed to fetch video info"
rm -f "$temp_meta"
return 1
fi
local title=$(jq -r '.title // "video"' "$temp_meta")
local slug=$(generate_slug "$title")
slug="${slug:-$video_id}"
rm -f "$temp_meta"
log_info "Slug: $slug"
# Create output directory
local output_dir="$OUTPUT_BASE/$slug"
mkdir -p "$output_dir"
log_ok "Output directory: $output_dir"
# 1. Extract metadata
extract_metadata "$url" "$output_dir" "$slug" || {
log_warn "Metadata extraction failed, continuing..."
}
local transcript_file="$output_dir/${slug}.transcript.txt"
local has_transcript=0
# 2. Try to download existing subtitles
if [ "$NO_WHISPER" != "1" ] || [ "$FORCE_WHISPER" != "1" ]; then
# Check PeerTube API first
check_peertube_captions "$video_id" "$instance" 2>/dev/null
if download_subtitles "$url" "$output_dir" "$slug" "$WHISPER_LANG"; then
has_transcript=1
fi
fi
# 3. Run Whisper if needed
if [ "$has_transcript" = "0" ] || [ "$FORCE_WHISPER" = "1" ]; then
if [ "$NO_WHISPER" = "1" ]; then
log_warn "Whisper disabled, no transcript available"
elif [ -n "$WHISPER_CMD" ]; then
local audio_file="$output_dir/${slug}.audio.wav"
if extract_audio "$url" "$output_dir" "$slug"; then
if run_whisper "$audio_file" "$output_dir" "$slug" "$WHISPER_MODEL" "$WHISPER_LANG"; then
has_transcript=1
fi
# Clean up audio file
rm -f "$audio_file"
fi
else
log_warn "No Whisper available and no subtitles found"
fi
fi
# 4. Run Claude analysis
if [ "$NO_ANALYSE" != "1" ] && [ "$has_transcript" = "1" ]; then
local meta_file="$output_dir/${slug}.meta.json"
analyse_with_claude "$meta_file" "$transcript_file" "$output_dir" "$slug" || {
log_warn "Analysis failed"
}
elif [ "$NO_ANALYSE" = "1" ]; then
log_info "Analysis disabled (--no-analyse)"
else
log_warn "No transcript available for analysis"
fi
# Summary
log_step "Processing complete"
printf "\nOutput files in %s:\n" "$output_dir"
ls -la "$output_dir" 2>/dev/null | tail -n +2
return 0
}
#=============================================================================
# CLI PARSING
#=============================================================================
show_help() {
cat << EOF
${BOLD}PeerTube Video Transcript & Analysis Tool${NC}
SecuBox Intelligence Module v${SCRIPT_VERSION}
${BOLD}Usage:${NC}
$(basename "$0") [OPTIONS] --url <video_url>
$(basename "$0") [OPTIONS] <video_url>
${BOLD}Options:${NC}
--url <url> PeerTube video URL
--no-whisper Disable Whisper (subtitles only)
--force-whisper Force Whisper even if subtitles exist
--no-analyse Download/transcribe without Claude analysis
--model <name> Whisper model (tiny, base, small, medium, large-v3)
Default: ${WHISPER_MODEL}
--lang <code> Whisper language code (fr, en, de, etc.)
Default: ${WHISPER_LANG}
--output <dir> Output base directory
Default: ${OUTPUT_BASE}
--claude-model <m> Claude model for analysis
Default: ${CLAUDE_MODEL}
-h, --help Show this help message
-v, --version Show version
${BOLD}Environment Variables:${NC}
ANTHROPIC_API_KEY Claude API key (required for analysis)
PEERTUBE_INSTANCE Default PeerTube instance
WHISPER_MODELS_PATH Path to Whisper models
${BOLD}Examples:${NC}
# Basic usage
$(basename "$0") https://tube.gk2.secubox.in/w/abc123
# Force Whisper transcription with large model
$(basename "$0") --force-whisper --model large-v3 --url https://...
# Subtitles only, no AI analysis
$(basename "$0") --no-whisper --no-analyse https://...
${BOLD}Output Structure:${NC}
./output/<slug>/
├── <slug>.meta.json # Video metadata
├── <slug>.fr.vtt # Original subtitles (if available)
├── <slug>.transcript.txt # Plain text transcript
└── <slug>.analyse.md # Claude AI analysis
EOF
}
show_version() {
echo "PeerTube Analyse v${SCRIPT_VERSION}"
echo "SecuBox Intelligence Module"
}
parse_args() {
VIDEO_URL=""
NO_WHISPER=""
FORCE_WHISPER=""
NO_ANALYSE=""
while [ $# -gt 0 ]; do
case "$1" in
--url)
VIDEO_URL="$2"
shift 2
;;
--no-whisper)
NO_WHISPER=1
shift
;;
--force-whisper)
FORCE_WHISPER=1
shift
;;
--no-analyse|--no-analyze)
NO_ANALYSE=1
shift
;;
--model)
WHISPER_MODEL="$2"
shift 2
;;
--lang)
WHISPER_LANG="$2"
shift 2
;;
--output)
OUTPUT_BASE="$2"
shift 2
;;
--claude-model)
CLAUDE_MODEL="$2"
shift 2
;;
-h|--help)
show_help
exit 0
;;
-v|--version)
show_version
exit 0
;;
-*)
log_error "Unknown option: $1"
show_help
exit 1
;;
*)
# Positional argument = URL
if [ -z "$VIDEO_URL" ]; then
VIDEO_URL="$1"
else
log_error "Multiple URLs not supported"
exit 1
fi
shift
;;
esac
done
if [ -z "$VIDEO_URL" ]; then
log_error "No video URL provided"
show_help
exit 1
fi
}
#=============================================================================
# ENTRY POINT
#=============================================================================
main() {
parse_args "$@"
printf "\n%b╔══════════════════════════════════════════════════════╗%b\n" "$BOLD$CYAN" "$NC"
printf "%b║ PeerTube Transcript & Analysis Tool v%-16s║%b\n" "$BOLD$CYAN" "$SCRIPT_VERSION" "$NC"
printf "%b║ SecuBox Intelligence Module ║%b\n" "$BOLD$CYAN" "$NC"
printf "%b╚══════════════════════════════════════════════════════╝%b\n\n" "$BOLD$CYAN" "$NC"
check_dependencies || exit 1
process_video "$VIDEO_URL"
local exit_code=$?
if [ $exit_code -eq 0 ]; then
printf "\n%b✓ All done!%b\n\n" "$BOLD$GREEN" "$NC"
else
printf "\n%b✗ Completed with errors%b\n\n" "$BOLD$YELLOW" "$NC"
fi
return $exit_code
}
# Run if not sourced
if [ "${0##*/}" = "peertube-analyse" ] || [ "${0##*/}" = "sh" ]; then
main "$@"
fi