Files

483 lines
16 KiB
Bash

#!/bin/bash
#
# DESCARGAR VIDEOS DEL CURSO DE VOLEY
# ====================================
# Descarga los 42 videos de Vimeo con reanudación automática.
# Puede detenerse y reanudarse cuando quieras.
#
# Modo de uso:
# bash descargar_videos.sh
#
# Opciones:
# bash descargar_videos.sh --resume # Reanuda descargas pendientes
# bash descargar_videos.sh --status # Ver estado de descargas
# bash descargar_videos.sh --clean # Borrar todo y empezar de nuevo
#
set -e
# ─── CONFIGURACIÓN ───────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SESSION_JSON="$SCRIPT_DIR/session.json"
COOKIES_FILE="$SCRIPT_DIR/.cookies_voley.txt"
VIDEOS_DIR="$SCRIPT_DIR/descargas/videos_offline"
REGISTRO="$SCRIPT_DIR/.videos_descargados.txt"
LISTA_VIDEOS="$SCRIPT_DIR/.lista_videos.txt"
DEST_DIR="$SCRIPT_DIR/descargas"
LIMIT_RATE="" # Ej: "5M" para limitar a 5 MB/s, vacío = sin límite
# ─── COLORES ─────────────────────────────────────────────────
VERDE='\033[0;32m'
AMARILLO='\033[1;33m'
ROJO='\033[0;31m'
AZUL='\033[0;34m'
RESET='\033[0m'
ok() { echo -e " ${VERDE}${RESET} $1"; }
warn() { echo -e " ${AMARILLO}${RESET} $1"; }
err() { echo -e " ${ROJO}${RESET} $1"; }
info() { echo -e " ${AZUL}${RESET} $1"; }
# ─── HELPERS ─────────────────────────────────────────────────
timestamp() {
date '+%Y-%m-%d %H:%M:%S'
}
log() {
echo "[$(timestamp)] $1"
}
check_dependencies() {
if ! command -v yt-dlp &>/dev/null; then
err "yt-dlp no está instalado."
echo " Instalálo con: pip install yt-dlp"
exit 1
fi
if ! command -v python3 &>/dev/null; then
err "python3 no está instalado."
exit 1
fi
}
# ─── CONVERTIR COOKIES ──────────────────────────────────────
convertir_cookies() {
if [ ! -f "$SESSION_JSON" ]; then
err "No se encontró $SESSION_JSON"
echo " Ejecutá primero: node download_course.mjs"
exit 1
fi
log "Convirtiendo cookies de Playwright a formato Netscape..."
python3 -c "
import json
with open('$SESSION_JSON') as f:
data = json.load(f)
cookies = data.get('cookies', [])
lines = ['# Netscape HTTP Cookie File']
for c in cookies:
domain = c.get('domain', '')
if not domain:
continue
flag = 'TRUE' if domain.startswith('.') else 'FALSE'
path = c.get('path', '/')
secure = 'TRUE' if c.get('secure', False) else 'FALSE'
exp = c.get('expires', 0)
if exp <= 0:
exp = 1778594896 # fecha lejana
name = c.get('name', '')
value = c.get('value', '')
lines.append(f'{domain}\t{flag}\t{path}\t{secure}\t{int(exp)}\t{name}\t{value}')
with open('$COOKIES_FILE', 'w') as f:
f.write('\n'.join(lines) + '\n')
print(f'{len(cookies)}')
" 2>/dev/null
if [ -f "$COOKIES_FILE" ]; then
local count=$(wc -l < "$COOKIES_FILE")
ok "Cookies convertidas ($((count - 1)))"
else
err "Error al convertir cookies"
exit 1
fi
}
# ─── EXTRAER LISTA DE VIDEOS ─────────────────────────────────
extraer_lista_videos() {
log "Extrayendo lista de videos del HTML descargado..."
python3 -c "
import os, re, json
base = '$DEST_DIR/secciones'
modulos = ['Modulo_1_Planificacion', 'Modulo_2_Entrenamiento']
# Mapeo de carpetas a IDs de sección
mapeo_secciones = {
'Planificacion': ('m/19096', 's/34868'),
'Preparacion_Atletica': ('m/19096', 's/34869'),
'Explicacion_TP': ('m/19096', 's/34870'),
'Seguimientos_Equipos': ('m/19096', 's/35523'),
'Analisis_Equipos_1': ('m/19096', 's/35524'),
'Analisis_Equipos_2': ('m/19096', 's/35525'),
'TP_Final_Unidad': ('m/19096', 's/35526'),
'Centrales_y_Oponentes': ('m/19097', 's/35519'),
'Puntas_y_Liberos': ('m/19097', 's/35520'),
'Entrenamiento_Armadoras': ('m/19097', 's/35521'),
'Explicacion_TP': ('m/19097', 's/35522'),
}
# Nombre humano de cada sección
nombres = {
'Planificacion': 'Planificacion',
'Preparacion_Atletica': 'Preparacion_Atletica',
'Explicacion_TP': 'Explicacion_TP',
'Seguimientos_Equipos': 'Seguimientos_Equipos',
'Analisis_Equipos_1': 'Analisis_Equipos_1',
'Analisis_Equipos_2': 'Analisis_Equipos_2',
'TP_Final_Unidad': 'TP_Final_Unidad',
'Centrales_y_Oponentes': 'Centrales_y_Oponentes',
'Puntas_y_Liberos': 'Puntas_y_Liberos',
'Entrenamiento_Armadoras': 'Entrenamiento_Armadoras',
}
videos = []
vistos = set()
for mod in modulos:
mod_path = os.path.join(base, mod)
if not os.path.isdir(mod_path):
continue
for sec in os.listdir(mod_path):
html_path = os.path.join(mod_path, sec, 'pagina.html')
if not os.path.isfile(html_path):
continue
html = open(html_path, 'r', errors='ignore').read()
if '500 Internal Server Error' in html:
continue
for m in re.finditer(r'player\.vimeo\.com/video/(\d+)', html):
vid = m.group(1)
if vid in vistos:
continue
vistos.add(vid)
# Buscar título del tema
idx = m.start()
before = html[max(0, idx - 400):idx]
tm = re.search(r'<em>([^<]+)</em>', before)
titulo = tm.group(1).strip() if tm else f'video_{vid}'
# Limpiar caracteres no válidos para文件名
titulo = re.sub(r'[<>:\"/\\\\|?*]+', '_', titulo)[:80]
# Construir URL de la página embebedora
mapa = mapeo_secciones.get(sec)
if mapa:
mod_id, sec_id = mapa
pagina_url = f'https://voley.onlineeducation.center/es/campus/student/training/e/14197/{mod_id}/{sec_id}/study-resources/list'
else:
pagina_url = ''
videos.append({
'id': vid,
'titulo': titulo,
'seccion': nombres.get(sec, sec),
'modulo': 'M1' if '1' in mod else 'M2',
'url': pagina_url,
})
with open('$LISTA_VIDEOS', 'w') as f:
json.dump(videos, f, ensure_ascii=False, indent=2)
print(len(videos))
" 2>/dev/null
local total=$(wc -l < "$LISTA_VIDEOS" 2>/dev/null || echo 0)
if [ "$total" -gt 0 ] 2>/dev/null; then
ok "Lista de videos generada"
python3 -c "import json; print(f' Total: {len(json.load(open(\"$LISTA_VIDEOS\")))} videos')" 2>/dev/null
else
err "No se encontraron videos en el HTML descargado"
exit 1
fi
}
# ─── DESCARGAR VIDEOS ───────────────────────────────────────
descargar_videos() {
mkdir -p "$VIDEOS_DIR"
touch "$REGISTRO"
# Leer videos ya descargados
local descargados=()
while IFS= read -r line; do
descargados+=("$line")
done < "$REGISTRO"
# Obtener lista completa
local total=$(python3 -c "import json; print(len(json.load(open('$LISTA_VIDEOS'))))" 2>/dev/null || echo 0)
if [ "$total" -eq 0 ]; then
err "Lista de videos vacía"
exit 1
fi
echo ""
echo " Total: $total videos"
echo " Ya descargados: ${#descargados[@]}"
echo " Pendientes: $((total - ${#descargados[@]}))"
echo ""
# Procesar cada video
for i in $(seq 0 $((total - 1))); do
local data=$(python3 -c "
import json
with open('$LISTA_VIDEOS') as f:
v = json.load(f)
v = v[$i]
print(f'{v[\"id\"]}|{v[\"titulo\"]}|{v[\"seccion\"]}|{v[\"modulo\"]}|{v[\"url\"]}')" 2>/dev/null)
local id=$(echo "$data" | cut -d'|' -f1)
local titulo=$(echo "$data" | cut -d'|' -f2)
local seccion=$(echo "$data" | cut -d'|' -f3)
local modulo=$(echo "$data" | cut -d'|' -f4)
local pagina_url=$(echo "$data" | cut -d'|' -f5-)
# Verificar si ya se descargó
if grep -q "^$id|" "$REGISTRO" 2>/dev/null; then
continue
fi
# Nombre del archivo de salida
local safe_name="${modulo}_${seccion}_${titulo}"
safe_name=$(echo "$safe_name" | sed 's/[^a-zA-Z0-9_]/_/g' | cut -c1-120)
local output_path="$VIDEOS_DIR/${safe_name}.mp4"
# Si el archivo ya existe en disco pero no en el registro, agregarlo
if [ -f "$output_path" ]; then
echo "${id}|${safe_name}.mp4" >> "$REGISTRO"
ok "[$((i+1))/$total] Ya existe: ${titulo:0:50}"
continue
fi
echo ""
echo " ─────────────────────────────────────────────"
printf " [%d/%d] 🎬 %s\n" $((i+1)) "$total" "${titulo:0:70}"
echo " ─────────────────────────────────────────────"
echo ""
if [ -z "$pagina_url" ]; then
warn "Sin URL de página embebedora, intentando con URL directa..."
pagina_url="https://player.vimeo.com/video/${id}"
fi
# Construir comando yt-dlp
local cmd="yt-dlp"
cmd+=" --cookies \"$COOKIES_FILE\""
cmd+=" -o \"$output_path\""
cmd+=" --merge-output-format mp4"
cmd+=" --no-playlist"
cmd+=" --no-progress"
cmd+=" --console-title"
cmd+=" --retries 3"
cmd+=" --continue"
if [ -n "$LIMIT_RATE" ]; then
cmd+=" --limit-rate $LIMIT_RATE"
fi
cmd+=" \"$pagina_url\""
# Ejecutar descarga
if eval "$cmd" 2>&1; then
if [ -f "$output_path" ]; then
local size=$(du -h "$output_path" | cut -f1)
echo "${id}|${safe_name}.mp4" >> "$REGISTRO"
ok "Descargado: ${titulo:0:40} ($size)"
else
# Buscar archivo con otro nombre
local found=$(find "$VIDEOS_DIR" -name "*${id}*" -o -name "*${titulo:0:20}*" 2>/dev/null | head -1)
if [ -n "$found" ]; then
local fname=$(basename "$found")
echo "${id}|${fname}" >> "$REGISTRO"
local size=$(du -h "$found" | cut -f1)
ok "Descargado (con otro nombre): ${titulo:0:40} ($size)"
else
warn "Puede que se haya descargado pero no se encontró el archivo esperado."
warn "Ruta buscada: $output_path"
fi
fi
else
err "Error descargando: ${titulo:0:50}"
fi
# Pequeña pausa entre descargas para no saturar
sleep 2
done
echo ""
local pendientes=$((total - $(wc -l < "$REGISTRO" 2>/dev/null || echo 0)))
if [ "$pendientes" -le 0 ]; then
echo " 🎉 ¡Todos los videos descargados!"
else
echo " Quedan $pendientes videos pendientes."
echo " Para reanudar: bash $0"
fi
}
# ─── GENERAR LIBRO CON VIDEOS ───────────────────────────────
generar_libro() {
log "Generando libro de estudio con videos locales..."
node -e "
const fs = require('fs');
const path = require('path');
// Leer registro de videos descargados
const registro = '$REGISTRO';
const videosMap = {};
if (fs.existsSync(registro)) {
const lines = fs.readFileSync(registro, 'utf-8').trim().split('\n');
for (const line of lines) {
const [id, archivo] = line.split('|');
if (id && archivo) videosMap[id] = archivo;
}
}
console.log(' Videos mapeados:', Object.keys(videosMap).length);
// Guardar mapa para que lo use el script principal
const mapaPath = '$SCRIPT_DIR/.mapa_videos.json';
fs.writeFileSync(mapaPath, JSON.stringify(videosMap, null, 2));
" 2>/dev/null
# Ahora regenerar el libro usando el script existente
if [ -f "$SCRIPT_DIR/generar_dashboard.mjs" ]; then
node "$SCRIPT_DIR/generar_dashboard.mjs" 2>&1
else
warn "No se encontró generar_dashboard.mjs"
warn "El libro se generará sin videos la próxima vez que ejecutes node generar_dashboard.mjs"
fi
}
# ─── ESTADO ──────────────────────────────────────────────────
mostrar_estado() {
echo ""
echo " ─── ESTADO DE DESCARGA ───"
echo ""
if [ ! -f "$LISTA_VIDEOS" ]; then
echo " Lista de videos no generada."
echo " Ejecutá primero: bash $0"
return
fi
local total=$(python3 -c "import json; print(len(json.load(open('$LISTA_VIDEOS'))))" 2>/dev/null || echo 0)
local descargados=$(wc -l < "$REGISTRO" 2>/dev/null || echo 0)
local pendientes=$((total - descargados))
echo " Total videos: $total"
echo " Descargados: $descargados"
echo " Pendientes: $pendientes"
echo ""
if [ "$descargados" -gt 0 ] && [ -d "$VIDEOS_DIR" ]; then
local total_size=$(du -sh "$VIDEOS_DIR" 2>/dev/null | cut -f1)
echo " Espacio usado: $total_size"
echo ""
echo " Últimos descargados:"
tail -5 "$REGISTRO" 2>/dev/null | while IFS='|' read -r id archivo; do
if [ -n "$archivo" ] && [ -f "$VIDEOS_DIR/$archivo" ]; then
local size=$(du -h "$VIDEOS_DIR/$archivo" 2>/dev/null | cut -f1)
echo " ✓ $archivo ($size)"
fi
done
fi
echo ""
}
# ─── LIMPIAR ─────────────────────────────────────────────────
limpiar() {
echo ""
warn "¿Borrar todos los videos descargados? (s/N): "
read -r respuesta
if [ "$respuesta" = "s" ] || [ "$respuesta" = "S" ]; then
rm -rf "$VIDEOS_DIR" "$REGISTRO" "$LISTA_VIDEOS" "$COOKIES_FILE"
ok "Eliminado. Listo para empezar de nuevo."
else
ok "Operación cancelada."
fi
}
# ─── MAIN ────────────────────────────────────────────────────
main() {
echo ""
echo "═══════════════════════════════════════════════════"
echo " 🏐 DESCARGAR VIDEOS — Curso de Vóley"
echo "═══════════════════════════════════════════════════"
echo ""
check_dependencies
# Procesar argumentos
case "${1:-}" in
--status|-s)
convertir_cookies
extraer_lista_videos
mostrar_estado
exit 0
;;
--clean|-c)
limpiar
exit 0
;;
--resume|-r)
true # simplemente continua
;;
*)
# Primer ejecución: setup completo
if [ ! -f "$COOKIES_FILE" ]; then
convertir_cookies
fi
if [ ! -f "$LISTA_VIDEOS" ]; then
extraer_lista_videos
fi
;;
esac
# Asegurar cookies
if [ ! -f "$COOKIES_FILE" ]; then
convertir_cookies
fi
if [ ! -f "$LISTA_VIDEOS" ]; then
extraer_lista_videos
fi
descargar_videos
echo ""
echo "═══════════════════════════════════════════════════"
echo " Listo."
echo "═══════════════════════════════════════════════════"
echo ""
echo " Videos en: $VIDEOS_DIR"
echo " Para retomar: bash $0 --resume"
echo " Para ver estado: bash $0 --status"
echo ""
}
main "$@"