STABLE v15.0

Manual Técnico para Desarrolladores

Documentación profunda de la arquitectura, protocolos y lógica de extracción.

1. Arquitectura del Sistema

Doloawerd opera bajo una arquitectura híbrida de microservicios locales con tres planos de comunicación. El sistema coordina un backend Python, una interfaz de escritorio Tkinter/CustomTkinter, una extensión de navegador Manifest V3 y un puente de mensajería nativa.

Python 3.10+
Flask 3.x
CustomTkinter
Playwright
aiohttp
yt-dlp
Chrome Ext MV3
libtorrent

Componentes del Sistema

┌─────────────────────────────────────────────────────────────────┐ │ DOLOAWERD MEDIA SUITE │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌────────────────────┐ ┌──────────────────────────────┐ │ │ │ Extensión MV3 │◄──►│ Native Messaging │ │ │ │ (Service Worker) │ │ (stdin/stdout JSON) │ │ │ └────────┬───────────┘ └──────────────┬───────────────┘ │ │ │ │ │ │ │ HTTP :8765 │ │ │ ▼ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Flask API Server (captura.py) │ │ │ │ /agregar | /api/v1/* | OPTIONS handler | CORS │ │ │ └─────────────────────┬───────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ GestorDescargas (gestor.py) │ │ │ │ Cola | Estados | Persistencia | Listeners | Threads │ │ │ └──┬──────────┬──────────┬──────────┬────────────────────┘ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ │ │ ┌──────┐ ┌────────┐ ┌────────┐ ┌──────────┐ │ │ │ HTTP │ │ yt-dlp │ │ Mega │ │ 1Fichier │ │ │ │segmen│ │ YouTube│ │ .nz │ │ API/HTML │ │ │ └──────┘ └────────┘ └────────┘ └──────────┘ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ │ │ ┌──────┐ ┌────────┐ ┌────────┐ ┌──────────┐ │ │ │ FTP │ │Torrent │ │Phantom │ │Shortlink │ │ │ │ │ │libtorr │ │Scribd │ │ Bypass │ │ │ └──────┘ └────────┘ └────────┘ └──────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ InterfazAvanzada (ui.py) │ │ │ │ Treeview | Sidebar | Diálogos | Tray | Animaciones │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘

Diagrama de Flujo de Control

[Navegador] → (Extension MV3) → [HTTP :8765 / Native Messaging]
                                    ↓
                            (Flask Server - captura.py)
                                    ↓
                            (GestorDescargas - gestor.py)
                                    ↓
                        ┌─── Resolver URL (extractor.py) ───┐
                        │ 1. Shortlink Bypass               │
                        │ 2. Site-specific resolvers        │
                        │ 3. JS Unpacker                    │
                        │ 4. yt-dlp extraction              │
                        │ 5. Deep Scan (recursive iframe)   │
                        │ 6. Direct download fallback       │
                        └──────────────────────────────────┘
                                    ↓
                        (Download Engine - ThreadPool)
                                    ↓
                            (File System - disco)
            

2. Flujo de Datos Completo

2.1 Adición de una URL

1. Usuario pega URL en UI  →  GestorDescargas.agregar()
2.                         →  Detección y bypass de acortadores
3.                         →  Detección de tipo (m3u8, doc, torrent, etc.)
4.                         →  Resolución con extractor.py
5.                         →  Creación de objeto Descarga
6.                         →  Encendido en ThreadPoolExecutor
7.                         →  Ejecución de descarga en hilo separado
            

2.2 Flujo desde la Extensión

1. Service Worker intercepta petición .m3u8
2. Content script detecta reproductor de video
3. Mensaje → chrome.runtime.sendMessage()
4. Service Worker → fetch POST a http://127.0.0.1:8765/api/v1/detect_media
5. Flask recibe JSON con {url, sourcePageUrl, headers, is_live, ...}
6. GestorDescargas.agregar() procesa y encola
7. Respuesta JSON con downloadId
            

2.3 Estados de la Máquina

                ┌──────────┐
                │  En cola  │
                └────┬─────┘
                     │
                     ▼
             ┌──────────────┐
             │ Descargando  │ ◄──── En Vivo (live streams)
             └──────┬───────┘
                    │
           ┌────────┼────────┐
           ▼        ▼        ▼
      ┌────────┐ ┌──────┐ ┌──────┐
      │Pausada │ │Compl.│ │Error │
      └────────┘ └──────┘ └──────┘
                        │
                        ▼
                    ┌─────────┐
                    │Cancelada│
                    └─────────┘
            

Los estados Completada, Error, Cancelada son finales. En Vivo es un estado especial de grabación de streams.

3. Motor de Resolución v15

El módulo extractor.py implementa una arquitectura en capas con resolutores específicos para cada plataforma, un desofuscador JS y un escáner recursivo de iframes.

3.1 Capas de Resolución (orden de prioridad)

Orden Mecanismo Descripción Módulo
1 Bypass de acortadores Detecta y salta AdFly, Ouo, Shorte.st, etc. shortlink_resolver.py
2 Corrección de URL Corrige typos comunes (streamtape, animeflv) corregir_url()
3 Site-specific resolvers AnimeFLV, NekoAnime, Hianime, HentaiLA, TryEmbed _resolver_*()
4 Embed conocidos StreamWish, StreamTape, Dood, MixDrop, Mega, Playnixes, Medixiru _es_embed_conocido()
5 JS Unpacker Desofusca código eval(p,a,c,k,e,d) _unpack_packer()
6 yt-dlp extraction YouTube, Twitter, Instagram, TikTok, Vimeo, Twitch extraer_con_ytdlp()
7 Deep Scan Escaneo recursivo de iframes hasta depth=2 DeepScanner
8 Direct download Fallback si la URL parece un archivo directo resolver_url()

3.2 Site-Specific Resolvers

Resolver Sitio Mecanismo Formato
_resolver_animeflv animeflv.net Extrae JSON var videos → código de servidor → URL streamtape MP4 directo
_resolver_nekoanime nekoanime.mx POST con nekocode → API → HLS endpoint temporal v15 HLS (m3u8)
_resolver_tryembed tryembed.us.cc API /api/stream_data → token → /s/<token>.m3u8 v15 HLS (m3u8)
_resolver_hianime hianime.ms Extrae data-server-url → resuelve servidores embebidos v15 HLS / MP4
_resolver_hentaila hentaila.com Extrae mirrors {server, url} → prioriza mejores servidores v15 HLS / MP4
_resolver_streamtape streamtape.com yt-dlp → fallback: extracción de innerHTML + construcción de /get_video MP4 directo
_resolver_streamwish streamwish.to Fetch HTML + JS Packer unpack → m3u8/mp4 HLS / MP4
_resolver_dood dood.to pass_md5 → token → URL construida con expiración MP4 directo
_resolver_mixdrop mixdrop.ag Fetch embed + JS Packer → variable wurl MP4 directo
_resolver_mega mega.nz Normaliza múltiples formatos de URL → #!ID!KEY compatible Archivo cifrado
_resolver_playnixes playnixes.com yt-dlp → fallback: headers completos → m3u8 CDN v15 HLS (m3u8)
_resolver_animeplanet anime-planet.com Extrae iframe de YouTube → resuelve con yt-dlp v15 YouTube MP4

3.3 JS Unpacker _unpack_packer()

Implementa un desofuscador para el formato eval(function(p,a,c,k,e,d)) usado por StreamWish, MixDrop y otros proveedores. El algoritmo:

  1. Extrae la cadena codificada, los parámetros a, c, k y el separador.
  2. Divide el array de palabras clave (k) por el separador.
  3. Reemplaza cada token numerado (en base a) por su palabra clave.
  4. Retorna el JavaScript desofuscado para buscar URLs.

3.4 Deep Scanner DeepScanner

Nuevo en v15.0: Escáner recursivo con max_depth=2 que busca medios en iframes y scripts.
class DeepScanner:
    def scan(url, referer, depth=0):
        1. _fetch_embed_html() — obtiene HTML con headers reales
        2. _extraer_urls_de_html_embed() — busca en scripts y atributos
           - Patrones: hls, file, source, url, src
           - Extensiones: .m3u8, .mpd, .mp4, .ts, .m4s
           - Rutas relativas para stream/hls/segments
        3. Si depth < max_depth: buscar iframes y escanear recursivamente
        4. _agregar_resultado() — valida extensión y evita duplicados
            

3.5 Corrección de URLs

El sistema mantiene un diccionario de typos conocidos en _TYPO_DOMINIOS:

_TYPO_DOMINIOS = {
    "streabmtape.com": "streamtape.com",
    "streamtabpe.com": "streamtape.com",
    "streambtape.com": "streamtape.com",
    # ... más variantes
}
            

3.6 Mega URL Normalization

_resolver_mega() y _corregir_url_mega() convierten TODOS los formatos de URL de Mega a un formato compatible con mega.py:

Formatos soportados:
  /embed/!ID!KEY → /#!ID!KEY
  /#!ID!KEY      → /#!ID!KEY
  /file/ID#KEY   → /#!ID!KEY
  !ID!KEY        → /#!ID!KEY
  ID#KEY         → /#!ID!KEY
            

4. API REST Interna v15

El servidor Flask (puerto 8765) expone una API REST completa para la comunicación con la extensión del navegador. Todas las rutas soportan CORS y OPTIONS (preflight).

4.1 CORS y Preflight

El servidor implementa after_request para añadir headers CORS en todas las respuestas:

@servidor.after_request
def after_request(response):
    response.headers.add('Access-Control-Allow-Origin', '*')
    response.headers.add('Access-Control-Allow-Headers', 'Content-Type')
    response.headers.add('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
    return response
            

Las peticiones OPTIONS (preflight) responden con 200 OK y los headers CORS, permitiendo que la extensión del navegador se comunique sin restricciones de origen cruzado.

4.2 Soporte MPEG-DASH (mpd)

MPEG-DASH es un formato de streaming adaptativo alternativo a HLS. Usa archivos de manifiesto .mpd (Media Presentation Description) que describen segmentos de video en múltiples calidades.

El gestor detecta automáticamente manifiestos MPD en los mismos puntos que HLS:

Los streams DASH de sitios como Zilla-Networks usan fMP4 con segmentos de inicialización (#EXT-X-MAP en HLS o Initialization en MPD), manejados por yt-dlp.

4.3 Re-resolución de URLs Expiradas

Cuando una descarga falla con códigos 401, 403 o 410 (enlace expirado), el gestor ejecuta un mecanismo de re-resolución automática:

_re_resolver_url(descarga):
    1. Verificar si el error es 401/403/410 (link expirado)
    2. Comprobar que la descarga tiene embed_url guardada
    3. Verificar que el servidor está en la lista de auth:
       servidores_con_auth = ["streamtape", "dood", "mixdrop",
                              "newsophon", "streamwish", "voe", "filemoon"]
    4. Llamar a resolver_url(embed_url) para obtener nueva URL
    5. Si se obtiene nueva URL:
       - Actualizar url, headers, referer de la descarga
       - Reanudar descarga desde donde quedó (resume parcial)
    6. Si falla: incrementar reintentos_resolver
    7. Máximo 2 reintentos antes de marcar como Error definitivo
            

4.4 Endpoints

GET/api/v1/status
Estado del servidor. Retorna online status, message y activeDownloadsCount.
POST/api/v1/detect_media
Detecta y agrega un medio para descarga. Body JSON: {url, sourcePageUrl, referer, filename, audioOnly, headers, is_live}
POST/api/v1/process_stream
Procesa un stream HLS/DASH. Body JSON: {url/manifestUrl, sourcePageUrl, referer, filename, audioOnly, headers}
POST/api/v1/download_batch
Descarga por lotes. Body JSON: {sourcePageUrl, referer, items: [{url, filename, headers}]}
POST/api/v1/upload_assembled
Recibe un archivo de video ensamblado (multipart/form-data). Remuxea con FFmpeg si es necesario (TS → MP4). Body: file (File), filename (string), download_id (string).
POST/api/v1/sync_browser_download
Sincroniza descargas desde el navegador. Body JSON: {id, nombre, estado, progreso}
GET/api/v1/download/<download_id>/status
Estado de una descarga específica por ID. Retorna id, url, nombre, estado, progreso, descargado, total, velocidad, error.

4.5 Formato de Respuesta

{
    "status": "success" | "error",
    "downloadId": "uuid",
    "message": "Descripción del resultado",
    "downloadIds": ["uuid1", "uuid2"],  // Para /download_batch
    "activeDownloadsCount": 3            // Para /status
}
            

5. Extensión del Navegador (Manifest V3)

5.1 Permisos

chrome.permissions: activeTab, storage, scripting, declarativeNetRequest,
                    tabs, alarms, webRequest, downloads, webNavigation,
                    nativeMessaging
host_permissions: <all_urls>
            

5.2 Architecture

service_worker.js (Router principal)
    ├── chrome.alarms (keepalive cada 0.5 min)
    ├── chrome.webRequest (sniffer .m3u8/.mpd)
    ├── chrome.runtime.onMessage (IPC con content scripts)
    └── fetch() a http://127.0.0.1:8765/api/v1/*

Content Scripts:
    ├── content_media_search.js — DOM + Shadow DOM + scripts
    ├── content_script.js — Ping SW cada 25s
    ├── content_link_capture.js — Links directos
    ├── youtube_integration.js — Botón YouTube
    ├── x_integration.js — Botón X/Twitter
    ├── animeav1_integration.js — Botón AnimeAV1
    └── universal_integration.js — Inyección universal

Popup:
    └── popup.html + popup.js — Estado (punto verde/rojo)
            

5.3 Service Worker Keepalive

El Service Worker de Manifest V3 se descarga después de ~30s de inactividad. Para evitarlo:

  1. chrome.alarms.create("keepAlive", {periodInMinutes: 0.5}) — Despierta el SW cada 30s.
  2. content_script.js envía un ping chrome.runtime.sendMessage({type: "ping"}) cada 25s desde cada pestaña.
  3. El SW responde con {type: "pong", activeDownloads}.

5.4 HLS Capture

chrome.webRequest.onBeforeRequest.addListener(
    (details) => {
        if (details.url.includes('.m3u8')) {
            captureStream(details);
        }
    },
    {urls: ["<all_urls>"]}
);
            

El sniffer intercepta todas las peticiones de red y filtra por extensión .m3u8 y .mpd, enviándolas al servidor Flask.

6. Native Messaging Bridge v15

El puente de mensajería nativa permite a la extensión de Chrome comunicarse directamente con la aplicación Python sin pasar por HTTP, usando el protocolo stdin/stdout JSON de Native Messaging.

6.1 Componentes

Archivo Función
native_messaging/com.doloawerd.app.json Manifiesto de host nativo para Chrome (paths, permisos)
native_messaging/native_host.py Proceso bridge que lee JSON de stdin y escribe a stdout
native_messaging/native_host.bat Wrapper de Windows para lanzar el script Python
native_messaging/register_host.py Registra el host nativo en el registro de Windows

6.2 Protocolo

// Mensaje de la extensión → Python
{
    "type": "download",
    "url": "https://...",
    "filename": "video.mp4",
    "referer": "https://..."
}

// Respuesta Python → Extensión (formato 4 bytes length + JSON)
{
    "status": "success",
    "downloadId": "uuid",
    "message": "Descarga iniciada"
}
            

6.3 Instalación

python native_messaging/register_host.py
# Registra en HKCU\Software\Google\Chrome\NativeMessagingHosts\com.doloawerd.app
            

6.5 Motor BitTorrent v13

torrent.py implementa descargas P2P usando la librería libtorrent (v2.0+).

6.5.1 Arquitectura

TorrentDownloader:
    ├── __init__(url, destino, callback_progreso, callback_error, callback_completado)
    ├── iniciar() → Crea sesión libtorrent, añade torrent/magnet
    ├── pausar() → Pausa todos los peers
    ├── reanudar() → Reanuda todos los peers
    ├── cancelar() → Detiene y elimina la sesión
    └── _monitor() → Loop de 1s que actualiza progreso
            

6.5.2 Flujo de Descarga

1. Detectar tipo de entrada:
   - URL magnet: → add_torrent(magnet_uri)
   - Archivo .torrent: → leer bytes, add_torrent(torrent_info)
   - URL http/https a .torrent: → download + add_torrent
2. Configurar sesión:
   - listen_port: 6881 (rango dinámico si ocupado)
   - alert_mask: error, status, progress
   - settings_pack: active_downloads, max_connections
3. Loop de monitoreo:
   - Obtener status del torrent cada 1s
   - state: downloading → checking → finished → seeding
   - Progreso: status.progress * 100
   - Velocidad: status.download_rate / 1024 (KB/s)
   - Peers: status.num_peers / status.num_seeds
4. Al completar:
   - Esperar a que termine el checking (hash check)
   - Notificar callback_completado con la ruta del archivo
            

6.5.3 Manejo de Estados

Estado libtorrentEstado interno
checking_resume_dataDescargando
checking_filesVerificando archivos
downloading_metadataObteniendo metadatos (magnet)
downloadingDescargando
finishedCompletada (verificando)
seedingCompletada

6.6 Motor FTP v13

ftp.py implementa descargas desde servidores FTP usando la librería estándar ftplib.

6.6.1 Arquitectura

class DescargadorFTP:
    def __init__(self, url, destino, ...)
    def iniciar(self) → FTP, login, RETR
    def pausar(self) → abort()
    def reanudar(self) → FTP, login, REST + RETR
    def cancelar(self) → quit()
            

6.6.2 Flujo de Descarga

1. Parsear URL: ftp://[user:pass@]host[:port]/path
2. Conectar: FTP(host, port, timeout=30)
3. Login: user/pass o anonymous@
4. Obtener tamaño: size(path)
5. Descargar en bloques:
   - socket.makefile('rb') para lectura binaria
   - RETR path → callback por bloque de 64KB
   - Actualizar progreso por bloque recibido
6. Reanudación:
   - Si archivo parcial existe: REST offset + RETR
   - Escribir en modo append
            

6.6.3 Manejo de Errores

7. Live Stream Engine v15

7.1 Detección de Live

El sistema detecta streams en vivo mediante tres mecanismos:

  1. yt-dlp: Detecta is_live=True o live_status == "is_live".
  2. Extensión: El content script envía is_live: true en el payload.
  3. Marcado manual: Resolvers específicos (Twitch, Kick) marcan "is_live": true.

7.2 Grabación de Live

Cuando is_live = True:
  - Estado inicial: "En Vivo"  
  - Progreso: 0% (sin progreso porcentual)
  - Descarga: yt-dlp con formato best
  - Al cancelar: los datos capturados se conservan como "Completada"
  - El archivo resultante se busca por extensión .mp4, .mp3, .mkv, .webm
            

7.3 Estados de Live

"En Vivo" → "Cancelada"  → Se conservan datos → "Completada" (con archivo parcial)
"En Vivo" → "Completada" → Grabación finalizada exitosamente
"En Vivo" → "Error"      → Fallo en la grabación
            

8. Motor Phantom: Aislamiento Nuclear Pro

phantom.py — Extracción de alta fidelidad de documentos Scribd usando Playwright Chromium.

8.1 Auto-instalación _ensure_playwright()

Fast path: Verifica que TANTO Chromium como chromium_headless_shell existan en disco via os.path.exists().
Fallback: Ejecuta playwright install chromium (idempotente) solo si falta alguno.
def _ensure_playwright():
    1. Import playwright — si falla, pip install playwright
    2. Verificar chromium executable_path + headless shell binary
    3. Si falta → playwright install chromium (DEVNULL)
    4. Return True/False
            

8.2 Algoritmo de Extracción v2.1

  1. Emulación de Pantalla: page.emulate_media(media="screen") para saltar el bloqueo @media print.
  2. Purga inicial: Elimina banners de cookies y modales con JavaScript.
  3. Scroll Atómico: 120 pasos con 500ms de latencia para activar lazy loading de imágenes.
  4. Forzado de visibilidad: Remueve clases .not_visible, fuerza display:block; visibility:visible; opacity:1.
  5. Migración de nodos: Mueve .outer_page a un cleanContainer preservando buffers de imagen.
  6. Purga de basura: Elimina .promo_container, .ad_container, .between_page_ads.
  7. Renderizado PDF: page.pdf() con 8.5"×11", sin margen, con print_background=true.

9. Motor de Descarga

9.1 Descarga HTTP Segmentada

El método _descargar_http_segmentado() en gestor.py usa aiohttp para descarga multihilo:

1. HEAD request → detectar Content-Length y Accept-Ranges
2. Si no soporta rangos → descarga en un solo stream
3. Dividir archivo en N segmentos (min(segmentos, total/MB))
4. Para cada segmento:
   - SALTAR bytes ya descargados si existe archivo parcial
   - GET con Range: bytes=inicio-fin
   - Escribir en la posición exacta del archivo (r+b, seek)
5. asyncio.gather() para lanzar todos los segmentos en paralelo
6. Límite de velocidad configurable por segmento
            

9.2 Mecanismo de Resume

1. _bytes_en_disco() — verifica tamaño real del archivo parcial
2. Si offset > 0: intentar Range header
3. Si servidor responde 206: reanudar desde offset
4. Si no soporta Range: reiniciar desde 0
5. Sincronizar bytes_resumidos con estado real en disco
            

9.3 Límite de Velocidad

async def _limitar_velocidad(bytes_chunk, divisores):
    if limite_kbps <= 0: return
    bytes_por_segundo = (limite_kbps * 1024) / divisores
    await asyncio.sleep(bytes_chunk / bytes_por_segundo)
            

9.4 Errores y Re-resolución

Si una descarga falla con 401, 403 o 410 (link expirado), el gestor re-resuelve la URL automáticamente hasta 2 veces usando la embed_url original.

servidores_con_auth = [
    "streamtape", "dood", "mixdrop",
    "newsophon", "streamwish", "voe", "filemoon"
]
            

9.5 Descripción de Errores

El método _descripcion_error_descarga() traduce códigos HTTP a mensajes legibles:

CódigoMensaje
400solicitud inválida
401requiere autenticación
403acceso denegado por el servidor
404archivo o endpoint no encontrado
410enlace expirado
416rango no válido para reanudar
429límite de peticiones
502/503/504error de gateway/proxy

10. Sistema de Configuración

10.1 config.json

{
    "ai_provider": "claude" | "groq" | "ollama",
    "claude_api_key": "sk-ant-...",
    "groq_api_key": "gsk_...",
    "groq_model": "llama-3.1-8b-instant",
    "ollama_url": "http://localhost:11434/api/generate",
    "ollama_model": "llama3",
    "fichier_api_key": "...",
    "servidor_navegador": true,
    "captura_portapapeles": false,
    "max_simultaneas": 3,
    "segmentos": 6,
    "limite_kbps": 0,
    "acortadores_personalizados": ["adf.ly", "ouo.io"]
}
            

10.2 AI Error Analysis

El método _resolve_ai_error_details() consulta al proveedor de IA configurado para analizar errores de descarga. Soporta:

11. Gestión de Sesión

11.1 sesion.json

Persiste el estado de las descargas activas, pausadas y en cola. Se restaura al iniciar la aplicación.

// Campos guardados de cada Descarga:
{
    "id", "url", "destino", "nombre", "estado",
    "total", "descargado", "velocidad", "progreso",
    "hash_sha256", "error", "creado", "terminado",
    "bytes_resumidos", "embed_url", "referer",
    "reintentos_resolver", "headers", "is_live"
}
            

11.2 Restauración

_restaurar_sesion():
    1. Leer sesion.json
    2. Para cada item:
       - Crear objeto Descarga con estado corregido
       - _sincronizar_estado_archivo(): verificar tamaño real en disco
       - Si el archivo ya está completo → marcar como Completada
       - Si estaba "Descargando" → restaurar como "Pausada"
    3. Guardar sesión actualizada
            

11.3 historial.json

Mantiene las últimas 300 entradas de descargas completadas, con error o canceladas. Se usa para la vista de historial en la UI.

11.5 Seguridad en Hilos (Thread Safety) v15

El gestor maneja múltiples descargas concurrentes, comunicación UI-backend y persistencia de estado. Para garantizar consistencia se implementan varios mecanismos:

11.5.1 ThreadPoolExecutor

self.executor = ThreadPoolExecutor(
    max_workers=max_simultaneas
)
            

Cada descarga se ejecuta en un hilo separado del pool. El número máximo de workers se configura en Ajustes (por defecto 3). Las descargas en cola esperan hasta que un worker se libere.

11.5.2 Locks de Estado

self._lock_descargas = threading.Lock()

# Protege operaciones sobre el dict de descargas:
with self._lock_descargas:
    descarga.estado = "Pausada"
    descarga.descargado = nuevo_valor
    self._sesion_descargas[id] = descarga.__dict__
            

Todas las operaciones de lectura/escritura sobre self._sesion_descargas están protegidas por este lock para evitar condiciones de carrera cuando múltiples descargas actualizan su estado simultáneamente.

11.5.3 Cola de UI (Queue + after)

self.cola_ui = queue.Queue()

# Hilo de descarga:
cola_ui.put(("actualizar", descarga_id, nuevo_estado))

# Hilo principal (Tkinter):
def _procesar_cola_ui():
    try:
        while True:
            accion, *args = cola_ui.get_nowait()
            if accion == "actualizar":
                actualizar_treeview(args[0], args[1])
    except queue.Empty:
        pass
    self.root.after(100, self._procesar_cola_ui)
            

Tkinter no es thread-safe — todas las actualizaciones de la UI deben ejecutarse desde el hilo principal. Por eso las descargas (en hilos secundarios) encolan las actualizaciones en una queue.Queue, y el hilo principal las procesa mediante root.after() cada 100ms.

11.5.4 Persistencia Segura

_guardar_sesion():
    with self._lock_descargas:
        with open("sesion.json", "w") as f:
            json.dump(datos, f, indent=2)
            f.flush()
            os.fsync(f.fileno())
            

La sesión se guarda bajo el lock y con fsync para asegurar que los datos lleguen a disco incluso si el programa se cierra inesperadamente.

12. Arquitectura de la UI v15

La interfaz está construida con Tkinter (fallback) o CustomTkinter (preferido), con un sistema de colores premium y animaciones suaves.

12.1 Componentes Principales

Componente Función
InterfazAvanzada Clase principal que orquesta todos los componentes
Treeview (tabla) Lista de descargas con columnas: check, nombre, tipo, tamaño, progreso, estado, velocidad, tiempo, añadido
Sidebar Filtros por categoría (Compressed, Programs, Videos, etc.) y estado (Completado, Error, etc.)
Toolbar Botones de acción: Nueva descarga, Escanear, Reanudar, Pausar, Colas, Ajustes
Orbes animados Canvas con 3 orbes multicapa que flotan sinusoidalmente como fondo decorativo
Diálogos Nueva descarga, Escaneo, Lote, Conflicto, Propiedades, Colas, Ajustes
Menú contextual Clic derecho sobre items: Abrir, Pausar, Eliminar, Mover a categoría, Copiar SHA256, Analizar error con IA

12.2 Animaciones

// Transiciones suaves en botones y sidebar
_PASOS = 6-8 pasos
_INTERVALO = 10-12ms
Duración total: 60-96ms por animación

_interpolar_color(c1, c2, t):
    Interpola RGB entre dos colores hex
    t=0 → c1, t=1 → c2
            

12.3 Atajos de Teclado

AtajoAcción
Ctrl+NNueva descarga
Ctrl+PPausar selección
Ctrl+RReanudar selección
Ctrl+WMinimizar a bandeja
DeleteEliminar selección
Ctrl+IVer propiedades

13. Bypass de Acortadores

shortlink_resolver.py implementa la detección y resolución de URLs acortadas.

13.1 Detección

is_shortlink() verifica contra una lista de patrones regex de acortadores conocidos más los dominios personalizados del usuario.

13.2 Resolución

bypass_shortlink(url):
    1. Si es acortador simple (bit.ly, t.co, tinyurl):
       → follow redirects con HEAD
    2. Si es acortador con anuncios (AdFly, Ouo):
       a. GET con headers de navegador
       b. follow redirects
       c. Buscar URL en scripts JS:
          - var url = '...'
          - window.location.replace('...')
          - window.location.href = '...'
          - target_url = '...'
       d. Retornar URL encontrada o última redirección
            

14. Verificación de Integridad

integridad.py proporciona verificación post-descarga y reparación de archivos.

14.1 Verificación por Tipo

ExtensiónMétodo
.zipzipfile.testzip() — busca archivos corruptos
.rarrarfile.testrar() — si rarfile está instalado
.mp4/.mkv/.avi/.movVerificación de cabecera (primeros 100 bytes)
OtrosVerificación de existencia y tamaño > 0

14.2 Reparación

reparar_descarga(descarga_id):
    1. Verificar que el archivo existe en disco
    2. Calcular tamaño: retroceder 5% o 2MB
    3. Truncar archivo en esa posición
    4. Re-encolar descarga desde ese punto
            
Nota Crítica: Para la conversión a MP3, unión de segmentos HLS y remuxeo de streams, el sistema requiere que ffmpeg.exe esté disponible en el PATH del sistema. El gestor lo verifica con shutil.which("ffmpeg").
StreamTape: Bloquea peticiones Python puras. La estrategia de resolución es: (1) yt-dlp como método principal, (2) fallback con headers completos de navegador y extracción de innerHTML, (3) construcción de URL /get_video con corrección de typos.
Playwright Auto-install: El módulo Phantom instala automáticamente playwright + chromium en primer uso. Verifica que ambos binarios (Chromium regular y headless shell) estén en disco, ya que p.chromium.launch(headless=True) usa el headless shell.