3.9 KiB
Tagger - Project Documentation
Version: 1.2.0 | Status: Stable | GUI: PySide6/Qt6
About
Desktop app for organizing files using hierarchical tags (category/name).
Features: Folder scanning, tag filtering (AND/OR/NOT), rename/merge tags, CSFD.cz integration (with local cache), hardlink structure, 3-level config (global/folder/file), orphaned sidecar detection.
Structure
Tagger/
├── Tagger.py # Entry point
├── src/core/ # Business logic (NO UI imports!)
│ ├── tag.py # Tag value object (immutable)
│ ├── tag_manager.py # Tag/category management
│ ├── file.py # File with metadata + CSFD cache
│ ├── file_manager.py # File management, filtering, orphan detection
│ ├── config.py # 3-level config system
│ ├── hardlink_manager.py
│ ├── csfd.py # CSFD scraper + CSFDMovie serialization
│ ├── media_utils.py # ffprobe integration
│ ├── constants.py # APP_NAME, VERSION
│ └── _version.py # Version fallback for PyInstaller
├── src/ui/
│ ├── gui.py # Qt6 GUI (MainWindow)
│ └── utils.py # load_icon()
└── tests/ # 274 tests
Architecture Rules
- UI must not contain business logic — call FileManager/TagManager
- Core must not import UI — no PySide6 in src/core/
- Dependency injection — pass via constructor
- UTF-8 everywhere —
encoding='utf-8',ensure_ascii=False
Config Files
| Level | File | Location | Contents |
|---|---|---|---|
| Global | .Tagger.!gtag |
~/.config/Tagger/ |
window geometry, last folder, recent folders |
| Folder | .Tagger.!ftag |
project folder | ignore patterns, hardlink settings |
| File | .filename.!tag |
same dir as file | tags, date, csfd_url, csfd_cache |
Key Components
Tag — immutable, Tag(category, name), Tag.from_string("cat/name")
File — file_path, tags[], date, csfd_url, csfd_cache, metadata in .filename.!tag
apply_csfd_tags()— fetch + cache CSFD dataget_cached_movie()— return CSFDMovie from cache (no network)set_csfd_url()— invalidates cache on URL change
TagManager — add_tag(), get_categories(), rename_tag(), merge_tag()
FileManager — append(folder), filter_files_by_tags(), close_folder()
on_orphaned_tagscallback — fires when orphaned.!tagsidecars are foundfind_orphaned_tags()— manual scan for orphaned sidecars
HardlinkManager — create_structure_for_files(), sync_structure()
CSFDMovie — to_dict() / from_dict() for cache serialization
Filtering
# AND (default — all must match)
fm.filter_files_by_tags(["Žánr/Drama", "Rok/1990"])
# OR — at least one must match
fm.filter_files_by_tags(any_of=["Žánr/Drama", "Žánr/Thriller"])
# NOT — none of these
fm.filter_files_by_tags(must_not=["Stav/Nové"])
# Combined
fm.filter_files_by_tags(
must_have=["Žánr/Drama"],
any_of=["Rok/1990", "Rok/1991"],
must_not=["Stav/Nové"],
)
Running
poetry run python Tagger.py # GUI
poetry run pytest -q # Tests
poetry run pyinstaller --onefile Tagger.py # Build
Shortcuts
Ctrl+O Open | Ctrl+T Tags | Ctrl+D Date | Ctrl+W Close | F5 Refresh | Del Remove
Debugging
Version 0.0.0 in build: Run app once from source to update _version.py, then rebuild.
Cannot import: Use poetry run python Tagger.py
Metadata corrupted: Auto-recovers with defaults.
Config not saved: Check ~/.config/Tagger/ exists and is writable.
Metrics
- Tests: 274 ✅
- Python: 3.14+
- Dependencies: PySide6, requests, beautifulsoup4, loguru, python-dotenv