Files

38 KiB
Raw Permalink Blame History

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

Classes

ImportMoviesDialog

class · line 52

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

__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

_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

_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

_row_of(title_edit: QLineEdit)

The row entry whose title field is title_edit, or None.

ImportMoviesDialog._row_by_source

method · line 273

_row_by_source(source: Path)

The row entry for the source file source, or None.

ImportMoviesDialog._refresh_skip_marks

method · line 279

_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

_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

_mark_collision(title_edit: QLineEdit) -> None

Red title when the resulting pooled name already exists.

ImportMoviesDialog._set_suggested

method · line 325

_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

_remove_row(title_edit: QLineEdit) -> None

Drop a single file from the import list without restarting the dialog.

ImportMoviesDialog._has_followers

method · line 354

_has_followers(source: Path) -> bool

True when another row is linked as a version of source.

ImportMoviesDialog._sync_version_row_fields

method · line 358

_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

_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.

method · line 409

_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.

method · line 466

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

_add_files() -> None

Add more source files chosen in a file dialog as new rows.

ImportMoviesDialog._autofill_tmdb

method · line 477

_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

_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

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

move_files -> bool

Whether the files should be moved into the pool instead of copied.

ImportMoviesDialog.skipped_no_tmdb

property · line 648

skipped_no_tmdb -> list[Path]

The source files dropped by "Vynechat řádky bez TMDb ID" (valid after accept).

ImportMoviesDialog.selected_library

property · line 653

selected_library -> str

The library chosen in the library combo box.

ImportMoviesDialog.entries

method · line 656

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

class ImportEpisodesDialog(QDialog):

Collect a series identity + episode files, name them Series SxxEyy.ext.

ImportEpisodesDialog.__init__

method · line 682

__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

_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

_mark_code(edit: QLineEdit) -> None

Colour the SxxEyy field: green = valid, red = unparsable, plain = empty.

ImportEpisodesDialog._remove_row

method · line 805

_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

_add_files() -> None

Add more episode files chosen in a file dialog.

ImportEpisodesDialog._add_folder

method · line 817

_add_folder() -> None

Add every video file under a picked folder (recursively, subfolders too).

ImportEpisodesDialog._autofill_tmdb

method · line 844

_autofill_tmdb() -> None

Fill title, year and TMDb id from TMDb's TV search (or the given TMDb id).

ImportEpisodesDialog.accept

method · line 870

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

title -> str

The series title as typed.

ImportEpisodesDialog.year

property · line 896

year -> str

The series year as typed (may be empty).

ImportEpisodesDialog.tmdb_id

property · line 900

tmdb_id -> str

The TMDb TV id as typed (may be empty).

ImportEpisodesDialog.library

property · line 904

library -> str

The series library chosen in the combo box.

ImportEpisodesDialog.move_files

property · line 908

move_files -> bool

Whether the episode files should be moved into the pool instead of copied.

ImportEpisodesDialog.sources

method · line 911

sources() -> list[Path]

The episode source files, in table order.

ImportEpisodesDialog.episode_codes

method · line 914

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

class SeriesOverviewDialog(QDialog):

Read-only Series → Season → Episode tree with a per-series rename action.

SeriesOverviewDialog.__init__

method · line 932

__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

_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

_selected_series_index()

Index of the series that contains the current tree selection, or None.

SeriesOverviewDialog._selected_node

method · line 1000

_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

_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

_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

_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

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

__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

_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

label() -> str

The popisek as entered (stripped). Empty means the default version.

DuplicatesDialog

class · line 1135

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

__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

_reload() -> None

Recompute the duplicate groups from the file manager and rebuild the tree.

DuplicatesDialog._selected

method · line 1215

_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

_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

_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

_remove_selected() -> None

Delete the selected movie from the pool (all its versions), after confirmation.

MovieEditDialog

class · line 1282

class MovieEditDialog(QDialog):

One dialog for every editable parameter of a movie.

MovieEditDialog.__init__

method · line 1285

__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

_reload_subtitles() -> None

Refill the subtitle table from the subtitle files attached to the movie.

MovieEditDialog._add_subtitle

method · line 1416

_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

_remove_subtitle() -> None

Delete the selected subtitle file after confirmation.

MovieEditDialog._refresh_identity_fields

method · line 1462

_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

_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

_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

_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

_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

_add_attr(key: str, value: str) -> None

Append a key/value row to the attribute table.

MovieEditDialog._remove_attr

method · line 1593

_remove_attr() -> None

Remove the selected attribute row.

MovieEditDialog._lookup

method · line 1598

_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

title -> str

The title as typed.

MovieEditDialog.year

property · line 1628

year -> str

The year as typed (may be empty).

MovieEditDialog.tmdb_id

property · line 1632

tmdb_id -> str

The TMDb id as typed (may be empty).

MovieEditDialog.library

property · line 1636

library -> str

The library chosen in the combo box.

MovieEditDialog.date

property · line 1640

date -> str

The date-added field as typed (YYYY-MM-DD or empty).

MovieEditDialog.attributes

property · line 1644

attributes -> dict

The attribute table as a dictionary; rows without a key are ignored.

MovieEditDialog.csfd_url

property · line 1655

csfd_url -> str

The ČSFD link as typed (may be empty).

MovieEditDialog.format_3d

property · line 1659

format_3d -> str

Selected 3D tag, or "" for "— žádný —" (clears any existing tag).

SeriesEditDialog

class · line 1664

class SeriesEditDialog(QDialog):

Edit a series (title/year/id/library) and each episode's SxxEyy.

SeriesEditDialog.__init__

method · line 1667

__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

accept() -> None

Refuse to close without a series title; otherwise accept the dialog.

SeriesEditDialog.episode_numbers

method · line 1727

episode_numbers() -> list[tuple]

The edited numbers as (episode, season, episode number) triples.

SeriesEditDialog.title

property · line 1731

title -> str

The series title as typed.

SeriesEditDialog.year

property · line 1735

year -> str

The series year as typed (may be empty).

SeriesEditDialog.tmdb_id

property · line 1739

tmdb_id -> str

The TMDb TV id as typed (may be empty).

SeriesEditDialog.library

property · line 1743

library -> str

The library chosen in the combo box.

LibraryOrganizerDialog

class · line 1747

class LibraryOrganizerDialog(QDialog):

Manage the pool's libraries (the top-level folders): name + kind.

LibraryOrganizerDialog.__init__

method · line 1757

__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

_add_row(name: str, kind: str) -> None

Add a library row with the given name and kind.

LibraryOrganizerDialog._remove_row

method · line 1809

_remove_row() -> None

Remove the selected library row (the folder on disk is not touched).

LibraryOrganizerDialog.libraries

method · line 1814

libraries() -> list[dict]

The edited libraries as {name, kind} dictionaries; rows without a name are skipped.

MinidlnaLibrariesDialog

class · line 1826

class MinidlnaLibrariesDialog(QDialog):

Choose which pool libraries feed miniDLNA generation.

MinidlnaLibrariesDialog.__init__

method · line 1829

__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

_set_all(state) -> None

Check or uncheck every library in the list.

MinidlnaLibrariesDialog.selected_libraries

method · line 1876

selected_libraries() -> list[str]

The names of the checked libraries.

MigrateDialog

class · line 1881

class MigrateDialog(QDialog):

Review TMDb match proposals for an existing pool and apply the ids.

MigrateDialog.__init__

method · line 1887

__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

_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

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

__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

tagmanager_categories(tagmanager: TagManager) -> List[str]

The category names of the tag manager, sorted.

AssignTagsDialog._category_item

method · line 2031

_category_item(category: str) -> QTreeWidgetItem

Return (creating if needed) the header item for a category.

AssignTagsDialog._add_tag_item

method · line 2041

_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

_add_new_tag() -> None

Prompt for a category + value and add it as a checked tag.

AssignTagsDialog.accept

method · line 2079

accept() -> None

Store the checkbox state of every tag in result_map and accept the dialog.

TagSchemaDialog

class · line 2085

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

__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

_append_row(entry: dict) -> None

Add a category row filled from a schema entry.

TagSchemaDialog._combo_index

static method · line 2185

_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

_remove_selected() -> None

Remove the selected category row.

TagSchemaDialog._save

method · line 2196

_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

class StatsDialog(QDialog):

Read-only overview of the pool: totals + per-category tag breakdown.

StatsDialog.__init__

method · line 2219

__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

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

__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

reload_series() -> None

Refresh the cached series list (scans the series/mixed libraries).

QtApp._build_menu

method · line 2293

_build_menu() -> None

Create the menu bar: Pool, Filmy, Seriály, miniDLNA, Jellyfin, Nástroje, Nastavení and Testy.

QtApp._add_action

method · line 2355

_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

_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

_update_details() -> None

Fill the right panel from the single selected item (movie or series).

QtApp._build_statusbar

method · line 2469

_build_statusbar() -> None

Create the status bar with the pool, output and Jellyfin state labels.

QtApp._update_jellyfin_status

method · line 2482

_update_jellyfin_status(version: str | None) -> None

Show Jellyfin connection state; None = not checked / not configured.

QtApp.refresh_sidebar

method · line 2496

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

_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

_active_filter_tags() -> List[Tag]

The active filter as Tag objects.

QtApp._refresh_library_filter

method · line 2560

_refresh_library_filter() -> None

Repopulate the library filter combo, keeping the current choice.

QtApp._selected_library_filter

method · line 2574

_selected_library_filter() -> str | None

The library the table is filtered to, or None for 'Vše'.

QtApp.refresh_table

method · line 2581

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

_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

_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

_item_size(item) -> str

Formatted size of a movie (all its versions), or a series' episodes.

QtApp._format_size

static method · line 2668

_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

_selected_items() -> list

The row objects (File or series) for the selected table rows, in row order.

QtApp._selected_movies

method · line 2679

_selected_movies() -> List[File]

The selected rows that are movies.

QtApp._selected_series

method · line 2682

_selected_series() -> list

The selected rows that are series.

QtApp.edit_item

method · line 2685

edit_item() -> None

Open the right editor for the single selected row (movie or series).

QtApp._show_table_menu

method · line 2696

_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

_update_selection_status() -> None

Refresh the detail panel and the status bar for the current selection.

QtApp._update_path_status

method · line 2735

_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

set_pool() -> None

Choose the pool folder, then reload the movies, series and library filter.

QtApp.set_filmoteka

method · line 2757

set_filmoteka() -> None

Choose the Filmotéka output folder.

QtApp.reload_pool

method · line 2764

reload_pool() -> None

Reload the movies and series from the pool and refresh the table.

QtApp.import_movie

method · line 2772

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

_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

open_movies() -> None

Open the selected movie files with the system viewer.

QtApp._open_path

method · line 2927

_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

assign_tags() -> None

Assign or remove tags on the selected movies through AssignTagsDialog.

QtApp.set_date

method · line 2955

set_date() -> None

Set or clear the date-added field on the selected movies.

QtApp.set_attribute

method · line 2967

set_attribute() -> None

Set or remove one free-form attribute on the selected movies.

QtApp.rename_movie

method · line 2991

rename_movie() -> None

Rename the single selected movie's file (keeping its extension).

QtApp.edit_movie

method · line 3012

edit_movie() -> None

Open the consolidated edit dialog for the selected movie.

QtApp.edit_series

method · line 3035

edit_series(ser) -> None

Open the series editor (params + per-episode SxxEyy tree) for ser.

QtApp.rename_to_canonical_naming

method · line 3054

rename_to_canonical_naming() -> None

Rename movies to the 'Title (rok) [tmdbid].ext' convention from metadata.

QtApp.import_episodes

method · line 3116

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

show_series() -> None

Open the series overview (SeriesOverviewDialog) and refresh the table afterwards.

QtApp.rename_all_series

method · line 3152

rename_all_series() -> None

Bulk-fix every series' episode filenames to SxxEyy - {série}.ext.

QtApp.normalize_default_names

method · line 3217

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

check_duplicates() -> None

Tools menu action: open DuplicatesDialog and refresh the table if it changed anything.

QtApp.migrate_pool

method · line 3245

migrate_pool() -> None

Match the existing pool against TMDb and assign ids (one-off migration).

QtApp.refresh_from_tmdb

method · line 3279

refresh_from_tmdb() -> None

(Re)load genre/year/rating tags from TMDb (by the tmdbid in the name).

QtApp.remove_movies

method · line 3309

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

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

select_minidlna_libraries() -> None

Choose which pool libraries feed miniDLNA generation (persisted).

QtApp.generate_filmoteka

method · line 3370

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

edit_libraries() -> None

Open the library organizer (manage the pool's top-level libraries).

QtApp._cleanup_orphan_folders

method · line 3483

_cleanup_orphan_folders() -> None

Offer to delete empty pool folders that are no longer libraries.

QtApp.move_to_library

method · line 3509

move_to_library() -> None

Move the selected movies into another library.

QtApp._jellyfin_ready

method · line 3539

_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

_jellyfin_call(fn)

Run a Jellyfin call under a wait cursor; return (result, error_str).

QtApp.jellyfin_check_connection

method · line 3560

jellyfin_check_connection() -> None

Check that the configured Jellyfin server responds and show its version.

QtApp.jellyfin_scan

method · line 3572

jellyfin_scan() -> None

Ask Jellyfin to scan its libraries.

QtApp.jellyfin_verify

method · line 3586

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

edit_tag_schema() -> None

Edit the tag schema through TagSchemaDialog and store the result in the pool index.

QtApp.show_statistics

method · line 3639

show_statistics() -> None

Show the library statistics in StatsDialog; the pool must not be empty.

QtApp.check_consistency

method · line 3646

check_consistency() -> None

Test: does the pool index match the actual files in pool/Filmy?

QtApp.check_video_integrity

method · line 3689

check_video_integrity() -> None

Test: scan movies for corrupted / unreadable video data (FFmpeg).

QtApp.closeEvent

method · line 3772

closeEvent(event) -> None

Save the window size to the global config before the window closes.

Functions

run

function · line 3779

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.