secubox-openwrt/package/secubox/secubox-app-peertube/files/usr/sbin/peertube-import
CyberMind-FR 50ddd2c1fe fix(peertube-import): Improve subtitle file detection pattern
- Changed glob pattern from ${slug}*.vtt to *.vtt to catch all subtitle files
- Fixed language extraction regex to work with any filename format
- Redirected yt-dlp subtitle output to stderr

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-21 20:24:07 +01:00

588 lines
17 KiB
Bash

#!/bin/sh
# PeerTube Video Import with Multi-Track Subtitle Sync
# SecuBox Intelligence Module
# Compatible: OpenWrt
set -e
#=============================================================================
# CONFIGURATION
#=============================================================================
SCRIPT_VERSION="1.0.0"
PEERTUBE_URL="${PEERTUBE_URL:-https://tube.gk2.secubox.in}"
PEERTUBE_TOKEN="${PEERTUBE_TOKEN:-}"
OUTPUT_BASE="${OUTPUT_BASE:-/tmp/peertube-import}"
DEFAULT_CHANNEL_ID=1
DEFAULT_PRIVACY=1 # 1=public, 2=unlisted, 3=private
#=============================================================================
# LOGGING
#=============================================================================
log_info() { echo >&2 "[INFO] $*"; }
log_ok() { echo >&2 "[OK] $*"; }
log_warn() { echo >&2 "[WARN] $*"; }
log_error() { echo >&2 "[ERROR] $*"; }
log_step() { echo >&2 ""; echo >&2 "==> $*"; }
log_progress() { echo >&2 "[PROGRESS] $*"; }
#=============================================================================
# UTILITY FUNCTIONS
#=============================================================================
# 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
}
# Get PeerTube authentication token
get_peertube_token() {
local username="$1"
local password="$2"
if [ -n "$PEERTUBE_TOKEN" ]; then
echo "$PEERTUBE_TOKEN"
return 0
fi
# Read credentials from UCI config (admin section)
if [ -z "$username" ]; then
username=$(uci -q get peertube.admin.username)
password=$(uci -q get peertube.admin.password)
fi
# Try to get OAuth client credentials
local client_id client_secret
local oauth_clients
oauth_clients=$(curl -s "${PEERTUBE_URL}/api/v1/oauth-clients/local")
if command -v jq >/dev/null 2>&1; then
client_id=$(echo "$oauth_clients" | jq -r '.client_id // empty')
client_secret=$(echo "$oauth_clients" | jq -r '.client_secret // empty')
else
client_id=$(echo "$oauth_clients" | jsonfilter -e '@.client_id' 2>/dev/null)
client_secret=$(echo "$oauth_clients" | jsonfilter -e '@.client_secret' 2>/dev/null)
fi
if [ -z "$client_id" ] || [ -z "$username" ]; then
log_error "Cannot get PeerTube token. Set PEERTUBE_TOKEN or configure api credentials."
return 1
fi
# Get access token
local token_response
token_response=$(curl -s -X POST "${PEERTUBE_URL}/api/v1/users/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=${client_id}" \
-d "client_secret=${client_secret}" \
-d "grant_type=password" \
-d "response_type=code" \
-d "username=${username}" \
-d "password=${password}")
local token
if command -v jq >/dev/null 2>&1; then
token=$(echo "$token_response" | jq -r '.access_token // empty')
else
token=$(echo "$token_response" | jsonfilter -e '@.access_token' 2>/dev/null)
fi
if [ -z "$token" ]; then
log_error "Failed to get access token"
return 1
fi
echo "$token"
}
#=============================================================================
# VIDEO DOWNLOAD
#=============================================================================
download_video() {
local url="$1"
local output_dir="$2"
local slug="$3"
log_step "Downloading video"
local video_file="$output_dir/${slug}.%(ext)s"
# Download best quality video - redirect output to stderr to keep stdout clean
if yt-dlp -f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best" \
--merge-output-format mp4 \
-o "$output_dir/${slug}.%(ext)s" \
--no-playlist \
"$url" >&2 2>&1; then
# Find downloaded file
local found_video
found_video=$(find "$output_dir" -name "${slug}.*" -type f | grep -E '\.(mp4|webm|mkv)$' | head -1)
if [ -n "$found_video" ] && [ -f "$found_video" ]; then
log_ok "Video downloaded: $found_video"
echo "$found_video"
return 0
fi
fi
log_error "Failed to download video"
return 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"
if yt-dlp --dump-json --no-warnings "$url" 2>/dev/null > "$meta_file.tmp"; then
if command -v jq >/dev/null 2>&1; then
jq '{
id: .id,
title: .title,
description: .description,
duration: .duration,
upload_date: .upload_date,
uploader: .uploader,
channel: .channel,
tags: .tags,
webpage_url: .webpage_url,
thumbnail: .thumbnail,
subtitles: ((.subtitles // {}) | keys),
automatic_captions: ((.automatic_captions // {}) | keys)
}' "$meta_file.tmp" > "$meta_file"
else
mv "$meta_file.tmp" "$meta_file"
fi
rm -f "$meta_file.tmp"
log_ok "Metadata saved: $meta_file"
echo "$meta_file"
return 0
fi
log_error "Failed to extract metadata"
return 1
}
#=============================================================================
# SUBTITLE DOWNLOAD
#=============================================================================
download_subtitles() {
local url="$1"
local output_dir="$2"
local slug="$3"
local languages="$4" # Comma-separated: fr,en,de
log_step "Downloading subtitles"
[ -z "$languages" ] && languages="fr,en"
log_info "Requested languages: $languages"
# Download subtitles with yt-dlp (both manual and auto-generated)
yt-dlp --write-sub --write-auto-sub \
--sub-lang "$languages" \
--sub-format vtt \
--convert-subs vtt \
--skip-download \
-o "$output_dir/${slug}" \
"$url" >&2 2>&1 || true
# List downloaded subtitle files (catch all .vtt files in case filename differs)
local count=0
for vtt in "$output_dir"/*.vtt; do
[ -f "$vtt" ] || continue
count=$((count + 1))
log_ok "Downloaded: $(basename "$vtt")"
done
if [ "$count" -eq 0 ]; then
log_warn "No subtitles found for requested languages"
return 1
fi
log_ok "Downloaded $count subtitle file(s)"
return 0
}
#=============================================================================
# PEERTUBE UPLOAD
#=============================================================================
upload_video_to_peertube() {
local video_file="$1"
local meta_file="$2"
local channel_id="$3"
local privacy="$4"
local token="$5"
log_step "Uploading video to PeerTube"
[ -z "$channel_id" ] && channel_id=$DEFAULT_CHANNEL_ID
[ -z "$privacy" ] && privacy=$DEFAULT_PRIVACY
# Get title and description from metadata
local title description
if command -v jq >/dev/null 2>&1 && [ -f "$meta_file" ]; then
title=$(jq -r '.title // "Imported Video"' "$meta_file")
description=$(jq -r '.description // ""' "$meta_file" | head -c 10000)
else
title="Imported Video"
description=""
fi
log_info "Title: $title"
log_info "Channel ID: $channel_id"
log_info "Privacy: $privacy"
# Sanitize description (escape newlines and quotes)
description=$(echo "$description" | tr '\n' ' ' | sed 's/"/\\"/g' | head -c 5000)
# Upload video - use temp file to avoid mixing stderr with response
local tmpfile="/tmp/peertube_upload_$$.json"
local http_code
http_code=$(curl -s -w "%{http_code}" -o "$tmpfile" -X POST "${PEERTUBE_URL}/api/v1/videos/upload" \
-H "Authorization: Bearer ${token}" \
-F "videofile=@${video_file}" \
-F "channelId=${channel_id}" \
-F "name=${title}" \
-F "privacy=${privacy}" \
-F "description=${description}" \
2>/dev/null)
local body=""
[ -f "$tmpfile" ] && body=$(cat "$tmpfile")
rm -f "$tmpfile"
if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then
local video_id video_uuid
if command -v jq >/dev/null 2>&1; then
video_id=$(echo "$body" | jq -r '.video.id // empty')
video_uuid=$(echo "$body" | jq -r '.video.uuid // empty')
else
video_id=$(echo "$body" | jsonfilter -e '@.video.id' 2>/dev/null)
video_uuid=$(echo "$body" | jsonfilter -e '@.video.uuid' 2>/dev/null)
fi
if [ -n "$video_id" ]; then
log_ok "Video uploaded: ID=$video_id, UUID=$video_uuid"
echo "${video_id}|${video_uuid}"
return 0
fi
fi
log_error "Upload failed (HTTP $http_code)"
log_error "$body"
return 1
}
upload_subtitles_to_peertube() {
local video_id="$1"
local output_dir="$2"
local slug="$3"
local token="$4"
log_step "Uploading subtitles to PeerTube"
local uploaded=0
for vtt in "$output_dir"/*.vtt; do
[ -f "$vtt" ] || continue
# Extract language code from filename
# Format: name.fr.vtt or name.en-US.vtt or name.en.vtt
local filename
filename=$(basename "$vtt")
local lang
# Extract language code before .vtt extension
lang=$(echo "$filename" | sed -E 's/.*\.([a-zA-Z]{2}(-[a-zA-Z]+)?)\.vtt$/\1/')
# Normalize language code (en-US -> en)
lang=$(echo "$lang" | cut -d'-' -f1)
log_info "Uploading subtitle: $lang ($filename)"
local http_code
http_code=$(curl -s -w "%{http_code}" -o /dev/null -X PUT \
"${PEERTUBE_URL}/api/v1/videos/${video_id}/captions/${lang}" \
-H "Authorization: Bearer ${token}" \
-F "captionfile=@${vtt}" \
2>/dev/null)
if [ "$http_code" = "200" ] || [ "$http_code" = "204" ] || [ "$http_code" = "201" ]; then
log_ok "Uploaded: $lang"
uploaded=$((uploaded + 1))
else
log_warn "Failed to upload $lang (HTTP $http_code)"
fi
done
log_ok "Uploaded $uploaded subtitle(s)"
return 0
}
#=============================================================================
# MAIN IMPORT FUNCTION
#=============================================================================
import_video() {
local url="$1"
local languages="$2"
local channel_id="$3"
local privacy="$4"
local username="$5"
local password="$6"
log_step "Starting video import"
log_info "URL: $url"
# Create output directory
# If OUTPUT_BASE already contains import_ (called from CGI), use it as-is
local output_dir
if echo "$OUTPUT_BASE" | grep -q "/import_"; then
output_dir="$OUTPUT_BASE"
else
output_dir="$OUTPUT_BASE/import_$(date +%s)_$$"
fi
mkdir -p "$output_dir"
# Extract metadata first to get title
local meta_file
meta_file=$(extract_metadata "$url" "$output_dir" "video") || {
log_error "Metadata extraction failed"
return 1
}
# Generate slug from title
local title slug
if command -v jq >/dev/null 2>&1; then
title=$(jq -r '.title // "video"' "$meta_file")
else
title=$(jsonfilter -i "$meta_file" -e '@.title' 2>/dev/null || echo "video")
fi
slug=$(generate_slug "$title")
[ -z "$slug" ] && slug="video_$$"
# Rename metadata file
mv "$meta_file" "$output_dir/${slug}.meta.json"
meta_file="$output_dir/${slug}.meta.json"
log_progress "downloading"
# Download video
local video_file
video_file=$(download_video "$url" "$output_dir" "$slug") || {
log_error "Video download failed"
return 1
}
# Download subtitles
download_subtitles "$url" "$output_dir" "$slug" "$languages" || {
log_warn "Subtitle download failed (continuing without subtitles)"
}
log_progress "uploading"
# Get PeerTube token
local token
token=$(get_peertube_token "$username" "$password") || {
log_error "Authentication failed"
return 1
}
# Upload video
local upload_result
upload_result=$(upload_video_to_peertube "$video_file" "$meta_file" "$channel_id" "$privacy" "$token") || {
log_error "Video upload failed"
return 1
}
local video_id video_uuid
video_id=$(echo "$upload_result" | cut -d'|' -f1)
video_uuid=$(echo "$upload_result" | cut -d'|' -f2)
# Upload subtitles
upload_subtitles_to_peertube "$video_id" "$output_dir" "$slug" "$token" || {
log_warn "Subtitle upload failed"
}
log_progress "completed"
# Output result
log_step "Import complete"
log_ok "Video ID: $video_id"
log_ok "Video UUID: $video_uuid"
log_ok "URL: ${PEERTUBE_URL}/w/${video_uuid}"
# Output JSON result
cat << EOF
{
"success": true,
"video_id": $video_id,
"video_uuid": "$video_uuid",
"video_url": "${PEERTUBE_URL}/w/${video_uuid}",
"title": $(echo "$title" | jq -Rs . 2>/dev/null || echo "\"$title\""),
"output_dir": "$output_dir"
}
EOF
return 0
}
#=============================================================================
# CLI PARSING
#=============================================================================
show_help() {
cat << EOF
PeerTube Video Import with Subtitle Sync
SecuBox Intelligence Module v${SCRIPT_VERSION}
Usage:
$(basename "$0") [OPTIONS] <url>
Options:
--url <url> Video URL (YouTube, Vimeo, etc.)
--lang <codes> Subtitle languages (comma-separated: fr,en,de)
Default: fr,en
--channel <id> PeerTube channel ID (default: 1)
--privacy <level> 1=public, 2=unlisted, 3=private (default: 1)
--username <user> PeerTube username (or set via UCI)
--password <pass> PeerTube password (or set via UCI)
--output <dir> Output directory (default: /tmp/peertube-import)
--peertube <url> PeerTube instance URL
-h, --help Show this help message
Environment Variables:
PEERTUBE_URL PeerTube instance URL
PEERTUBE_TOKEN OAuth access token (skip authentication)
Examples:
# Basic import
$(basename "$0") https://youtube.com/watch?v=xxx
# Import with multiple subtitle languages
$(basename "$0") --lang fr,en,de,es https://youtube.com/watch?v=xxx
# Import as unlisted video
$(basename "$0") --privacy 2 https://youtube.com/watch?v=xxx
EOF
}
parse_args() {
VIDEO_URL=""
LANGUAGES="fr,en"
CHANNEL_ID=""
PRIVACY=""
USERNAME=""
PASSWORD=""
while [ $# -gt 0 ]; do
case "$1" in
--url)
VIDEO_URL="$2"
shift 2
;;
--lang|--languages)
LANGUAGES="$2"
shift 2
;;
--channel)
CHANNEL_ID="$2"
shift 2
;;
--privacy)
PRIVACY="$2"
shift 2
;;
--username)
USERNAME="$2"
shift 2
;;
--password)
PASSWORD="$2"
shift 2
;;
--output)
OUTPUT_BASE="$2"
shift 2
;;
--peertube)
PEERTUBE_URL="$2"
shift 2
;;
-h|--help)
show_help
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 "$@"
echo >&2 ""
echo >&2 "╔══════════════════════════════════════════════════════╗"
echo >&2 "║ PeerTube Video Import v${SCRIPT_VERSION}"
echo >&2 "║ SecuBox Intelligence Module ║"
echo >&2 "╚══════════════════════════════════════════════════════╝"
echo >&2 ""
# Check dependencies
for dep in yt-dlp curl; do
if ! command -v "$dep" >/dev/null 2>&1; then
log_error "Required dependency not found: $dep"
exit 1
fi
done
import_video "$VIDEO_URL" "$LANGUAGES" "$CHANNEL_ID" "$PRIVACY" "$USERNAME" "$PASSWORD"
exit $?
}
# Run if not sourced
if [ "${0##*/}" = "peertube-import" ] || [ "${0##*/}" = "sh" ]; then
main "$@"
fi