1889 lines
38 KiB
Markdown
1889 lines
38 KiB
Markdown
# `src/ui/qt_app.py`
|
||
|
||
PySide6 GUI for Curator — reframed around the Filmotéka (movie-library) workflow.
|
||
|
||
Layout:
|
||
- Toolbar / menu: configure the Pool and the Filmotéka output, import a movie,
|
||
generate the Filmotéka hardlink tree.
|
||
- Left sidebar: tag tree used as an AND-filter over the movie list.
|
||
- Center: the movie table (the contents of pool/Filmy).
|
||
- Status bar: counts and the current pool/output paths.
|
||
|
||
Source: [`src/ui/qt_app.py`](../../src/ui/qt_app.py)
|
||
|
||
## Classes
|
||
|
||
### `ImportMoviesDialog`
|
||
|
||
*class · line 52*
|
||
|
||
```python
|
||
class ImportMoviesDialog(QDialog):
|
||
```
|
||
|
||
Collect a Title + Year + TMDb id per file for a batch import into the pool.
|
||
|
||
One row per source file (filename shown; Title, Year and TMDb id editable).
|
||
"Dohledat z TMDb" fills them from TMDb — by the given id, or by searching the
|
||
filename. A single toggle decides whether the files are copied (default) or
|
||
moved into the pool.
|
||
|
||
#### `ImportMoviesDialog.__init__`
|
||
|
||
*method · line 61*
|
||
|
||
```python
|
||
__init__(parent: QWidget, sources: List[Path], existing_names: set[str] | None=None, libraries: list[str] | None=None) -> None
|
||
```
|
||
|
||
Build the batch import dialog for the given source files. `existing_names` marks names already in the pool, `libraries` fills the library selector.
|
||
|
||
#### `ImportMoviesDialog._append_row`
|
||
|
||
*method · line 162*
|
||
|
||
```python
|
||
_append_row(source: Path) -> None
|
||
```
|
||
|
||
Add one source file as a row: title/year/TMDb/ČSFD fields pre-filled from its name and its sidecar metadata, a 3D format combo, the version button and a remove button.
|
||
|
||
#### `ImportMoviesDialog._prefill_row`
|
||
|
||
*method · line 235*
|
||
|
||
```python
|
||
_prefill_row(source: Path, title_edit: QLineEdit, year_edit: QLineEdit, tmdb_edit: QLineEdit, csfd_edit: QLineEdit) -> None
|
||
```
|
||
|
||
Seed a new import row from metadata already next to the source file.
|
||
|
||
Curator's pre-index layouts win for the ČSFD link (a per-file
|
||
`.{name}.!tag` sidecar, or an old `.Curator.!index` up the tree); a
|
||
Kodi/Jellyfin `.nfo` fills what they don't carry (notably the TMDb id).
|
||
|
||
#### `ImportMoviesDialog._row_of`
|
||
|
||
*method · line 267*
|
||
|
||
```python
|
||
_row_of(title_edit: QLineEdit)
|
||
```
|
||
|
||
The row entry whose title field is `title_edit`, or `None`.
|
||
|
||
#### `ImportMoviesDialog._row_by_source`
|
||
|
||
*method · line 273*
|
||
|
||
```python
|
||
_row_by_source(source: Path)
|
||
```
|
||
|
||
The row entry for the source file `source`, or `None`.
|
||
|
||
#### `ImportMoviesDialog._refresh_skip_marks`
|
||
|
||
*method · line 279*
|
||
|
||
```python
|
||
_refresh_skip_marks() -> None
|
||
```
|
||
|
||
Strike through the rows that "Vynechat řádky bez TMDb ID" would drop. A version row is struck only when its target would be dropped.
|
||
|
||
#### `ImportMoviesDialog._candidate_stem`
|
||
|
||
*method · line 301*
|
||
|
||
```python
|
||
_candidate_stem(title: str, year: str, tmdb_id: str) -> str | None
|
||
```
|
||
|
||
The canonical 'Title (year) [tmdbid]' stem, or None if incomplete.
|
||
|
||
#### `ImportMoviesDialog._mark_collision`
|
||
|
||
*method · line 310*
|
||
|
||
```python
|
||
_mark_collision(title_edit: QLineEdit) -> None
|
||
```
|
||
|
||
Red title when the resulting pooled name already exists.
|
||
|
||
#### `ImportMoviesDialog._set_suggested`
|
||
|
||
*method · line 325*
|
||
|
||
```python
|
||
_set_suggested(edit: QLineEdit, suggested: bool) -> None
|
||
```
|
||
|
||
Blue tint marks an auto-filled value (TMDb / ČSFD), not yet confirmed.
|
||
|
||
#### `ImportMoviesDialog._remove_row`
|
||
|
||
*method · line 330*
|
||
|
||
```python
|
||
_remove_row(title_edit: QLineEdit) -> None
|
||
```
|
||
|
||
Drop a single file from the import list without restarting the dialog.
|
||
|
||
#### `ImportMoviesDialog._has_followers`
|
||
|
||
*method · line 354*
|
||
|
||
```python
|
||
_has_followers(source: Path) -> bool
|
||
```
|
||
|
||
True when another row is linked as a version *of* `source`.
|
||
|
||
#### `ImportMoviesDialog._sync_version_row_fields`
|
||
|
||
*method · line 358*
|
||
|
||
```python
|
||
_sync_version_row_fields(source: Path) -> None
|
||
```
|
||
|
||
Grey out a row's own identity fields while it's linked as a version —
|
||
its title/year/tmdbid/ČSFD come from the film it's a version *of*.
|
||
|
||
The 3D formát combo is separate: it's for a row that stays a flat,
|
||
single-file movie, so it's disabled both when the row *is* a version
|
||
(its 3D-ness belongs on its own popisek instead — see
|
||
`_link_as_version`) and when it *has* one (once grouped, 3D
|
||
lives on whichever version is actually 3D, not on the group itself).
|
||
|
||
#### `ImportMoviesDialog._refresh_version_button`
|
||
|
||
*method · line 390*
|
||
|
||
```python
|
||
_refresh_version_button(source: Path) -> None
|
||
```
|
||
|
||
Update the caption and tooltip of a row's version button: "Přiřadit jako verzi…" when unlinked, the target's name when linked.
|
||
|
||
#### `ImportMoviesDialog._link_as_version`
|
||
|
||
*method · line 409*
|
||
|
||
```python
|
||
_link_as_version(source: Path) -> None
|
||
```
|
||
|
||
Click handler of the version button. Unlinks a linked row (after confirmation), or asks for a target row and a popisek and links the row as a version of it.
|
||
|
||
#### `ImportMoviesDialog.version_links`
|
||
|
||
*method · line 466*
|
||
|
||
```python
|
||
version_links() -> dict[Path, tuple[Path, str]]
|
||
```
|
||
|
||
`{version source: (target source, popisek)}` set via "Přiřadit
|
||
jako verzi…" — read by the caller to import these as versions
|
||
(`FileManager.add_movie_version`) instead of standalone films.
|
||
|
||
#### `ImportMoviesDialog._add_files`
|
||
|
||
*method · line 472*
|
||
|
||
```python
|
||
_add_files() -> None
|
||
```
|
||
|
||
Add more source files chosen in a file dialog as new rows.
|
||
|
||
#### `ImportMoviesDialog._autofill_tmdb`
|
||
|
||
*method · line 477*
|
||
|
||
```python
|
||
_autofill_tmdb() -> None
|
||
```
|
||
|
||
Fill Title/Year/TMDb-id from TMDb per row (blue = suggested, verify).
|
||
|
||
A row that already has a TMDb id is resolved by id; otherwise the
|
||
filename is searched (narrowed by the year if present) and the top hit's
|
||
id/title/year are filled in.
|
||
|
||
#### `ImportMoviesDialog._autofill_via_csfd`
|
||
|
||
*method · line 523*
|
||
|
||
```python
|
||
_autofill_via_csfd() -> None
|
||
```
|
||
|
||
Fill ČSFD-odkaz / TMDb-id / Title / Year per row via the ČSFD bridge.
|
||
|
||
Per row: use the ČSFD link if given, otherwise search ČSFD by the cleaned
|
||
filename. From the ČSFD film take the original title + year and search
|
||
TMDb by those (Czech title as a fallback). Filled values are blue
|
||
(suggested — verify). Rows that already have a value keep it.
|
||
|
||
#### `ImportMoviesDialog.accept`
|
||
|
||
*method · line 594*
|
||
|
||
```python
|
||
accept() -> None
|
||
```
|
||
|
||
Validate the rows before the dialog closes: standalone rows need a four-digit year, version rows don't. Applies the "skip rows without TMDb ID" option first.
|
||
|
||
#### `ImportMoviesDialog.move_files`
|
||
|
||
*property · line 644*
|
||
|
||
```python
|
||
move_files -> bool
|
||
```
|
||
|
||
Whether the files should be moved into the pool instead of copied.
|
||
|
||
#### `ImportMoviesDialog.skipped_no_tmdb`
|
||
|
||
*property · line 648*
|
||
|
||
```python
|
||
skipped_no_tmdb -> list[Path]
|
||
```
|
||
|
||
The source files dropped by "Vynechat řádky bez TMDb ID" (valid after accept).
|
||
|
||
#### `ImportMoviesDialog.selected_library`
|
||
|
||
*property · line 653*
|
||
|
||
```python
|
||
selected_library -> str
|
||
```
|
||
|
||
The library chosen in the library combo box.
|
||
|
||
#### `ImportMoviesDialog.entries`
|
||
|
||
*method · line 656*
|
||
|
||
```python
|
||
entries() -> list[tuple[Path, str, str, str, str, str | None]]
|
||
```
|
||
|
||
Return (source, title, year, tmdb_id, csfd_url, format_3d) per
|
||
**standalone** row — a row linked as a version (see
|
||
`version_links`) is excluded, it isn't imported as a film of
|
||
its own. `format_3d` is always `None` for a row that has a
|
||
follower (see `_has_followers`) even if its combo still shows a
|
||
value — once grouped, 3D belongs to a specific version's popisek, not
|
||
the flat identity this row would otherwise import as.
|
||
|
||
### `ImportEpisodesDialog`
|
||
|
||
*class · line 679*
|
||
|
||
```python
|
||
class ImportEpisodesDialog(QDialog):
|
||
```
|
||
|
||
Collect a series identity + episode files, name them Series SxxEyy.ext.
|
||
|
||
#### `ImportEpisodesDialog.__init__`
|
||
|
||
*method · line 682*
|
||
|
||
```python
|
||
__init__(parent: QWidget, sources: List[Path], libraries: list[str] | None=None) -> None
|
||
```
|
||
|
||
Build the episode import dialog for a series: series identity fields (title, year, TMDb id, library) and one row per episode file.
|
||
|
||
#### `ImportEpisodesDialog._append_row`
|
||
|
||
*method · line 767*
|
||
|
||
```python
|
||
_append_row(source: Path) -> None
|
||
```
|
||
|
||
Add one episode file as a row. The SxxEyy field is pre-filled from the file name (including a part number) and colour-coded by validity.
|
||
|
||
#### `ImportEpisodesDialog._mark_code`
|
||
|
||
*method · line 790*
|
||
|
||
```python
|
||
_mark_code(edit: QLineEdit) -> None
|
||
```
|
||
|
||
Colour the SxxEyy field: green = valid, red = unparsable, plain = empty.
|
||
|
||
#### `ImportEpisodesDialog._remove_row`
|
||
|
||
*method · line 805*
|
||
|
||
```python
|
||
_remove_row(source: Path) -> None
|
||
```
|
||
|
||
Remove the row for the episode file `source` from the table and the row list.
|
||
|
||
#### `ImportEpisodesDialog._add_files`
|
||
|
||
*method · line 812*
|
||
|
||
```python
|
||
_add_files() -> None
|
||
```
|
||
|
||
Add more episode files chosen in a file dialog.
|
||
|
||
#### `ImportEpisodesDialog._add_folder`
|
||
|
||
*method · line 817*
|
||
|
||
```python
|
||
_add_folder() -> None
|
||
```
|
||
|
||
Add every video file under a picked folder (recursively, subfolders too).
|
||
|
||
#### `ImportEpisodesDialog._autofill_tmdb`
|
||
|
||
*method · line 844*
|
||
|
||
```python
|
||
_autofill_tmdb() -> None
|
||
```
|
||
|
||
Fill title, year and TMDb id from TMDb's TV search (or the given TMDb id).
|
||
|
||
#### `ImportEpisodesDialog.accept`
|
||
|
||
*method · line 870*
|
||
|
||
```python
|
||
accept() -> None
|
||
```
|
||
|
||
Validate the series identity and the episode list; warns when some episodes have no valid SxxEyy code (they will be skipped) and asks whether to continue.
|
||
|
||
#### `ImportEpisodesDialog.title`
|
||
|
||
*property · line 892*
|
||
|
||
```python
|
||
title -> str
|
||
```
|
||
|
||
The series title as typed.
|
||
|
||
#### `ImportEpisodesDialog.year`
|
||
|
||
*property · line 896*
|
||
|
||
```python
|
||
year -> str
|
||
```
|
||
|
||
The series year as typed (may be empty).
|
||
|
||
#### `ImportEpisodesDialog.tmdb_id`
|
||
|
||
*property · line 900*
|
||
|
||
```python
|
||
tmdb_id -> str
|
||
```
|
||
|
||
The TMDb TV id as typed (may be empty).
|
||
|
||
#### `ImportEpisodesDialog.library`
|
||
|
||
*property · line 904*
|
||
|
||
```python
|
||
library -> str
|
||
```
|
||
|
||
The series library chosen in the combo box.
|
||
|
||
#### `ImportEpisodesDialog.move_files`
|
||
|
||
*property · line 908*
|
||
|
||
```python
|
||
move_files -> bool
|
||
```
|
||
|
||
Whether the episode files should be moved into the pool instead of copied.
|
||
|
||
#### `ImportEpisodesDialog.sources`
|
||
|
||
*method · line 911*
|
||
|
||
```python
|
||
sources() -> list[Path]
|
||
```
|
||
|
||
The episode source files, in table order.
|
||
|
||
#### `ImportEpisodesDialog.episode_codes`
|
||
|
||
*method · line 914*
|
||
|
||
```python
|
||
episode_codes() -> dict[Path, tuple[int, int, Optional[int]]]
|
||
```
|
||
|
||
Per-file `(season, episode, part)` taken from the SxxEyy column.
|
||
|
||
Covers what the user typed / corrected in the dialog; rows whose field
|
||
is empty or unparsable are omitted (they fall back to filename parsing
|
||
in `import_episodes` and are skipped if that fails too).
|
||
|
||
### `SeriesOverviewDialog`
|
||
|
||
*class · line 929*
|
||
|
||
```python
|
||
class SeriesOverviewDialog(QDialog):
|
||
```
|
||
|
||
Read-only Series → Season → Episode tree with a per-series rename action.
|
||
|
||
#### `SeriesOverviewDialog.__init__`
|
||
|
||
*method · line 932*
|
||
|
||
```python
|
||
__init__(parent: QWidget, filehandler: FileManager) -> None
|
||
```
|
||
|
||
Build the series overview: a tree of series → seasons → episodes with buttons to edit, rename or remove the selected node.
|
||
|
||
#### `SeriesOverviewDialog._reload`
|
||
|
||
*method · line 967*
|
||
|
||
```python
|
||
_reload() -> None
|
||
```
|
||
|
||
Rescan the series libraries and rebuild the tree. Each episode shows whether its file name is already canonical ("OK") or needs renaming.
|
||
|
||
#### `SeriesOverviewDialog._selected_series_index`
|
||
|
||
*method · line 994*
|
||
|
||
```python
|
||
_selected_series_index()
|
||
```
|
||
|
||
Index of the series that contains the current tree selection, or `None`.
|
||
|
||
#### `SeriesOverviewDialog._selected_node`
|
||
|
||
*method · line 1000*
|
||
|
||
```python
|
||
_selected_node()
|
||
```
|
||
|
||
`("series"|"season"|"episode", Series, extra)` for the current
|
||
selection, by tree depth — `extra` is `None` / `(season, eps)` /
|
||
the `Episode`. `None` when nothing is selected.
|
||
|
||
#### `SeriesOverviewDialog._rename_selected`
|
||
|
||
*method · line 1015*
|
||
|
||
```python
|
||
_rename_selected() -> None
|
||
```
|
||
|
||
Rename the episodes of the selected series to the canonical `SxxEyy - Title` form and report how many were renamed, skipped or conflicting.
|
||
|
||
#### `SeriesOverviewDialog._remove_selected`
|
||
|
||
*method · line 1027*
|
||
|
||
```python
|
||
_remove_selected() -> None
|
||
```
|
||
|
||
Delete what is selected in the tree — a whole series, one season, or one episode — after confirmation.
|
||
|
||
#### `SeriesOverviewDialog._edit_selected`
|
||
|
||
*method · line 1056*
|
||
|
||
```python
|
||
_edit_selected() -> None
|
||
```
|
||
|
||
Open the series editor for the selected series and apply the changes: episode numbers, folder rename/move and retitled episodes.
|
||
|
||
### `VersionLabelDialog`
|
||
|
||
*class · line 1079*
|
||
|
||
```python
|
||
class VersionLabelDialog(QDialog):
|
||
```
|
||
|
||
Pick a label for a new movie version — a 3D formát preset or free text.
|
||
|
||
Jellyfin tells versions apart purely by this label text, so a 3D preset
|
||
just *is* the label (`3D HSBS`) — same tokens it already scans for in a
|
||
flat `.3D.HSBS` filename, just placed after " - " instead.
|
||
|
||
#### `VersionLabelDialog.__init__`
|
||
|
||
*method · line 1087*
|
||
|
||
```python
|
||
__init__(parent: QWidget, initial: str='', intro: str='') -> None
|
||
```
|
||
|
||
Build the popisek dialog: a 3D format preset or free text. An empty text is allowed and means the default (plain) version.
|
||
|
||
#### `VersionLabelDialog._sync_label`
|
||
|
||
*method · line 1126*
|
||
|
||
```python
|
||
_sync_label() -> None
|
||
```
|
||
|
||
Keep the text field in step with the 3D preset: a chosen format fills the field with `3D <FORMAT>` and disables it; "vlastní popisek" re-enables it.
|
||
|
||
#### `VersionLabelDialog.label`
|
||
|
||
*method · line 1131*
|
||
|
||
```python
|
||
label() -> str
|
||
```
|
||
|
||
The popisek as entered (stripped). Empty means the default version.
|
||
|
||
### `DuplicatesDialog`
|
||
|
||
*class · line 1135*
|
||
|
||
```python
|
||
class DuplicatesDialog(QDialog):
|
||
```
|
||
|
||
The *Tools → Duplicate check* dialog: lists groups of movies that look like the same film (same TMDb id, same title and year, or identical file size), with the reasons, and offers to merge a movie into another as a version or to delete it.
|
||
|
||
#### `DuplicatesDialog.__init__`
|
||
|
||
*method · line 1143*
|
||
|
||
```python
|
||
__init__(parent: QWidget, filehandler: FileManager) -> None
|
||
```
|
||
|
||
Build the duplicate checker: a tree of duplicate groups with the reasons they were flagged, and merge / delete actions for the selected movie.
|
||
|
||
#### `DuplicatesDialog._reload`
|
||
|
||
*method · line 1179*
|
||
|
||
```python
|
||
_reload() -> None
|
||
```
|
||
|
||
Recompute the duplicate groups from the file manager and rebuild the tree.
|
||
|
||
#### `DuplicatesDialog._selected`
|
||
|
||
*method · line 1215*
|
||
|
||
```python
|
||
_selected() -> tuple[dict, File] | None
|
||
```
|
||
|
||
The selected `(group, movie)` pair, or `None` when a group header or nothing is selected.
|
||
|
||
#### `DuplicatesDialog._sync_buttons`
|
||
|
||
*method · line 1225*
|
||
|
||
```python
|
||
_sync_buttons() -> None
|
||
```
|
||
|
||
Enable the merge and delete buttons only when a movie (not a group header) is selected.
|
||
|
||
#### `DuplicatesDialog._merge_selected`
|
||
|
||
*method · line 1230*
|
||
|
||
```python
|
||
_merge_selected() -> None
|
||
```
|
||
|
||
Fold the selected movie into another movie of its group as a version. Asks which movie to keep (when there are several) and for the popisek of the moved file.
|
||
|
||
#### `DuplicatesDialog._remove_selected`
|
||
|
||
*method · line 1260*
|
||
|
||
```python
|
||
_remove_selected() -> None
|
||
```
|
||
|
||
Delete the selected movie from the pool (all its versions), after confirmation.
|
||
|
||
### `MovieEditDialog`
|
||
|
||
*class · line 1282*
|
||
|
||
```python
|
||
class MovieEditDialog(QDialog):
|
||
```
|
||
|
||
One dialog for every editable parameter of a movie.
|
||
|
||
#### `MovieEditDialog.__init__`
|
||
|
||
*method · line 1285*
|
||
|
||
```python
|
||
__init__(parent: QWidget, file_obj: File, filehandler: FileManager) -> None
|
||
```
|
||
|
||
Build the "Upravit film" dialog for one movie: identity fields, 3D format, attributes, subtitles and the version list with add / rename / remove buttons.
|
||
|
||
#### `MovieEditDialog._reload_subtitles`
|
||
|
||
*method · line 1407*
|
||
|
||
```python
|
||
_reload_subtitles() -> None
|
||
```
|
||
|
||
Refill the subtitle table from the subtitle files attached to the movie.
|
||
|
||
#### `MovieEditDialog._add_subtitle`
|
||
|
||
*method · line 1416*
|
||
|
||
```python
|
||
_add_subtitle() -> None
|
||
```
|
||
|
||
Attach a subtitle file chosen in a dialog, asking for its language and whether it is forced.
|
||
|
||
#### `MovieEditDialog._remove_subtitle`
|
||
|
||
*method · line 1445*
|
||
|
||
```python
|
||
_remove_subtitle() -> None
|
||
```
|
||
|
||
Delete the selected subtitle file after confirmation.
|
||
|
||
#### `MovieEditDialog._refresh_identity_fields`
|
||
|
||
*method · line 1462*
|
||
|
||
```python
|
||
_refresh_identity_fields() -> None
|
||
```
|
||
|
||
(Re)fill TMDb ID and enable/disable the identity fields for the
|
||
current grouping state of `self.file_obj` — called at open and again
|
||
after adding/removing a version, since that can promote or demote it.
|
||
|
||
#### `MovieEditDialog._reload_versions`
|
||
|
||
*method · line 1494*
|
||
|
||
```python
|
||
_reload_versions() -> None
|
||
```
|
||
|
||
Refill the version table. A grouped movie lists its files with their popisek; a flat movie shows one row labelled "Výchozí".
|
||
|
||
#### `MovieEditDialog._add_version`
|
||
|
||
*method · line 1518*
|
||
|
||
```python
|
||
_add_version() -> None
|
||
```
|
||
|
||
Add another version: pick a file and a popisek, then call `add_movie_version` (which promotes a flat movie to a folder).
|
||
|
||
#### `MovieEditDialog._rename_version`
|
||
|
||
*method · line 1535*
|
||
|
||
```python
|
||
_rename_version() -> None
|
||
```
|
||
|
||
Change the popisek of the selected version (only once the movie has two or more versions).
|
||
|
||
#### `MovieEditDialog._remove_version`
|
||
|
||
*method · line 1559*
|
||
|
||
```python
|
||
_remove_version() -> None
|
||
```
|
||
|
||
Delete the selected version after confirmation. The dialog's own file can't be removed here. Down to one version, the movie is demoted back to a flat file.
|
||
|
||
#### `MovieEditDialog._add_attr`
|
||
|
||
*method · line 1587*
|
||
|
||
```python
|
||
_add_attr(key: str, value: str) -> None
|
||
```
|
||
|
||
Append a key/value row to the attribute table.
|
||
|
||
#### `MovieEditDialog._remove_attr`
|
||
|
||
*method · line 1593*
|
||
|
||
```python
|
||
_remove_attr() -> None
|
||
```
|
||
|
||
Remove the selected attribute row.
|
||
|
||
#### `MovieEditDialog._lookup`
|
||
|
||
*method · line 1598*
|
||
|
||
```python
|
||
_lookup() -> None
|
||
```
|
||
|
||
Look up the title and year from TMDb by the typed TMDb id, or by the title when no id is given.
|
||
|
||
#### `MovieEditDialog.title`
|
||
|
||
*property · line 1624*
|
||
|
||
```python
|
||
title -> str
|
||
```
|
||
|
||
The title as typed.
|
||
|
||
#### `MovieEditDialog.year`
|
||
|
||
*property · line 1628*
|
||
|
||
```python
|
||
year -> str
|
||
```
|
||
|
||
The year as typed (may be empty).
|
||
|
||
#### `MovieEditDialog.tmdb_id`
|
||
|
||
*property · line 1632*
|
||
|
||
```python
|
||
tmdb_id -> str
|
||
```
|
||
|
||
The TMDb id as typed (may be empty).
|
||
|
||
#### `MovieEditDialog.library`
|
||
|
||
*property · line 1636*
|
||
|
||
```python
|
||
library -> str
|
||
```
|
||
|
||
The library chosen in the combo box.
|
||
|
||
#### `MovieEditDialog.date`
|
||
|
||
*property · line 1640*
|
||
|
||
```python
|
||
date -> str
|
||
```
|
||
|
||
The date-added field as typed (`YYYY-MM-DD` or empty).
|
||
|
||
#### `MovieEditDialog.attributes`
|
||
|
||
*property · line 1644*
|
||
|
||
```python
|
||
attributes -> dict
|
||
```
|
||
|
||
The attribute table as a dictionary; rows without a key are ignored.
|
||
|
||
#### `MovieEditDialog.csfd_url`
|
||
|
||
*property · line 1655*
|
||
|
||
```python
|
||
csfd_url -> str
|
||
```
|
||
|
||
The ČSFD link as typed (may be empty).
|
||
|
||
#### `MovieEditDialog.format_3d`
|
||
|
||
*property · line 1659*
|
||
|
||
```python
|
||
format_3d -> str
|
||
```
|
||
|
||
Selected 3D tag, or `""` for "— žádný —" (clears any existing tag).
|
||
|
||
### `SeriesEditDialog`
|
||
|
||
*class · line 1664*
|
||
|
||
```python
|
||
class SeriesEditDialog(QDialog):
|
||
```
|
||
|
||
Edit a series (title/year/id/library) and each episode's SxxEyy.
|
||
|
||
#### `SeriesEditDialog.__init__`
|
||
|
||
*method · line 1667*
|
||
|
||
```python
|
||
__init__(parent: QWidget, ser, filehandler: FileManager) -> None
|
||
```
|
||
|
||
Build the series editor: series identity fields plus a season/episode number spin box pair for every episode.
|
||
|
||
#### `SeriesEditDialog.accept`
|
||
|
||
*method · line 1721*
|
||
|
||
```python
|
||
accept() -> None
|
||
```
|
||
|
||
Refuse to close without a series title; otherwise accept the dialog.
|
||
|
||
#### `SeriesEditDialog.episode_numbers`
|
||
|
||
*method · line 1727*
|
||
|
||
```python
|
||
episode_numbers() -> list[tuple]
|
||
```
|
||
|
||
The edited numbers as `(episode, season, episode number)` triples.
|
||
|
||
#### `SeriesEditDialog.title`
|
||
|
||
*property · line 1731*
|
||
|
||
```python
|
||
title -> str
|
||
```
|
||
|
||
The series title as typed.
|
||
|
||
#### `SeriesEditDialog.year`
|
||
|
||
*property · line 1735*
|
||
|
||
```python
|
||
year -> str
|
||
```
|
||
|
||
The series year as typed (may be empty).
|
||
|
||
#### `SeriesEditDialog.tmdb_id`
|
||
|
||
*property · line 1739*
|
||
|
||
```python
|
||
tmdb_id -> str
|
||
```
|
||
|
||
The TMDb TV id as typed (may be empty).
|
||
|
||
#### `SeriesEditDialog.library`
|
||
|
||
*property · line 1743*
|
||
|
||
```python
|
||
library -> str
|
||
```
|
||
|
||
The library chosen in the combo box.
|
||
|
||
### `LibraryOrganizerDialog`
|
||
|
||
*class · line 1747*
|
||
|
||
```python
|
||
class LibraryOrganizerDialog(QDialog):
|
||
```
|
||
|
||
Manage the pool's libraries (the top-level folders): name + kind.
|
||
|
||
#### `LibraryOrganizerDialog.__init__`
|
||
|
||
*method · line 1757*
|
||
|
||
```python
|
||
__init__(parent: QWidget, libraries: list[dict]) -> None
|
||
```
|
||
|
||
Build the library list editor: one row per library folder with a kind selector.
|
||
|
||
#### `LibraryOrganizerDialog._add_row`
|
||
|
||
*method · line 1798*
|
||
|
||
```python
|
||
_add_row(name: str, kind: str) -> None
|
||
```
|
||
|
||
Add a library row with the given name and kind.
|
||
|
||
#### `LibraryOrganizerDialog._remove_row`
|
||
|
||
*method · line 1809*
|
||
|
||
```python
|
||
_remove_row() -> None
|
||
```
|
||
|
||
Remove the selected library row (the folder on disk is not touched).
|
||
|
||
#### `LibraryOrganizerDialog.libraries`
|
||
|
||
*method · line 1814*
|
||
|
||
```python
|
||
libraries() -> list[dict]
|
||
```
|
||
|
||
The edited libraries as `{name, kind}` dictionaries; rows without a name are skipped.
|
||
|
||
### `MinidlnaLibrariesDialog`
|
||
|
||
*class · line 1826*
|
||
|
||
```python
|
||
class MinidlnaLibrariesDialog(QDialog):
|
||
```
|
||
|
||
Choose which pool libraries feed miniDLNA generation.
|
||
|
||
#### `MinidlnaLibrariesDialog.__init__`
|
||
|
||
*method · line 1829*
|
||
|
||
```python
|
||
__init__(parent: QWidget, libraries: list[dict], selected: list[str] | None) -> None
|
||
```
|
||
|
||
Build the checklist of libraries that miniDLNA generation should include.
|
||
|
||
#### `MinidlnaLibrariesDialog._set_all`
|
||
|
||
*method · line 1872*
|
||
|
||
```python
|
||
_set_all(state) -> None
|
||
```
|
||
|
||
Check or uncheck every library in the list.
|
||
|
||
#### `MinidlnaLibrariesDialog.selected_libraries`
|
||
|
||
*method · line 1876*
|
||
|
||
```python
|
||
selected_libraries() -> list[str]
|
||
```
|
||
|
||
The names of the checked libraries.
|
||
|
||
### `MigrateDialog`
|
||
|
||
*class · line 1881*
|
||
|
||
```python
|
||
class MigrateDialog(QDialog):
|
||
```
|
||
|
||
Review TMDb match proposals for an existing pool and apply the ids.
|
||
|
||
#### `MigrateDialog.__init__`
|
||
|
||
*method · line 1887*
|
||
|
||
```python
|
||
__init__(parent: QWidget, filehandler: FileManager, proposals: list) -> None
|
||
```
|
||
|
||
Build the review table of TMDb matching proposals for existing movies, with the candidate list per row.
|
||
|
||
#### `MigrateDialog._apply`
|
||
|
||
*method · line 1959*
|
||
|
||
```python
|
||
_apply(only_auto: bool) -> None
|
||
```
|
||
|
||
Assign the chosen TMDb id to each proposal (`FileManager.assign_tmdb_id`, which renames the file to the canonical `Title (rok) [tmdbid]` form). `only_auto` limits it to proposals classified as confident. Failures are collected and reported; the dialog then closes.
|
||
|
||
### `AssignTagsDialog`
|
||
|
||
*class · line 1983*
|
||
|
||
```python
|
||
class AssignTagsDialog(QDialog):
|
||
```
|
||
|
||
Tri-state bulk tag assignment over the selected movies.
|
||
|
||
Result maps full_path -> 1 (assign), 0 (remove), 2 (leave mixed/unchanged).
|
||
|
||
#### `AssignTagsDialog.__init__`
|
||
|
||
*method · line 1989*
|
||
|
||
```python
|
||
__init__(parent: QWidget, tagmanager: TagManager, files: List[File], categories: List[str] | None=None) -> None
|
||
```
|
||
|
||
Build the tag checklist for the selected movies. A tag shown as partially checked is present on only some of them.
|
||
|
||
#### `AssignTagsDialog.tagmanager_categories`
|
||
|
||
*static method · line 2028*
|
||
|
||
```python
|
||
tagmanager_categories(tagmanager: TagManager) -> List[str]
|
||
```
|
||
|
||
The category names of the tag manager, sorted.
|
||
|
||
#### `AssignTagsDialog._category_item`
|
||
|
||
*method · line 2031*
|
||
|
||
```python
|
||
_category_item(category: str) -> QTreeWidgetItem
|
||
```
|
||
|
||
Return (creating if needed) the header item for a category.
|
||
|
||
#### `AssignTagsDialog._add_tag_item`
|
||
|
||
*method · line 2041*
|
||
|
||
```python
|
||
_add_tag_item(category: str, name: str, force_checked: bool=False)
|
||
```
|
||
|
||
Add a checkable tag row under its category (deduplicated).
|
||
|
||
#### `AssignTagsDialog._add_new_tag`
|
||
|
||
*method · line 2063*
|
||
|
||
```python
|
||
_add_new_tag() -> None
|
||
```
|
||
|
||
Prompt for a category + value and add it as a checked tag.
|
||
|
||
#### `AssignTagsDialog.accept`
|
||
|
||
*method · line 2079*
|
||
|
||
```python
|
||
accept() -> None
|
||
```
|
||
|
||
Store the checkbox state of every tag in `result_map` and accept the dialog.
|
||
|
||
### `TagSchemaDialog`
|
||
|
||
*class · line 2085*
|
||
|
||
```python
|
||
class TagSchemaDialog(QDialog):
|
||
```
|
||
|
||
Editor for the tag schema: categories, their NFO source and folder rules.
|
||
|
||
Each row defines a tag category, optionally which NFO field feeds it (with a
|
||
value transform), and how it maps into the miniDLNA tree (a grouping folder,
|
||
the output root, or no folders at all).
|
||
|
||
#### `TagSchemaDialog.__init__`
|
||
|
||
*method · line 2106*
|
||
|
||
```python
|
||
__init__(parent: QWidget, schema: list[dict]) -> None
|
||
```
|
||
|
||
Build the editor of the tag schema: one row per tag category with its NFO source, whether it creates folders, and the folder name rules.
|
||
|
||
#### `TagSchemaDialog._append_row`
|
||
|
||
*method · line 2151*
|
||
|
||
```python
|
||
_append_row(entry: dict) -> None
|
||
```
|
||
|
||
Add a category row filled from a schema entry.
|
||
|
||
#### `TagSchemaDialog._combo_index`
|
||
|
||
*static method · line 2185*
|
||
|
||
```python
|
||
_combo_index(choices: list, value) -> int
|
||
```
|
||
|
||
Index of the entry whose value is `value` in a `(label, value)` choice list, or 0.
|
||
|
||
#### `TagSchemaDialog._remove_selected`
|
||
|
||
*method · line 2191*
|
||
|
||
```python
|
||
_remove_selected() -> None
|
||
```
|
||
|
||
Remove the selected category row.
|
||
|
||
#### `TagSchemaDialog._save`
|
||
|
||
*method · line 2196*
|
||
|
||
```python
|
||
_save() -> None
|
||
```
|
||
|
||
Read the table back into the schema list `result_schema`: one entry per row with a category, holding its CSFD field, value transform, miniDLNA folder root (only when "make folders" is ticked) and filename template. Then accept the dialog.
|
||
|
||
### `StatsDialog`
|
||
|
||
*class · line 2216*
|
||
|
||
```python
|
||
class StatsDialog(QDialog):
|
||
```
|
||
|
||
Read-only overview of the pool: totals + per-category tag breakdown.
|
||
|
||
#### `StatsDialog.__init__`
|
||
|
||
*method · line 2219*
|
||
|
||
```python
|
||
__init__(parent: QWidget, stats: dict, size_text: str) -> None
|
||
```
|
||
|
||
Show the library statistics (movie count, total size, average rating and the other figures from `FileManager.statistics`).
|
||
|
||
### `QtApp`
|
||
|
||
*class · line 2254*
|
||
|
||
```python
|
||
class QtApp(QMainWindow):
|
||
```
|
||
|
||
The main window: menus, the tag sidebar, the movie and series table, the detail panel and the status bar. It delegates all work to `FileManager`.
|
||
|
||
#### `QtApp.__init__`
|
||
|
||
*method · line 2255*
|
||
|
||
```python
|
||
__init__(filehandler: FileManager, tagmanager: TagManager) -> None
|
||
```
|
||
|
||
Build the main window around the file manager and the tag manager, then load the pool and restore the window geometry.
|
||
|
||
#### `QtApp.reload_series`
|
||
|
||
*method · line 2285*
|
||
|
||
```python
|
||
reload_series() -> None
|
||
```
|
||
|
||
Refresh the cached series list (scans the series/mixed libraries).
|
||
|
||
#### `QtApp._build_menu`
|
||
|
||
*method · line 2293*
|
||
|
||
```python
|
||
_build_menu() -> None
|
||
```
|
||
|
||
Create the menu bar: Pool, Filmy, Seriály, miniDLNA, Jellyfin, Nástroje, Nastavení and Testy.
|
||
|
||
#### `QtApp._add_action`
|
||
|
||
*method · line 2355*
|
||
|
||
```python
|
||
_add_action(menu: QMenu, text: str, slot, shortcut: str | None=None) -> QAction
|
||
```
|
||
|
||
Create a menu action with an optional keyboard shortcut and connect it to `slot`.
|
||
|
||
#### `QtApp._build_central`
|
||
|
||
*method · line 2363*
|
||
|
||
```python
|
||
_build_central() -> None
|
||
```
|
||
|
||
Build the central widget: the tag sidebar, the movie/series table and the detail panel, with a splitter between them.
|
||
|
||
#### `QtApp._update_details`
|
||
|
||
*method · line 2443*
|
||
|
||
```python
|
||
_update_details() -> None
|
||
```
|
||
|
||
Fill the right panel from the single selected item (movie or series).
|
||
|
||
#### `QtApp._build_statusbar`
|
||
|
||
*method · line 2469*
|
||
|
||
```python
|
||
_build_statusbar() -> None
|
||
```
|
||
|
||
Create the status bar with the pool, output and Jellyfin state labels.
|
||
|
||
#### `QtApp._update_jellyfin_status`
|
||
|
||
*method · line 2482*
|
||
|
||
```python
|
||
_update_jellyfin_status(version: str | None) -> None
|
||
```
|
||
|
||
Show Jellyfin connection state; None = not checked / not configured.
|
||
|
||
#### `QtApp.refresh_sidebar`
|
||
|
||
*method · line 2496*
|
||
|
||
```python
|
||
refresh_sidebar(filtered: Optional[List[File]]=None) -> None
|
||
```
|
||
|
||
Rebuild the filter tree, preserving the active filter and updating counts.
|
||
|
||
The count after each tag is how many of `filtered` (the movies matching
|
||
the current filter; all movies when nothing is checked) also carry that
|
||
tag — i.e. how many would remain if that tag were checked. Check state is
|
||
restored from `self._active_filter` so it survives the rebuild.
|
||
|
||
#### `QtApp._on_tag_filter_changed`
|
||
|
||
*method · line 2536*
|
||
|
||
```python
|
||
_on_tag_filter_changed(item, _col) -> None
|
||
```
|
||
|
||
Update the active AND filter when a tag checkbox in the sidebar is toggled. Category header rows are ignored.
|
||
|
||
#### `QtApp._active_filter_tags`
|
||
|
||
*method · line 2549*
|
||
|
||
```python
|
||
_active_filter_tags() -> List[Tag]
|
||
```
|
||
|
||
The active filter as `Tag` objects.
|
||
|
||
#### `QtApp._refresh_library_filter`
|
||
|
||
*method · line 2560*
|
||
|
||
```python
|
||
_refresh_library_filter() -> None
|
||
```
|
||
|
||
Repopulate the library filter combo, keeping the current choice.
|
||
|
||
#### `QtApp._selected_library_filter`
|
||
|
||
*method · line 2574*
|
||
|
||
```python
|
||
_selected_library_filter() -> str | None
|
||
```
|
||
|
||
The library the table is filtered to, or None for 'Vše'.
|
||
|
||
#### `QtApp.refresh_table`
|
||
|
||
*method · line 2581*
|
||
|
||
```python
|
||
refresh_table(*_args) -> None
|
||
```
|
||
|
||
Rebuild the table from the current state: the tag filter, the library filter and the search text are applied to movies; series are filtered by library and search only (they carry no tags). Each movie group appears once, via its primary version.
|
||
|
||
#### `QtApp._versions_word`
|
||
|
||
*static method · line 2620*
|
||
|
||
```python
|
||
_versions_word(count: int) -> str
|
||
```
|
||
|
||
The Czech plural form of "version" for a count (`verze` for 2–4, `verzí` otherwise).
|
||
|
||
#### `QtApp._fill_row`
|
||
|
||
*method · line 2623*
|
||
|
||
```python
|
||
_fill_row(row: int, item) -> None
|
||
```
|
||
|
||
Fill one table row from a movie (File) or a series.Series.
|
||
|
||
A movie made of several media files (a version folder) is marked so it
|
||
doesn't pass for a single file: `▣` before the name in bold, the
|
||
version count in the Typ column, the summed size and a tooltip listing
|
||
every file.
|
||
|
||
#### `QtApp._item_size`
|
||
|
||
*method · line 2655*
|
||
|
||
```python
|
||
_item_size(item) -> str
|
||
```
|
||
|
||
Formatted size of a movie (all its versions), or a series' episodes.
|
||
|
||
#### `QtApp._format_size`
|
||
|
||
*static method · line 2668*
|
||
|
||
```python
|
||
_format_size(size_bytes: float) -> str
|
||
```
|
||
|
||
Format a byte count with a binary unit (B, KB, MB, GB, TB), one decimal place.
|
||
|
||
#### `QtApp._selected_items`
|
||
|
||
*method · line 2675*
|
||
|
||
```python
|
||
_selected_items() -> list
|
||
```
|
||
|
||
The row objects (`File` or series) for the selected table rows, in row order.
|
||
|
||
#### `QtApp._selected_movies`
|
||
|
||
*method · line 2679*
|
||
|
||
```python
|
||
_selected_movies() -> List[File]
|
||
```
|
||
|
||
The selected rows that are movies.
|
||
|
||
#### `QtApp._selected_series`
|
||
|
||
*method · line 2682*
|
||
|
||
```python
|
||
_selected_series() -> list
|
||
```
|
||
|
||
The selected rows that are series.
|
||
|
||
#### `QtApp.edit_item`
|
||
|
||
*method · line 2685*
|
||
|
||
```python
|
||
edit_item() -> None
|
||
```
|
||
|
||
Open the right editor for the single selected row (movie or series).
|
||
|
||
#### `QtApp._show_table_menu`
|
||
|
||
*method · line 2696*
|
||
|
||
```python
|
||
_show_table_menu(pos) -> None
|
||
```
|
||
|
||
Show the context menu of the table for the current selection: series actions for a series-only selection, movie actions otherwise.
|
||
|
||
#### `QtApp._update_selection_status`
|
||
|
||
*method · line 2721*
|
||
|
||
```python
|
||
_update_selection_status() -> None
|
||
```
|
||
|
||
Refresh the detail panel and the status bar for the current selection.
|
||
|
||
#### `QtApp._update_path_status`
|
||
|
||
*method · line 2735*
|
||
|
||
```python
|
||
_update_path_status() -> None
|
||
```
|
||
|
||
Show the pool and output paths and the number of movies in the status bar.
|
||
|
||
#### `QtApp.set_pool`
|
||
|
||
*method · line 2746*
|
||
|
||
```python
|
||
set_pool() -> None
|
||
```
|
||
|
||
Choose the pool folder, then reload the movies, series and library filter.
|
||
|
||
#### `QtApp.set_filmoteka`
|
||
|
||
*method · line 2757*
|
||
|
||
```python
|
||
set_filmoteka() -> None
|
||
```
|
||
|
||
Choose the Filmotéka output folder.
|
||
|
||
#### `QtApp.reload_pool`
|
||
|
||
*method · line 2764*
|
||
|
||
```python
|
||
reload_pool() -> None
|
||
```
|
||
|
||
Reload the movies and series from the pool and refresh the table.
|
||
|
||
#### `QtApp.import_movie`
|
||
|
||
*method · line 2772*
|
||
|
||
```python
|
||
import_movie() -> None
|
||
```
|
||
|
||
Batch-import movie files: pick files, review them in `ImportMoviesDialog`, resolve collisions with films already in the pool (add as version, replace, or skip), then import the rows and attach version-linked rows to their targets.
|
||
|
||
#### `QtApp._resolve_import_conflicts`
|
||
|
||
*method · line 2888*
|
||
|
||
```python
|
||
_resolve_import_conflicts(names: list[str]) -> str | None
|
||
```
|
||
|
||
Ask what to do with films already in the pool. Returns the policy
|
||
for `FileManager.import_movie` — `"version"` (add the new file
|
||
as another version of that film), `"replace"` (the whole old film
|
||
goes, then the new one is imported) or `"skip"` — or `None` when
|
||
the user cancels the import.
|
||
|
||
There's deliberately no "keep both as separate films": these share a
|
||
TMDb id, so they would share one movie record anyway — the same film
|
||
twice is a duplicate, and a duplicate is what *versions* are for.
|
||
|
||
#### `QtApp.open_movies`
|
||
|
||
*method · line 2923*
|
||
|
||
```python
|
||
open_movies() -> None
|
||
```
|
||
|
||
Open the selected movie files with the system viewer.
|
||
|
||
#### `QtApp._open_path`
|
||
|
||
*method · line 2927*
|
||
|
||
```python
|
||
_open_path(path: Path) -> None
|
||
```
|
||
|
||
Open a file with the platform's default application (`startfile`, `open` or `xdg-open`).
|
||
|
||
#### `QtApp.assign_tags`
|
||
|
||
*method · line 2938*
|
||
|
||
```python
|
||
assign_tags() -> None
|
||
```
|
||
|
||
Assign or remove tags on the selected movies through `AssignTagsDialog`.
|
||
|
||
#### `QtApp.set_date`
|
||
|
||
*method · line 2955*
|
||
|
||
```python
|
||
set_date() -> None
|
||
```
|
||
|
||
Set or clear the date-added field on the selected movies.
|
||
|
||
#### `QtApp.set_attribute`
|
||
|
||
*method · line 2967*
|
||
|
||
```python
|
||
set_attribute() -> None
|
||
```
|
||
|
||
Set or remove one free-form attribute on the selected movies.
|
||
|
||
#### `QtApp.rename_movie`
|
||
|
||
*method · line 2991*
|
||
|
||
```python
|
||
rename_movie() -> None
|
||
```
|
||
|
||
Rename the single selected movie's file (keeping its extension).
|
||
|
||
#### `QtApp.edit_movie`
|
||
|
||
*method · line 3012*
|
||
|
||
```python
|
||
edit_movie() -> None
|
||
```
|
||
|
||
Open the consolidated edit dialog for the selected movie.
|
||
|
||
#### `QtApp.edit_series`
|
||
|
||
*method · line 3035*
|
||
|
||
```python
|
||
edit_series(ser) -> None
|
||
```
|
||
|
||
Open the series editor (params + per-episode SxxEyy tree) for `ser`.
|
||
|
||
#### `QtApp.rename_to_canonical_naming`
|
||
|
||
*method · line 3054*
|
||
|
||
```python
|
||
rename_to_canonical_naming() -> None
|
||
```
|
||
|
||
Rename movies to the 'Title (rok) [tmdbid].ext' convention from metadata.
|
||
|
||
#### `QtApp.import_episodes`
|
||
|
||
*method · line 3116*
|
||
|
||
```python
|
||
import_episodes() -> None
|
||
```
|
||
|
||
Import episode files into a series (Series/Season NN/Series SxxEyy).
|
||
|
||
The dialog opens empty — add episodes file-by-file or pick a whole series
|
||
folder (its subfolders are scanned too).
|
||
|
||
#### `QtApp.show_series`
|
||
|
||
*method · line 3144*
|
||
|
||
```python
|
||
show_series() -> None
|
||
```
|
||
|
||
Open the series overview (`SeriesOverviewDialog`) and refresh the table afterwards.
|
||
|
||
#### `QtApp.rename_all_series`
|
||
|
||
*method · line 3152*
|
||
|
||
```python
|
||
rename_all_series() -> None
|
||
```
|
||
|
||
Bulk-fix every series' episode filenames to `SxxEyy - {série}.ext`.
|
||
|
||
#### `QtApp.normalize_default_names`
|
||
|
||
*method · line 3217*
|
||
|
||
```python
|
||
normalize_default_names() -> None
|
||
```
|
||
|
||
Tools menu action: rename `<folder> - Výchozí.ext` files of grouped movies to the bare `<folder>.ext` name after confirmation.
|
||
|
||
#### `QtApp.check_duplicates`
|
||
|
||
*method · line 3235*
|
||
|
||
```python
|
||
check_duplicates() -> None
|
||
```
|
||
|
||
Tools menu action: open `DuplicatesDialog` and refresh the table if it changed anything.
|
||
|
||
#### `QtApp.migrate_pool`
|
||
|
||
*method · line 3245*
|
||
|
||
```python
|
||
migrate_pool() -> None
|
||
```
|
||
|
||
Match the existing pool against TMDb and assign ids (one-off migration).
|
||
|
||
#### `QtApp.refresh_from_tmdb`
|
||
|
||
*method · line 3279*
|
||
|
||
```python
|
||
refresh_from_tmdb() -> None
|
||
```
|
||
|
||
(Re)load genre/year/rating tags from TMDb (by the tmdbid in the name).
|
||
|
||
#### `QtApp.remove_movies`
|
||
|
||
*method · line 3309*
|
||
|
||
```python
|
||
remove_movies() -> None
|
||
```
|
||
|
||
Delete the selected movies from the pool (every version of a grouped movie is removed too), after confirmation.
|
||
|
||
#### `QtApp.remove_series`
|
||
|
||
*method · line 3330*
|
||
|
||
```python
|
||
remove_series(series_list: list | None=None) -> None
|
||
```
|
||
|
||
Delete whole series folders from the pool (all seasons/episodes).
|
||
|
||
#### `QtApp.select_minidlna_libraries`
|
||
|
||
*method · line 3352*
|
||
|
||
```python
|
||
select_minidlna_libraries() -> None
|
||
```
|
||
|
||
Choose which pool libraries feed miniDLNA generation (persisted).
|
||
|
||
#### `QtApp.generate_filmoteka`
|
||
|
||
*method · line 3370*
|
||
|
||
```python
|
||
generate_filmoteka() -> None
|
||
```
|
||
|
||
Generate the miniDLNA / Filmotéka output for the libraries in scope. Refreshes genres from TMDb, syncs the genre hardlink tree (`HardlinkManager.sync_structure`, which also removes obsolete links), hardlinks mirrored libraries 1:1 and rebuilds the mixed-library series bucket. Empty scope shows an information message instead.
|
||
|
||
#### `QtApp.edit_libraries`
|
||
|
||
*method · line 3464*
|
||
|
||
```python
|
||
edit_libraries() -> None
|
||
```
|
||
|
||
Open the library organizer (manage the pool's top-level libraries).
|
||
|
||
#### `QtApp._cleanup_orphan_folders`
|
||
|
||
*method · line 3483*
|
||
|
||
```python
|
||
_cleanup_orphan_folders() -> None
|
||
```
|
||
|
||
Offer to delete empty pool folders that are no longer libraries.
|
||
|
||
#### `QtApp.move_to_library`
|
||
|
||
*method · line 3509*
|
||
|
||
```python
|
||
move_to_library() -> None
|
||
```
|
||
|
||
Move the selected movies into another library.
|
||
|
||
#### `QtApp._jellyfin_ready`
|
||
|
||
*method · line 3539*
|
||
|
||
```python
|
||
_jellyfin_ready() -> bool
|
||
```
|
||
|
||
True when the Jellyfin URL and API key are configured; otherwise shows a warning and returns False.
|
||
|
||
#### `QtApp._jellyfin_call`
|
||
|
||
*method · line 3549*
|
||
|
||
```python
|
||
_jellyfin_call(fn)
|
||
```
|
||
|
||
Run a Jellyfin call under a wait cursor; return (result, error_str).
|
||
|
||
#### `QtApp.jellyfin_check_connection`
|
||
|
||
*method · line 3560*
|
||
|
||
```python
|
||
jellyfin_check_connection() -> None
|
||
```
|
||
|
||
Check that the configured Jellyfin server responds and show its version.
|
||
|
||
#### `QtApp.jellyfin_scan`
|
||
|
||
*method · line 3572*
|
||
|
||
```python
|
||
jellyfin_scan() -> None
|
||
```
|
||
|
||
Ask Jellyfin to scan its libraries.
|
||
|
||
#### `QtApp.jellyfin_verify`
|
||
|
||
*method · line 3586*
|
||
|
||
```python
|
||
jellyfin_verify() -> None
|
||
```
|
||
|
||
Fetch the movie list from Jellyfin and compare it with the pool (`FileManager.verify_against_jellyfin`). Reports the matched count and lists movies present only in the pool or only in Jellyfin.
|
||
|
||
#### `QtApp.edit_tag_schema`
|
||
|
||
*method · line 3627*
|
||
|
||
```python
|
||
edit_tag_schema() -> None
|
||
```
|
||
|
||
Edit the tag schema through `TagSchemaDialog` and store the result in the pool index.
|
||
|
||
#### `QtApp.show_statistics`
|
||
|
||
*method · line 3639*
|
||
|
||
```python
|
||
show_statistics() -> None
|
||
```
|
||
|
||
Show the library statistics in `StatsDialog`; the pool must not be empty.
|
||
|
||
#### `QtApp.check_consistency`
|
||
|
||
*method · line 3646*
|
||
|
||
```python
|
||
check_consistency() -> None
|
||
```
|
||
|
||
Test: does the pool index match the actual files in pool/Filmy?
|
||
|
||
#### `QtApp.check_video_integrity`
|
||
|
||
*method · line 3689*
|
||
|
||
```python
|
||
check_video_integrity() -> None
|
||
```
|
||
|
||
Test: scan movies for corrupted / unreadable video data (FFmpeg).
|
||
|
||
#### `QtApp.closeEvent`
|
||
|
||
*method · line 3772*
|
||
|
||
```python
|
||
closeEvent(event) -> None
|
||
```
|
||
|
||
Save the window size to the global config before the window closes.
|
||
|
||
## Functions
|
||
|
||
### `run`
|
||
|
||
*function · line 3779*
|
||
|
||
```python
|
||
run(filehandler: FileManager, tagmanager: TagManager) -> None
|
||
```
|
||
|
||
Create the Qt application (if none exists yet), show the main window and run the event loop until it closes.
|