Skip to content

GonzaloLeyton/letterbox_script

Repository files navigation

letterbox_script

Script en Python para exportar la watchlist pública de un usuario de Letterboxd a un archivo JSON con datos básicos de cada película (título, año, slug y URL).

¿Por qué este enfoque?

La API oficial de Letterboxd es solo por solicitud y no la otorgan para proyectos personales ni de análisis de datos. El export oficial en CSV solo funciona para tu propia cuenta y no incluye IDs externos. Por eso este script usa letterboxdpy, una librería que lee los datos públicos de Letterboxd sin necesidad de login.

⚠️ letterboxdpy hace scraping del HTML de Letterboxd. Si el sitio cambia su estructura, la librería (y este script) podrían dejar de funcionar hasta que se actualice.

Requisitos

  • Python 3.10 o superior

Instalación

python3 -m venv venv
source venv/bin/activate        # En Windows: venv\Scripts\activate
pip install -r requirements.txt

Uso

python watchlist_to_json.py <username>

Por defecto genera <username>_watchlist.json. Para elegir otro archivo:

python watchlist_to_json.py <username> -o salida.json

Ejemplo

python watchlist_to_json.py nmcassa
# 78 películas → nmcassa_watchlist.json

Formato de salida

{
  "username": "nmcassa",
  "count": 78,
  "movies": [
    {
      "letterboxd_id": "45260",
      "title": "Rollerball",
      "year": 1975,
      "slug": "rollerball",
      "url": "https://letterboxd.com/film/rollerball/"
    }
  ]
}

Detectar películas nuevas (persistencia en SQLite)

watchlist_sync.py guarda el estado de una watchlist en una base de datos SQLite (watchlist.db por defecto, sin dependencias externas) y reporta solo las películas agregadas desde la última ejecución. Soporta varios usuarios en el mismo archivo.

# Primer run: crea la "baseline" (guarda todo, no lo lista como novedad)
python watchlist_sync.py plzm
# Baseline creado para 'plzm': 535 películas guardadas.

# Runs siguientes: reporta solo lo nuevo
python watchlist_sync.py plzm
# Sin novedades para 'plzm' (total: 535).
# ...o bien:
# 2 película(s) nueva(s) para 'plzm':
#   • Dune: Part Two (2024) — https://letterboxd.com/film/dune-part-two/

Opciones:

python watchlist_sync.py <username> --db ruta/al/archivo.db   # otra ubicación de la DB
python watchlist_sync.py <username> --json nuevas.json        # volcar solo las nuevas a JSON

Cada película queda registrada con first_seen (cuándo se detectó por primera vez) y last_seen (última corrida en que seguía presente), lo que permite saber cuándo se agregó cada una. El archivo .db está en .gitignore.

Complementar con datos de torrents (Node.js)

enrich_torrents.js complementa cada película guardada en watchlist.db con datos de torrents usando torrent-search-api. Como esa librería es de Node.js, este script es independiente del resto (Python) pero comparte la misma base de datos.

Requisitos: Node.js ≥ 22.5 (usa el módulo incorporado node:sqlite, por eso no hay dependencias nativas extra; la única dependencia npm es torrent-search-api).

El script tiene dos comandos: enrich (por defecto) y download.

npm install

# Enriquecer las películas de un usuario (Top 5 torrents por película)
node --experimental-sqlite enrich_torrents.js enrich --user plzm --limit 5
# o vía npm script (enrich es el comando por defecto):
npm run enrich -- --user plzm --limit 5

Opciones de enrich:

Flag Descripción Default
--db <ruta> Archivo SQLite watchlist.db
--user <username> Solo películas de ese usuario todas
--limit <N> Cuántos candidatos evaluar por película (se guarda solo el mejor) 5
--concurrency <N> Películas a enriquecer en paralelo 1
--providers <lista> Proveedores separados por coma (ej. Yts,1337x) todos los públicos
--category <cat> Categoría de búsqueda Movies
--max-movies <N> Procesar como máximo N películas (útil para pruebas) sin límite
--refresh Re-consultar aunque ya tengan torrents salta las ya hechas
--delay <ms> Pausa entre películas (por worker) 1000

Se guarda un único torrent por película (el mejor según el ranking) en una columna torrents (JSON) que se agrega automáticamente a la tabla films:

{
  "updated_at": "2026-06-06T...Z",
  "query": "Dune 2024",
  "results": [
    { "title": "Dune 2024 2160p ...", "quality": "2160p", "size": "...",
      "seeds": 1234, "peers": 56, "provider": "IpTorrents",
      "link": "http://.../....torrent", "desc": "https://...",
      "magnet": "magnet:?xt=urn:btih:..." }
  ]
}

Filtrado de falsos positivos y prioridad de calidad: antes de guardar, el script descarta resultados que no correspondan a la película:

  • series/TV (patrones SxxExx, "Season", "Episode", "Complete Series");
  • torrents cuyo título no contenga el año de la película;
  • torrents que no incluyan todos los tokens del título.

De los que sí coinciden, prioriza calidad 1080p o superior (2160p/4K > 1080p

720p > SD) con seeds, y solo incluye calidad menor o sin seeds cuando no hay opciones de mejor calidad. La resolución detectada se guarda en quality.

Providers privados (IPTorrents)

IpTorrents es un provider privado: requiere autenticación. Las credenciales se leen de variables de entorno (nunca se versionan). Copia .env.example a .env y completa una de las dos opciones:

# Opción 1 — cookies (recomendado; cópialas del navegador ya logueado)
IPTORRENTS_COOKIE=uid=TU_UID; pass=TU_PASS

# Opción 2 — usuario y contraseña
IPTORRENTS_USERNAME=tu_usuario
IPTORRENTS_PASSWORD=tu_contraseña

Luego inclúyelo en --providers (no entra con los públicos por defecto):

# Local
export IPTORRENTS_COOKIE="uid=...; pass=..."
node --experimental-sqlite enrich_torrents.js enrich --user plzm --providers Limetorrents,IpTorrents

# Con make/Docker: el .env se carga solo y se reenvía al contenedor
make enrich user=plzm ARGS="--providers Limetorrents,IpTorrents"

Si falta la credencial, el script avisa y continúa con el resto de providers. El mismo mecanismo sirve para download (que también habilita providers).

Para diagnosticar la autenticación hay un script que muestra la URL consultada, las cookies enviadas (enmascaradas), el código HTTP y si la sesión es válida:

make test-ipt          # vía Docker (requiere `make build` una vez, es un archivo nuevo)
# o en local:
node iptorrents_check.js

Salida típica con cookies válidas:

— Chequeo crudo de sesión (fetch directo con cookies) —
  HTTP 200 OK
  → Sesión válida: la página trae la tabla de torrents. ✅

Si responde 302 → /login.php o 0 resultados, la cookie es inválida o expiró: vuelve a copiar uid y pass del navegador. Si usas otro mirror (no iptorrents.eu), apúntalo con IPTORRENTS_BASEURL=https://iptorrents.com (se aplica también en enrich/download). Las peticiones a IpTorrents incluyen un User-Agent de navegador para evitar bloqueos.

IPTorrents usa un parser propio. El provider de torrent-search-api quedó obsoleto (baja la página correcta pero sus selectores ya no calzan y devuelve 0). Por eso la búsqueda y descarga de IPTorrents las hace iptorrents.js (fetch con tus cookies + parseo con cheerio). Requiere IPTORRENTS_COOKIE (con usuario/clave no aplica este camino).

Prioridad (corto-circuito): si hay IPTORRENTS_COOKIE, enrich consulta IPTorrents primero (con el parser propio). Si devuelve al menos un torrent que cumple nuestras reglas (no es falso positivo y pasa el filtro de calidad), se queda con eso y no consulta a los demás providers. Solo si IPTorrents no tiene la película se hace la búsqueda general del resto.

Descargar los archivos .torrent encontrados

El comando download toma los torrents ya guardados en la DB y descarga sus archivos .torrent (vía downloadTorrent de la librería) a un directorio local. Si un proveedor no permite la descarga del .torrent, hace fallback guardando el magnet en un archivo .magnet.

Cada película tiene un único torrent (el mejor), así que download baja ese.

# Descargar los torrents guardados de un usuario a ./torrents
node --experimental-sqlite enrich_torrents.js download --user plzm

# A otra carpeta
node --experimental-sqlite enrich_torrents.js download --user plzm --out descargas

Opciones de download:

Flag Descripción Default
--out <dir> Carpeta destino de los .torrent torrents
--user <username> Solo películas de ese usuario todas
--concurrency <N> Películas a descargar en paralelo 1
--max-movies <N> Limitar la cantidad de películas sin límite
--providers <lista> Proveedores a habilitar (deben coincidir con los guardados) todos los públicos
--delay <ms> Pausa entre descargas (por worker) 1000
--redownload Re-descargar aunque la película ya tenga un torrent descargado omite esas películas

Cuando un torrent se descarga, se marca en la DB: dentro de su entrada en la columna torrents se agregan downloaded: true, downloaded_at (ISO 8601) y file (ruta del archivo generado). En corridas posteriores, si una película ya tiene al menos un torrent descargado, se omite completa (no se bajan los demás torrents de esa película); usa --redownload para forzar la re-descarga.

Nota: download solo funciona con torrents enriquecidos que tengan link (campo guardado por enrich). La carpeta torrents/ está en .gitignore.

Por defecto solo procesa películas sin enriquecer (torrents IS NULL), así que puedes correrlo por lotes con --max-movies. La librería hace scraping de indexadores públicos: algunos proveedores pueden estar caídos, por eso el script tolera fallos por proveedor y por película y continúa. Úsalo de forma responsable sobre tu propia watchlist.

Ejecutar con Docker

El Dockerfile incluye Python y Node.js en una sola imagen, con todas las dependencias, para correr ambos scripts de forma aislada (no necesitas instalar nada en tu máquina salvo Docker).

# 1. Construir la imagen
docker build -t letterbox-script .

# 2. Crear un directorio local para persistir la base de datos
mkdir -p data

La DB se persiste montando un directorio del host en /data (-v). Apunta los scripts a --db /data/watchlist.db:

# Sincronizar la watchlist (Python)
docker run --rm -v "$(pwd)/data:/data" letterbox-script \
  python watchlist_sync.py plzm --db /data/watchlist.db

# Enriquecer con torrents (Node)
docker run --rm -v "$(pwd)/data:/data" letterbox-script \
  node --experimental-sqlite enrich_torrents.js enrich --user plzm --db /data/watchlist.db --limit 5

# Descargar los .torrent a ./data/torrents
docker run --rm -v "$(pwd)/data:/data" letterbox-script \
  node --experimental-sqlite enrich_torrents.js download --user plzm --db /data/watchlist.db --out /data/torrents

# Shell interactivo dentro del contenedor (CMD por defecto)
docker run --rm -it -v "$(pwd)/data:/data" letterbox-script

Como el directorio data/ está montado desde el host, watchlist.db y los .torrent sobreviven a la eliminación del contenedor (--rm).

Atajos con make

El Makefile envuelve los comandos de Docker. Pasa el usuario con user=:

make build                                   # construye la imagen
make sync      user=plzm                     # sincroniza la watchlist
make enrich    user=plzm concurrency=5       # enriquece 5 películas en paralelo
make download  user=plzm                     # descarga los .torrent
make json      user=plzm                     # exporta a data/plzm_watchlist.json (+ enrich)
make show-json user=plzm n=10                # imprime el JSON de las primeras 10
make shell                                   # shell dentro del contenedor
make help                                    # lista todos los targets

Variables disponibles: user, db (default /data/watchlist.db), concurrency, n (para show-json, default 10), out, ARGS (flags extra), DATA (directorio del host, default ./data) e IMAGE. (--limit ya no se pasa: se guarda solo el mejor torrent; si quieres ampliar el pool de candidatos, usa ARGS="--limit 10".)

El make json (y watchlist_to_json.py --db) incluye en cada película la información de torrents (enrich) guardada en la base de datos.

Si tu red corporativa intercepta TLS y el docker build falla con SELF_SIGNED_CERT_IN_CHAIN, descomenta el bloque de CA del Dockerfile (ver comentarios) y deja tu certificado como proxy-ca.crt.

Limitaciones

  • Solo funciona con watchlists públicas.
  • Devuelve datos básicos. Para enriquecer con director, géneros, duración o IDs de TMDB/IMDB se puede extender usando la clase Movie de letterboxdpy o la API de TMDB.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors