style: apply formatter cleanup (import order, whitespace)
This commit is contained in:
@@ -54,7 +54,7 @@ This changelog follows [Keep a Changelog](https://keepachangelog.com/) principle
|
|||||||
- **`NfoRepairService` (`src/core/services/nfo_repair_service.py`)**: New service
|
- **`NfoRepairService` (`src/core/services/nfo_repair_service.py`)**: New service
|
||||||
that detects incomplete `tvshow.nfo` files and triggers TMDB re-fetch.
|
that detects incomplete `tvshow.nfo` files and triggers TMDB re-fetch.
|
||||||
Provides `parse_nfo_tags()`, `find_missing_tags()`, `nfo_needs_repair()`, and
|
Provides `parse_nfo_tags()`, `find_missing_tags()`, `nfo_needs_repair()`, and
|
||||||
`NfoRepairService.repair_series()`. 13 required tags are checked.
|
`NfoRepairService.repair_series()`. 13 required tags are checked.
|
||||||
- **`perform_nfo_repair_scan()` startup hook
|
- **`perform_nfo_repair_scan()` startup hook
|
||||||
(`src/server/services/initialization_service.py`)**: New async function
|
(`src/server/services/initialization_service.py`)**: New async function
|
||||||
called during application startup. Iterates every series directory, checks
|
called during application startup. Iterates every series directory, checks
|
||||||
|
|||||||
@@ -639,33 +639,33 @@ curl -X POST "http://127.0.0.1:8000/api/scheduler/config" \
|
|||||||
## 10. Tag Reference
|
## 10. Tag Reference
|
||||||
|
|
||||||
The table below lists every XML tag written to `tvshow.nfo` and its source in
|
The table below lists every XML tag written to `tvshow.nfo` and its source in
|
||||||
the TMDB API response. All tags are written whenever the NFO is created or
|
the TMDB API response. All tags are written whenever the NFO is created or
|
||||||
updated via `create_tvshow_nfo()` / `update_tvshow_nfo()`.
|
updated via `create_tvshow_nfo()` / `update_tvshow_nfo()`.
|
||||||
|
|
||||||
| NFO tag | TMDB source field | Required |
|
| NFO tag | TMDB source field | Required |
|
||||||
| --------------- | ---------------------------------------------- | -------- |
|
| --------------- | ----------------------------------------------------- | -------- |
|
||||||
| `title` | `name` | ✅ |
|
| `title` | `name` | ✅ |
|
||||||
| `originaltitle` | `original_name` | ✅ |
|
| `originaltitle` | `original_name` | ✅ |
|
||||||
| `showtitle` | `name` (same as `title`) | ✅ |
|
| `showtitle` | `name` (same as `title`) | ✅ |
|
||||||
| `sorttitle` | `name` (same as `title`) | ✅ |
|
| `sorttitle` | `name` (same as `title`) | ✅ |
|
||||||
| `year` | First 4 chars of `first_air_date` | ✅ |
|
| `year` | First 4 chars of `first_air_date` | ✅ |
|
||||||
| `plot` | `overview` | ✅ |
|
| `plot` | `overview` | ✅ |
|
||||||
| `outline` | `overview` (same as `plot`) | ✅ |
|
| `outline` | `overview` (same as `plot`) | ✅ |
|
||||||
| `tagline` | `tagline` | optional |
|
| `tagline` | `tagline` | optional |
|
||||||
| `runtime` | `episode_run_time[0]` | ✅ |
|
| `runtime` | `episode_run_time[0]` | ✅ |
|
||||||
| `premiered` | `first_air_date` | ✅ |
|
| `premiered` | `first_air_date` | ✅ |
|
||||||
| `status` | `status` | ✅ |
|
| `status` | `status` | ✅ |
|
||||||
| `mpaa` | US content rating from `content_ratings.results`| optional |
|
| `mpaa` | US content rating from `content_ratings.results` | optional |
|
||||||
| `fsk` | DE content rating (written as `mpaa` when preferred) | optional |
|
| `fsk` | DE content rating (written as `mpaa` when preferred) | optional |
|
||||||
| `imdbid` | `external_ids.imdb_id` | ✅ |
|
| `imdbid` | `external_ids.imdb_id` | ✅ |
|
||||||
| `tmdbid` | `id` | ✅ |
|
| `tmdbid` | `id` | ✅ |
|
||||||
| `tvdbid` | `external_ids.tvdb_id` | optional |
|
| `tvdbid` | `external_ids.tvdb_id` | optional |
|
||||||
| `genre` | `genres[].name` (one element per genre) | ✅ |
|
| `genre` | `genres[].name` (one element per genre) | ✅ |
|
||||||
| `studio` | `networks[].name` (one element per network) | ✅ |
|
| `studio` | `networks[].name` (one element per network) | ✅ |
|
||||||
| `country` | `origin_country[]` or `production_countries[].name` | ✅ |
|
| `country` | `origin_country[]` or `production_countries[].name` | ✅ |
|
||||||
| `actor` | `credits.cast[]` (top 10, with name/role/thumb)| ✅ |
|
| `actor` | `credits.cast[]` (top 10, with name/role/thumb) | ✅ |
|
||||||
| `watched` | Always `false` on creation | ✅ |
|
| `watched` | Always `false` on creation | ✅ |
|
||||||
| `dateadded` | System clock at creation time (`YYYY-MM-DD HH:MM:SS`) | ✅ |
|
| `dateadded` | System clock at creation time (`YYYY-MM-DD HH:MM:SS`) | ✅ |
|
||||||
|
|
||||||
The mapping logic lives in `src/core/utils/nfo_mapper.py` (`tmdb_to_nfo_model`).
|
The mapping logic lives in `src/core/utils/nfo_mapper.py` (`tmdb_to_nfo_model`).
|
||||||
The XML serialisation lives in `src/core/utils/nfo_generator.py`
|
The XML serialisation lives in `src/core/utils/nfo_generator.py`
|
||||||
@@ -687,36 +687,36 @@ automatically repairs any that are missing required tags.
|
|||||||
`src/core/services/nfo_repair_service.py` parses each `tvshow.nfo` with
|
`src/core/services/nfo_repair_service.py` parses each `tvshow.nfo` with
|
||||||
`lxml` and checks for the 13 required tags listed below.
|
`lxml` and checks for the 13 required tags listed below.
|
||||||
3. **Repair** — Series whose NFO is incomplete are queued for background reload
|
3. **Repair** — Series whose NFO is incomplete are queued for background reload
|
||||||
via `BackgroundLoaderService.add_series_loading_task()`. The background
|
via `BackgroundLoaderService.add_series_loading_task()`. The background
|
||||||
loader re-fetches metadata from TMDB and rewrites the NFO with all tags
|
loader re-fetches metadata from TMDB and rewrites the NFO with all tags
|
||||||
populated.
|
populated.
|
||||||
|
|
||||||
### Tags Checked (13 required)
|
### Tags Checked (13 required)
|
||||||
|
|
||||||
| XPath | Tag name |
|
| XPath | Tag name |
|
||||||
| -------------- | --------------- |
|
| ----------------- | --------------- |
|
||||||
| `./title` | `title` |
|
| `./title` | `title` |
|
||||||
| `./originaltitle` | `originaltitle` |
|
| `./originaltitle` | `originaltitle` |
|
||||||
| `./year` | `year` |
|
| `./year` | `year` |
|
||||||
| `./plot` | `plot` |
|
| `./plot` | `plot` |
|
||||||
| `./runtime` | `runtime` |
|
| `./runtime` | `runtime` |
|
||||||
| `./premiered` | `premiered` |
|
| `./premiered` | `premiered` |
|
||||||
| `./status` | `status` |
|
| `./status` | `status` |
|
||||||
| `./imdbid` | `imdbid` |
|
| `./imdbid` | `imdbid` |
|
||||||
| `./genre` | `genre` |
|
| `./genre` | `genre` |
|
||||||
| `./studio` | `studio` |
|
| `./studio` | `studio` |
|
||||||
| `./country` | `country` |
|
| `./country` | `country` |
|
||||||
| `./actor/name` | `actor/name` |
|
| `./actor/name` | `actor/name` |
|
||||||
| `./watched` | `watched` |
|
| `./watched` | `watched` |
|
||||||
|
|
||||||
### Log Messages
|
### Log Messages
|
||||||
|
|
||||||
| Message | Meaning |
|
| Message | Meaning |
|
||||||
| --- | --- |
|
| ----------------------------------------------------------- | ------------------------------------------------- |
|
||||||
| `NFO repair scan complete: 0 of N series queued for repair` | All NFOs are complete — no action needed |
|
| `NFO repair scan complete: 0 of N series queued for repair` | All NFOs are complete — no action needed |
|
||||||
| `NFO repair scan complete: X of N series queued for repair` | X series had incomplete NFOs and have been queued |
|
| `NFO repair scan complete: X of N series queued for repair` | X series had incomplete NFOs and have been queued |
|
||||||
| `NFO repair scan skipped: TMDB API key not configured` | Set `tmdb_api_key` in `data/config.json` |
|
| `NFO repair scan skipped: TMDB API key not configured` | Set `tmdb_api_key` in `data/config.json` |
|
||||||
| `NFO repair scan skipped: anime directory not configured` | Set `anime_directory` in `data/config.json` |
|
| `NFO repair scan skipped: anime directory not configured` | Set `anime_directory` in `data/config.json` |
|
||||||
|
|
||||||
### Triggering a Manual Repair
|
### Triggering a Manual Repair
|
||||||
|
|
||||||
@@ -731,11 +731,11 @@ This calls `NFOService.update_tvshow_nfo()` directly and overwrites the existing
|
|||||||
|
|
||||||
### Source Files
|
### Source Files
|
||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
| ---- | ------- |
|
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||||
| `src/core/services/nfo_repair_service.py` | `REQUIRED_TAGS`, `parse_nfo_tags`, `find_missing_tags`, `nfo_needs_repair`, `NfoRepairService` |
|
| `src/core/services/nfo_repair_service.py` | `REQUIRED_TAGS`, `parse_nfo_tags`, `find_missing_tags`, `nfo_needs_repair`, `NfoRepairService` |
|
||||||
| `src/server/services/initialization_service.py` | `perform_nfo_repair_scan` startup hook |
|
| `src/server/services/initialization_service.py` | `perform_nfo_repair_scan` startup hook |
|
||||||
| `src/server/fastapi_app.py` | Wires `perform_nfo_repair_scan` into the lifespan |
|
| `src/server/fastapi_app.py` | Wires `perform_nfo_repair_scan` into the lifespan |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -219,8 +219,8 @@ async def lifespan(_application: FastAPI):
|
|||||||
from src.server.services.initialization_service import (
|
from src.server.services.initialization_service import (
|
||||||
perform_initial_setup,
|
perform_initial_setup,
|
||||||
perform_media_scan_if_needed,
|
perform_media_scan_if_needed,
|
||||||
perform_nfo_scan_if_needed,
|
|
||||||
perform_nfo_repair_scan,
|
perform_nfo_repair_scan,
|
||||||
|
perform_nfo_scan_if_needed,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ These tests confirm that:
|
|||||||
1. The lifespan calls perform_nfo_repair_scan after perform_media_scan_if_needed.
|
1. The lifespan calls perform_nfo_repair_scan after perform_media_scan_if_needed.
|
||||||
2. Series with incomplete NFO files are queued via the background_loader.
|
2. Series with incomplete NFO files are queued via the background_loader.
|
||||||
"""
|
"""
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch, call
|
from unittest.mock import AsyncMock, MagicMock, call, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ class TestNfoRepairScanCalledOnStartup:
|
|||||||
def test_perform_nfo_repair_scan_imported_in_lifespan(self):
|
def test_perform_nfo_repair_scan_imported_in_lifespan(self):
|
||||||
"""fastapi_app.py lifespan imports perform_nfo_repair_scan."""
|
"""fastapi_app.py lifespan imports perform_nfo_repair_scan."""
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
import src.server.fastapi_app as app_module
|
import src.server.fastapi_app as app_module
|
||||||
|
|
||||||
source = importlib.util.find_spec("src.server.fastapi_app").origin
|
source = importlib.util.find_spec("src.server.fastapi_app").origin
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ from src.server.services.initialization_service import (
|
|||||||
_validate_anime_directory,
|
_validate_anime_directory,
|
||||||
perform_initial_setup,
|
perform_initial_setup,
|
||||||
perform_media_scan_if_needed,
|
perform_media_scan_if_needed,
|
||||||
perform_nfo_scan_if_needed,
|
|
||||||
perform_nfo_repair_scan,
|
perform_nfo_repair_scan,
|
||||||
|
perform_nfo_scan_if_needed,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ from unittest.mock import AsyncMock, MagicMock
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from src.core.services.nfo_repair_service import (
|
from src.core.services.nfo_repair_service import (
|
||||||
NfoRepairService,
|
|
||||||
REQUIRED_TAGS,
|
REQUIRED_TAGS,
|
||||||
|
NfoRepairService,
|
||||||
find_missing_tags,
|
find_missing_tags,
|
||||||
nfo_needs_repair,
|
nfo_needs_repair,
|
||||||
parse_nfo_tags,
|
parse_nfo_tags,
|
||||||
|
|||||||
Reference in New Issue
Block a user