This document provides coding agents with essential information about the SPVideoCoursesPlayer codebase.
SPVideoCoursesPlayer is a PyQt6-based desktop video player for Windows, designed for managing and watching downloaded video courses with progress tracking, markers, and advanced audio features.
Tech Stack: Python 3.10+, PyQt6, libmpv, SQLite, FFmpeg
python main.py# Check translation completeness (verifies all keys exist in en.json and ru.json)
python tests/check_translations.py
# Test taskbar buttons functionality
python tests/test_taskbar_buttons.py
# Run a single test file
python tests/<test_name>.py# Build standalone Windows executable with PyInstaller
pyinstaller --name "SP Video Courses Player" --windowed --icon=resources/icons/app.ico main.py
# Output will be in dist/ folderpip install -r requirements.txt
# Required: PyQt6, python-mpv, comtypes, mutagen, pyinstaller# Debug scanner functionality
python debug_scanner.py
# Verify refactoring integrity
python verify_refactoring.py
# Check Python version (requires 3.10+, tested with 3.14.2)
python --versionSPVideoCoursesPlayer/
├── main.py # Application entry point, main window, PiP overlay
├── player.py # Video player widget with MPV integration
├── library.py # Video library tree widget and delegates
├── database.py # DatabaseManager for SQLite operations
├── config_manager.py # Settings.ini read/write operations
├── scanner.py # Video scanning and thumbnail generation
├── mpv_handler.py # MPV DLL setup and video widget
├── translator.py # i18n translation system (Translator class, tr())
├── constants.py # Project-wide path constants (ROOT_DIR, RESOURCES_DIR, DATA_DIR)
├── utils.py # Shared utility functions (format_time, natural_sort_key, etc.)
├── hotkeys.py # Keyboard shortcut management
├── styles.py # Qt stylesheet definitions (StyleManager, DARK_STYLE)
├── icon_manager.py # Icon loading and caching
├── video_item_data.py # VideoItemData class for library items
├── thumbnail_provider.py # Thumbnail generation and caching
├── search_utils.py # Smart search functionality
├── taskbar_progress.py # Windows taskbar integration
├── *_dialog.py # Various dialog windows (settings, tags, markers, etc.)
├── *_popup.py # Popup widgets (subtitles, volume, preview, etc.)
├── update_*.py # Update utilities (app, ffmpeg, libmpv)
├── resources/ # Icons, translations, binaries, styles
│ ├── translations/ # en.json, ru.json
│ ├── styles/ # dark.qss
│ └── bin/ # ffmpeg.exe, ffprobe.exe, libmpv-2.dll
├── data/ # SQLite DB, thumbnails (gitignored)
└── tests/ # Test scripts
- Standard library imports first
- Third-party imports second (PyQt6, etc.)
- Local imports last
- Group related imports together
- Use
from constants import ROOT_DIR, RESOURCES_DIR, DATA_DIRfor paths
Example:
import sys
import os
from pathlib import Path
from PyQt6.QtWidgets import QWidget, QVBoxLayout
from PyQt6.QtCore import Qt, pyqtSignal
from constants import ROOT_DIR, RESOURCES_DIR
from utils import format_time, natural_sort_key
from translator import tr- Indentation: 4 spaces (no tabs)
- Line length: Aim for 100-120 characters, but not strict
- Quotes: Single quotes
'preferred, double quotes"for strings with single quotes - Docstrings: Use triple double-quotes
"""for module/class/function docs
- Classes: PascalCase (e.g.,
VideoPlayerWidget,DatabaseManager) - Functions/Methods: snake_case (e.g.,
format_time,get_video_info) - Constants: UPPER_SNAKE_CASE (e.g.,
ROOT_DIR,DATA_DIR) - Private methods: Prefix with underscore (e.g.,
_read_config,_print_lock) - PyQt signals: snake_case (e.g.,
video_finished,position_changed)
- Not consistently used throughout the codebase
- Add type hints for new functions when clarity is needed
- Use
Pathfrom pathlib for file paths
- Use try-except blocks for file I/O, database operations, and external processes
- Log errors with
logging.error()including traceback:logging.error(f"Error: {e}", exc_info=True) - Use descriptive error messages with emoji prefixes:
❌ ERROR,⚠️ WARNING,✅ SUCCESS,ℹ️ INFO - Fail gracefully in UI code - don't crash the application
- Always wrap MPV operations in try-except blocks (MPV can throw exceptions during playback)
Example:
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
except Exception as e:
logging.error(f"❌ Failed to load {file_path}: {e}", exc_info=True)
return None- Use Python's
loggingmodule (configured in main.py) - Levels:
DEBUG,INFO,WARNING,ERROR - Format:
logging.debug(f"Message with {variable}") - Comment out verbose debug logs in production code
- Always use
DatabaseManagerclass (database.py) - Use context managers:
with self.db.get_connection() as conn: - Use parameterized queries to prevent SQL injection
- Foreign keys are enabled with CASCADE deletes
- Use
tr('key.subkey')for all user-facing strings - Translation keys use dot notation:
'player.play','settings.title' - Support placeholders:
tr('video_info.size_kb', size='123.4') - Translation files:
resources/translations/en.json,ru.json
- Inherit from appropriate Qt base classes
- Use signals for inter-widget communication
- Connect signals in
__init__or dedicated setup methods - Use
pyqtSignalfor custom signals - Prefer
QTimer.singleShot()for delayed execution - Use
Qt.ConnectionType.QueuedConnectionfor thread-safe signals
- Always use
pathlib.Pathobjects, not strings - Import constants:
from constants import ROOT_DIR, RESOURCES_DIR, DATA_DIR - Use
/operator for path joining:RESOURCES_DIR / 'icons' / 'play.png' - Check existence with
.exists(), create dirs with.mkdir(exist_ok=True)
- All settings managed through
ConfigManagerclass - Settings stored in
resources/settings.ini(gitignored) - Use
config.get_*()methods, never read INI directly - Defaults defined in
ConfigManager.DEFAULTS
- Use
ThreadPoolExecutorfor concurrent operations (scanner.py) - Use
threading.Lock()for shared resource protection - Use
QThreadfor long-running Qt operations - Emit signals from worker threads to update UI
- Create
*_dialog.pyfile - Inherit from
QDialog - Use
tr()for all text - Apply dark theme:
self.setStyleSheet(DARK_STYLE) - Import and instantiate in main.py
- Add CREATE TABLE in
database.py→init_database() - Add indices if needed
- Create getter/setter methods in
DatabaseManager - Update scanner.py if table needs population during scan
- Add to
resources/translations/en.jsonandru.json - Use nested structure:
{"player": {"play": "Play"}} - Run
python tests/check_translations.pyto verify
- Python Version: Requires Python 3.10+ (tested with 3.14.2)
- Windows-only: Uses comtypes for taskbar integration
- MPV required: libmpv-2.dll must be in resources/bin/
- FFmpeg required: For thumbnail generation and video analysis
- Encoding: UTF-8 everywhere,
setup_encoding()called at startup - Locale:
locale.setlocale(locale.LC_NUMERIC, 'C')for MPV compatibility - No linting: Project doesn't use flake8/pylint/black - follow existing style
- No unit tests: Only utility test scripts in tests/
- High DPI: Application supports High DPI displays with Qt's PassThrough scaling policy
- Enable debug logging: Already set to
logging.DEBUGin main.py - Check
data/video_courses.dbwith SQLite browser - Verify binary paths in settings.ini
- Test translations with
tests/check_translations.py - Use
logging.debug()liberally, comment out before commit
- PyQt6: GUI framework
- python-mpv: MPV player bindings
- comtypes: Windows COM for taskbar features
- mutagen: Audio metadata reading
- pyinstaller: Building standalone executable
- Path Handling: ALWAYS use
pathlib.Pathobjects, never strings - Hotkey Centralization: Route all keyboard events through
HotkeyManager(uses physical scan codes for layout independence) - Focus Suppression: Standardize
setFocusPolicy(Qt.FocusPolicy.NoFocus)on buttons/sliders - MPV Safety: Wrap all MPV calls in try/except blocks
- Config & Settings: Add new user-facing toggles to
settings.iniwith defaults inConfigManager.DEFAULTS - Signal Management: Use
blockSignals(True)during filtering/batch operations to prevent accidental DB writes - Icon Loading: In dialogs, call
load_icons()BEFOREsetup_ui()to avoid crashes - Window State: When restoring QSplitter state, explicitly call
setCollapsible()AFTERrestoreState()to enforce desired behavior
- Maintain existing code style and patterns
- Use
tr()for any user-visible text - Add error handling with logging
- Test with both English and Russian translations
- Verify database migrations if schema changes
- Update CHANGELOG.md for significant changes
- Don't commit data/, settings.ini, or binaries
- MPV Threading: Background threads must not touch the UI - use signals for thread-safe communication
- CSS Overrides: Global styles (DARK_STYLE) can interfere with small UI elements - test thoroughly
- Menu Initialization: Define
QActionobjects before usage to avoid crashes - Preview Performance: FFmpeg frame extraction via
QProcesshas unavoidable delay but is stable - Splitter State:
restoreState()overwrites code-level settings - always re-applysetCollapsible()after restore
- Main entry:
main.py(3000+ lines, contains MainWindow, PiPOverlay) - Config:
resources/settings.ini(gitignored, managed by ConfigManager) - Database:
data/video_courses.db(SQLite, WAL mode enabled) - Translations:
resources/translations/en.json,ru.json - Binaries:
resources/bin/(ffmpeg.exe, ffprobe.exe, libmpv-2.dll)
MainWindow(main.py): Main application windowVideoPlayerWidget(player.py): MPV-based video playerHoverTreeWidget(library.py): Video library tree viewDatabaseManager(database.py): All DB operationsConfigManager(config_manager.py): Settings.ini managementVideoScanner(scanner.py): Video scanning and thumbnail generationHotkeyManager(hotkeys.py): Centralized keyboard handling
- Get config value:
config.get_language(),config.get_video_extensions() - Set config value:
config.set_language('en'),config.save() - DB query:
with self.db.get_connection() as conn: cursor = conn.cursor(); ... - Translate text:
tr('player.play'),tr('video_info.size_kb', size='123.4') - Load icon:
QIcon(str(RESOURCES_DIR / 'icons' / 'play.png'))