Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh
python ruff_hook.py || exit 1
python -m pytest tests/ --ignore=tests/test_downloader.py -m "not network" -q || exit 1
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ jobs:
pip install Pillow reportlab gdown requests pytest

- name: Run tests
run: pytest tests/ -v --tb=short
run: pytest tests/ --ignore=tests/test_downloader.py -m "not network" -v --tb=short
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ config.json
# Generated by build_exe.py — contains the XOR-obfuscated API key; never commit
src/_bundled_key.py

# Generated by build_exe.py — contains build-time flags; never commit
gui/_build_flags.py

# Runtime output folder created next to the .exe (or project root in dev)
MPCFillToPDF/

# OS
.DS_Store
Thumbs.db
Expand All @@ -59,5 +65,6 @@ desktop.ini
*.suo
*.user

# Claude Code local settings (machine-specific)
# Claude Code settings (machine-specific, not shared)
.claude/settings.json
.claude/settings.local.json
17 changes: 9 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.13
hooks:
- id: ruff
args: [--fix]
- id: ruff-format

- repo: local
hooks:
- id: ruff-autofix
name: ruff (fix + format + re-stage)
language: python
additional_dependencies: [ruff]
entry: python ruff_hook.py
pass_filenames: false
always_run: true

- id: pytest
name: pytest
entry: python -m pytest tests/ --ignore=tests/test_downloader.py -q
entry: python -m pytest tests/ --ignore=tests/test_downloader.py -m "not network" -q
language: system
pass_filenames: false
always_run: true
52 changes: 52 additions & 0 deletions .specs-fire/state.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
active_intent:
id: "INTENT-001"
title: "Importar deck desde URL de deckbuilder en la tab de Magic"
status: completed
last_updated: "2026-06-21"

work_items:
- id: "WI-001"
title: "src/deck_importer.py — fetch deck list con set+número por carta"
status: completed
files:
- path: "src/deck_importer.py"
action: created

- id: "WI-002"
title: "src/scryfall.py — descarga imágenes por set+número con soporte MDFC"
status: completed
files:
- path: "src/scryfall.py"
action: created

- id: "WI-003"
title: "src/pipeline.py — run_deck_url()"
status: completed
files:
- path: "src/pipeline.py"
action: modified

- id: "WI-004"
title: "gui/xml_tab.py — sección 'Importar desde URL' con sideboard checkbox"
status: completed
files:
- path: "gui/xml_tab.py"
action: modified

- id: "WI-005"
title: "gui/main.py — AppState + worker para run_deck_url"
status: completed
files:
- path: "gui/main.py"
action: modified

- id: "WI-006"
title: "tests — test_deck_importer.py + test_scryfall.py"
status: completed
files:
- path: "tests/test_deck_importer.py"
action: created
- path: "tests/test_scryfall.py"
action: created

last_updated: "2026-06-21"
30 changes: 30 additions & 0 deletions .specsmd/fire/memory-bank.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
project: MPCFillToPDF
description: >
App Python/Tkinter que convierte proyectos de mazos de cartas
(Magic, One Piece, Riftbound, Lorcana) en PDFs para impresión en copistería.
tech_stack:
language: Python 3.10+
gui: Tkinter
pdf: reportlab
image: Pillow
download: requests + gdown
games_supported:
- Magic (XML MPCFill)
- One Piece (scraping: onepiece.gg, egmanevents, cardkaizoku)
- Riftbound (scraping)
- Lorcana (scraping)
existing_features:
- Descarga paralela de imágenes (Google Drive + scraping)
- Recorte de sangrado MPC
- Generación PDF duplex (3x3 grid, A4)
- Checkpoint / recuperación parcial tras error
- Validación XML
- ETA / velocidad de descarga
- Notificación del sistema al terminar
- Retry de descargas fallidas
- Preview de cartas locales
- Tiempos de fase (timing log)
- Split de PDF por tamaño (máx 480 MB)
- Modo solo-frontales
- Fusión de XMLs
- Importar deck desde URL (Moxfield, Archidekt) con imágenes Scryfall por set+número, soporte MDFC
13 changes: 8 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,12 @@ MPCFillToPDF/
│ └── main.py # CLI: batch-processes xml/*.xml into out/
├── gui/
│ ├── main.py # Tkinter GUI entry point
│ └── paths.py # Resolves out/ and workdir/ next to the .exe when frozen
│ └── paths.py # Resolves runtime dirs next to the .exe when frozen
├── build_exe.py # PyInstaller build script (produces dist/MPCFillToPDF.exe)
├── xml/ # Drop .xml inputs here (CLI mode)
├── out/ # Generated PDFs (gitignored)
├── workdir/ # Cached downloads + intermediate images (gitignored)
├── MPCFillToPDF/ # Created next to the .exe (or project root in dev); gitignored
│ ├── archivos generados/ # Generated PDFs
│ └── procesamiento/ # Download cache, intermediate images, gui.log
├── examples/
│ ├── example.xml # Reference MPCFill project file
│ ├── example.pdf # Target PDF output (reference)
Expand All @@ -76,11 +77,13 @@ MPCFillToPDF/
- The user picks XML(s) via a file dialog, optionally toggles "Conservar caché", and clicks **Generar PDF(s)**.
- The pipeline runs in a worker thread; UI updates via a `queue.Queue` drained from the Tk loop.
- When done, the output folder opens automatically (`os.startfile` on Windows).
- `gui/paths.py` resolves `out/` and `workdir/` next to `sys.executable` when frozen by PyInstaller, otherwise next to the project root.
- `gui/paths.py` resolves `MPCFillToPDF/archivos generados/` and `MPCFillToPDF/procesamiento/` next to `sys.executable` when frozen by PyInstaller, otherwise next to the project root.

### Packaging (V2 → .exe)
- `python build_exe.py` runs PyInstaller with `--onefile --windowed`, bundling `src/assets/` as data.
- Output: `dist/MPCFillToPDF.exe`. The .exe is portable — drop it anywhere and it creates `out/` and `workdir/` next to itself on first run.
- `python build_exe.py --debug-logging` habilita `gui.log` en el exe resultante (por defecto desactivado).
- Output: `dist/MPCFillToPDF.exe`. The .exe is portable — drop it anywhere and it creates `MPCFillToPDF/archivos generados/` and `MPCFillToPDF/procesamiento/` next to itself on first run.
- `gui/_build_flags.py` is generated by `build_exe.py` at build time and deleted afterwards; it is gitignored and never committed.

### Size-based splitting
- Cap: each output PDF stays under 500 MB on disk (decimal MB). `MAX_PDF_BYTES` in `pdf_generator.py` is set to 480 MB so the projected estimate has a safety margin.
Expand Down
107 changes: 51 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,52 @@ Convierte un archivo de proyecto de [MPCFill](https://mpcfill.com/) (XML) en un

El XML de MPCFill referencia imágenes alojadas en Google Drive. Esta herramienta las descarga, les quita el sangrado de MPC, las recoloca con un sangrado en espejo de 1 mm y monta el PDF con líneas de corte y marcas de impresora.

También soporta mazos de otros juegos de cartas descargándolos directamente desde webs especializadas (ver [Webs soportadas](#webs-soportadas-para-diferentes-tcg)).

---

## Webs soportadas para diferentes TCG

### One Piece Card Game

| Web | URL de ejemplo |
|-----|---------------|
| [onepiece.gg](https://onepiece.gg) | `https://onepiece.gg/decks/nombre-del-mazo` |
| [deckbuilder.egmanevents.com](https://deckbuilder.egmanevents.com) | `https://deckbuilder.egmanevents.com/?deck=CARTA:X,...` o `https://deckbuilder.egmanevents.com/d/CODIGO` |
| [deckbuilder.cardkaizoku.com](https://deckbuilder.cardkaizoku.com) | `https://deckbuilder.cardkaizoku.com/?deck=2xOP01-001\|3xOP01-002\|...` |

Pega la URL del mazo en la pestaña **One Piece** de la interfaz gráfica y pulsa **Añadir**.

---

### Riftbound TCG

| Web | URL de ejemplo |
|-----|---------------|
| [riftbound.gg](https://riftbound.gg) | `https://riftbound.gg/decks/nombre-del-mazo/` |
| [piltoverarchive.com](https://piltoverarchive.com) | `https://piltoverarchive.com/decks/view/<UUID>` |
| [riftmana.com](https://riftmana.com) | `https://riftmana.com/decks/nombre-del-mazo` |
| [riftbinder.com](https://riftbinder.com) | `https://riftbinder.com/decks/<ID>` |
| [riftdex.com](https://riftdex.com) | `https://riftdex.com/deck/<UUID>` |

Pega la URL del mazo en la pestaña **Riftbound** de la interfaz gráfica y pulsa **Añadir**.

> **Nota:** Solo se pueden descargar mazos **públicos**. Si el mazo no está disponible (privado o eliminado), aparecerá un mensaje de error.

---

### Lorcana

| Web | URL de ejemplo |
|-----|---------------|
| [lorcana.gg](https://lorcana.gg) | `https://lorcana.gg/decks/nombre-del-mazo/` |
| [inkdecks.com](https://inkdecks.com) | `https://inkdecks.com/lorcana-metagame/deck-nombre-ID` |
| [dreamborn.ink](https://dreamborn.ink) | `https://dreamborn.ink/es/decks/ID` |

Pega la URL del mazo en la pestaña **Lorcana** de la interfaz gráfica y pulsa **Añadir**.

> **Nota:** dreamborn.ink requiere que **Google Chrome** esté instalado, ya que la web usa protección anti-bots que solo un navegador real puede superar. El programa abre Chrome minimizado, carga la página y lo cierra automáticamente.

---

## Clave de API de Google Drive (recomendado)
Expand Down Expand Up @@ -82,59 +128,7 @@ Instala `Pillow`, `reportlab` y `gdown`.

## Uso

Hay dos formas: por línea de comandos (CLI) o con la interfaz gráfica (GUI).

### A) Línea de comandos (CLI)

1. Coloca tus archivos `.xml` de MPCFill en la carpeta `xml/` (en la raíz del proyecto). Puedes poner uno o varios.
2. Ejecuta:
```
python -m cli.main
```
3. Los PDFs aparecen en una carpeta nueva por ejecución dentro de `out/`, con el nombre `DD_MM_YYYY_HH-MM-SS`. Cada PDF se nombra como el XML de origen:
- `xml/mazo.xml` → `out/22_05_2026_14-30-12/out_mazo.pdf`
- Si un PDF supera 500 MB se parte en `out_mazo_1.pdf`, `out_mazo_2.pdf`, … (el corte siempre cae tras una página de reversos para que cada parte siga siendo imprimible a doble cara).

#### Opciones de la CLI

| Opción | Por defecto | Para qué sirve |
|----------------|-------------|----------------|
| `--xml-dir` | `xml` | Carpeta donde leer los `.xml`. |
| `--out-dir` | `out` | Carpeta donde escribir los PDFs. |
| `--workdir` | `workdir` | Carpeta para imágenes descargadas (`raw/`) e intermedias (`bled/`). |
| `--test` | desactivado | **No** borra `workdir/raw` ni `workdir/bled` al terminar; útil para iterar sin volver a descargar y recortar. |
| `--yes` / `-y` | desactivado | Continuar sin pedir confirmación si alguna baraja no es múltiplo de 9. Útil para scripts. |

Ejemplos:
```
python -m cli.main
python -m cli.main --test
python -m cli.main --xml-dir mis_xmls --out-dir resultado
python -m cli.main -y # sin prompts
```

#### Ejemplo de ejecución

```
Encontrados 2 XML(s) en 'xml'.
Carpeta de salida: out\22_05_2026_14-30-12
- mazo_a.xml: 95 cartas (4 hueco(s) en blanco)
- mazo_b.xml: 4 cartas (5 hueco(s) en blanco)

Se fusionarán las siguientes barajas para evitar huecos en blanco:
• mazo_a_mazo_b_union.pdf ← mazo_a.xml, mazo_b.xml (99 cartas)

Procesando: mazo_a_mazo_b_union (fusión)
Descargando: [##############################] 89/89 ( 12.4s)
Recortando : [##############################] 89/89 ( 6.1s)
Generando : [##############################] 11/11 ( 88.2s)
-> out\22_05_2026_14-30-12\out_mazo_a_mazo_b_union.pdf (417.5 MB)

Resumen de fusiones escrito en: out\22_05_2026_14-30-12\resumen.txt
Tiempo total: 106.7s
```

### B) Interfaz gráfica (GUI)
### Interfaz gráfica (GUI)

Lanza la ventana:
```
Expand All @@ -146,7 +140,7 @@ Aparece una ventana con:
- **Seleccionar XMLs…** abre el explorador para elegir uno o varios `.xml` (Ctrl+click para varios).
- **Lista** con los archivos en cola y botones para *Quitar selección* / *Vaciar*.
- **Conservar caché**: si está marcado, no borra `workdir/raw` y `workdir/bled` al terminar (acelera futuras regeneraciones del mismo XML). **Por defecto desactivado.**
- **Generar PDF(s)**: arranca el proceso. Solo se activa cuando hay al menos un XML en la lista.
- **Generar PDF(s)**: arranca el proceso. Solo se activa cuando hay al menos un XML en la lista o un mazo añadido desde una web.
- **Estado + barra de progreso** se actualizan durante la generación.

Antes de generar:
Expand Down Expand Up @@ -188,10 +182,8 @@ El sistema lo gestiona así:

```
MPCFillToPDF/
├── xml/ ← .xml de MPCFill (modo CLI)
├── out/ ← una subcarpeta por ejecución (DD_MM_YYYY_HH-MM-SS) con los PDFs y, si hay fusión, resumen.txt
├── workdir/ ← caché temporal: raw/ (descargas) y bled/ (recortes)
├── cli/main.py ← entrada CLI
├── gui/
│ ├── main.py ← entrada GUI (Tkinter)
│ └── paths.py ← resolver out/ y workdir/ junto al .exe
Expand All @@ -202,7 +194,10 @@ MPCFillToPDF/
│ ├── pdf_generator.py ← maquetación + crop marks + barra de calibración
│ ├── pipeline.py ← orquestador (run, run_merged)
│ ├── precheck.py ← conteo, planificación de fusiones, manifiesto
│ ├── op_scraper.py ← descarga mazos de One Piece desde webs especializadas
│ └── assets/ ← imágenes embebidas (color_bar.png, corner_mark.png)
├── resources/
│ └── backs/op/ ← reversos para cartas de One Piece (default.png, lider.png)
└── build_exe.py ← script de empaquetado PyInstaller
```

Expand Down
Loading
Loading