- Hide iPod tab when no iPod connected, show on device detection
- Disable Transfer button and context menu item when no iPod mounted
- Add missing settings load/save: video_resolution, use_musicbrainz, show_download
- Fix clean_temp section mismatch (was [General], now [Advanced])
- Add _save_settings() called on closeEvent
iTunesDB_Writer's internal 'from ipod_device import ChecksumType' was resolving
to our local src/ipod_device.py instead of iOpenPod's version, even with
iop path at sys.path[0] (Python 3.14 import resolution quirk).
Now temporarily removes project src/ from sys.path during iOpenPod imports,
restoring it afterwards. The validated flow: main.py imports local ipod_device
first, then _import_iop evicts and replaces it when iPod tab is used.
- Replace hardcoded /tmp/iOpenPod with importlib.util.find_spec lookup
with fallback to /tmp/iOpenPod and ~/iOpenPod for dev/backward compat
- Add iopenpod>=1.0.53 to requirements.txt
- Remove deprecated mkdir temp/downloads/converted from install.sh
(all paths now XDG-compliant)
- Fix KeyCaptureDialog for both old (int) and new (enum) PyQt6 enum types
using universal _to_int() helper
- New library_cache.py: JSON cache (~/.cache/neo-pod-desktop/library_cache.json)
with mtime-based invalidation — second launch skips tag extraction for
unchanged files (instant load for large libraries)
- _scan_library(force=False) uses cache by default; Refresh/F5 uses force=True
- _incremental_update_after_edit() updates cache + refreshes UI without
full filesystem walk
- Cache entries invalidated on file deletion
- Add update_tags() method to MetadataHandler for writing arbitrary tag
changes to MP3/M4A/FLAC/OGG files (title, artist, album, album_artist,
genre, date, track/disc number, comment, cover art)
- New MetadataEditorDialog (QDialog) with: cover art preview (drag-drop,
click-to-change, remove), form fields for all common tags, F2 / context
menu / toolbar button integration
- Batch editing: select multiple tracks to edit common fields (title and
track numbers hidden), mixed values shown as placeholder
- Search now filters across Artist, Title, and Album columns
- Fix: layout warning from double setLayout in dialog
- Fix: cover cache invalidation after metadata edit
- Fix: missing QDialog import causing crash in shortcut remapping
- Add F2 hotkey default for Edit Metadata action
- src/hotkeys.py: HotkeyManager with 17 default shortcuts, config
load/save via config.ini, KeyCaptureDialog for reassignment UI
- src/config_loader.py: [Hotkeys] section defaults, _set_defaults
called on every load to backfill missing sections
- src/main.py: integrate HotkeyManager, seek_forward/backward (+/-5s),
context-aware select_all/delete_selected, Settings tab UI with
shortcut table and Reset to Defaults button
- Add xdg_cache_path() utility respecting
- CoverCache now stores covers in ~/.cache/neo-pod-desktop/covers/
- MetadataHandler temp files in ~/.cache/neo-pod-desktop/thumbnails/
- Survives reboot, no more lost cover_path during process_file
- Separate AAC and MP3 ffmpeg paths: remove +global_header and +genpts from MP3
- Clamp AAC bitrate to 160 kbps (iPod Nano 7G max); warn on override
- Make mp3 the default format in UI, converter, and CLI
- Auto-remove converted source tracks from library UI (keep files on disk)
- Bulk delete_tracks with single scan+sync (progress support)
- Async duplicate removal with progress via WorkerThread
_import_iop() now permanently evicts local ipod_device from sys.modules
on first call (instead of restoring it), caches all iOpenPod modules,
and avoids the spurious ModuleNotFoundError cascade on rapid calls.
sync_itunescdb() wrapped in try/except — no longer propagates
import/sync errors to add_track() callers.
ipod_device.py fallback no longer duplicates files to /Music/ when
add_track() already copied them to F00/.
main.py imports our local ipod_device first, which caches it in
sys.modules. When ipod_nano7_db later tried 'from ipod_device import
ChecksumType', Python returned the cached local module (which lacks
ChecksumType), causing 'ipod_nano7_db not available' and falling back
to simple file copy into wrong /Music/ directory.
Fix: replace top-level iOpenPod imports with lazy _import_iop() helper
that temporarily evicts local ipod_device from sys.modules, adds
/tmp/iOpenPod to sys.path, loads the iOpenPod module, then restores
the path. All iOpenPod imports now happen inside method bodies only.
Replace custom SQLite schema and binary mhbd writer with iOpenPod's
battle-tested database generators. The Nano 7G firmware reads SQLite
exclusively and cross-references db_id between binary and SQLite —
the old code had mismatched IDs, missing base_location table, and
incomplete schema.
Changes:
- ipod_nano7_db.py: full rewrite — use iOpenPod write_sqlite_databases
+ write_itunesdb instead of manual SQLite inserts and broken mhfd
- add_track() now copies file then regenerates ALL databases atomically
- sync_itunescdb() scans iPod filesystem, builds TrackInfo, calls
iOpenPod writers with matching db_pid
- get_all_tracks() scans filesystem with mutagen tag extraction
- ipod_mhbd_writer.py: kept as standalone reference/utility
Fix: music finally shows on iPod Nano 7G (xC695)
- Add container table with Master (iPod) and Music containers
- Add version_info, db_info, genre_map, location_kind_map, category_map
- Create Dynamic.itdb, Extras.itdb, Genius.itdb with minimal schemas
- Insert default container entries with correct PIDs
- Nano 7G firmware requires all these for music display
- copy_track_to_ipod: create target directory via os.makedirs
- sync_itunescdb: create iTunesCDB from scratch if missing
- sync_itunescdb: create iTunesCDB.ext with hash if missing
- Creates all required tables (item, artist, album, avformat_info,
store_info, item_to_container, track_size_calc) in Library.itdb
- Creates location table in Locations.itdb
- Called from Nano7Database.__init__(), so empty DB is always valid
- Fixes 'no such table: item' error on iPod with wiped DB
- Change ithmb file open from 'wb' (truncate) to 'ab' (append)
- Compute write offset from actual file position via seek(0,2)
- Prevents losing all previous artwork data on each transfer
- Config is now in XDG_CONFIG_HOME (~/.config/) for write access in AppImage
- Auto-migrates existing config.ini from project root on first run
- Creates directory on save() if not exists
- Create venv with --copies to avoid system pip restrictions (PEP 668)
- Install deps into venv before PyInstaller run
- Use absolute paths for --add-data
- New src/artwork/ module with iPod Nano 7G cover art support
- codecs: RGB565/RGB555/UYVY/JPEG encode/decode via numpy
- presets: 50+ iPod artwork format definitions
- chunks: ArtworkDB binary parser/writer (MHFD→MHSD→MHLI→MHII→MHNI)
- writer: ithmb file generation + ArtworkDB assembly
- extractor: cover extraction from audio files (APIC/covr/FLAC pictures)
- cache: local JPEG cache for player UI
- iTunes-style playback bar with 60x60 cover art display
- Scan library now caches cover art by artist+album
- iPod Nano7 transfer writes artwork to ArtworkDB + ithmb
- FFmpeg conversion preserves embedded cover art (-map 0:t?)
- One-time reembed_covers.py script in /tmp
- MetadataHandler process_file() fallback to cover_path
- Bug fixes: missing read_existing_artwork import, signed PID in Q format
Library tab redesign:
- Merge 'Ready to Transfer' table and 'Source Files' list into one unified table
- Replace bottom buttons with top toolbar (Add Files, Convert, Transfer, Remove, Refresh, Search)
- Add search/filter bar to filter tracks by title
- Add context menu (right-click): Play, Convert/Transfer, Show in File Manager, Remove
- Source tracks shown in gray (dimmed), ready tracks in normal color
- Remove status column with emoji — clean iTunes-like layout
Settings:
- Add 'Show YouTube Download Tab' checkbox in Advanced section
- Download tab hidden by default, can be enabled in Settings
- Uses QTabWidget.setTabVisible() to toggle without destroying widgets
- Add QMediaPlayer + QAudioOutput for playback within the app
- Playback bar at top of Library tab with:
- Transport controls: Prev, Play/Pause, Next
- Seekable progress slider with current/total time labels
- Volume slider
- Now playing label showing Artist - Title
- Double-click table row to start playback
- Playback auto-advances to next track on end
- Move Output Directory setting from Library tab to Settings tab
- Output Directory now shown in a dedicated 'Library' group in Settings
- Add _setup_player(), _play_track_from_index(), _format_time_ms()
- Add all playback handlers for position, duration, status changes
- Replace QListWidget with QTableWidget in Library tab
- 6 columns: #, Artist, Title, Duration, Genre, Size
- Track numbers displayed without leading zeros (1, 2, 3...)
- Dash shown when track number is unknown
- Sort by track number within album by default
- Clickable column headers for sorting by any field
- Alternating row colors for readability
- Row selection (SelectRows behavior)
- Artist and Title columns stretch to fill space
- Add genre field to TrackInfo dataclass
- Include genre in metadata extract_tags() return value
- Extract genre and track_number during library scan
- Hide 'Ready to Transfer' header and list when no tracks are ready
- Hide 'Source Files — Need Conversion' header and list when no source files exist
- Sections appear/disappear automatically on scan, add, convert, delete
- Add self.library_source_files list to store manually added file paths
- _on_library_add_files now adds to library_source_files instead of
calling _scan_library() (which would clear the list)
- _refresh_source_ui merges scanned sources + library_source_files
with dedup by path. All items get proper UserRole data.
- _on_library_remove_selected now removes from library_source_files too
- Previously added files would be lost on every scan because
_scan_library() called .clear() on the source list
- _on_library_remove_selected now removes files from disk with confirmation
- Shows count, file names, sizes, and total size in dialog
- Cleans up empty parent directories after deletion
- Re-scans library after deletion to refresh the UI
- Add _setup_linux_theming() called before QApplication creation
- Auto-detect libqgtk3.so plugin from common Qt6 plugin paths
- Sets QT_QPA_PLATFORMTHEME=gtk3 so Qt follows system GTK theme
- Falls back to qt6ct if gtk3 plugin not found
- Falls back to Fusion style if no platform theme available
- Respects user-set QT_QPA_PLATFORMTHEME (never overrides)
- Add _cleanup_worker() helper: calls wait() then deleteLater() before
dropping the Python reference. This ensures the C++ thread is fully
cleaned up before the QObject is destroyed.
- Replace all 'self.worker_thread = None' with _cleanup_worker()
- Add running thread guards to all handlers: prevent starting a new
WorkerThread while one is already running
- Add timeout to wait() calls (3000ms) to avoid hanging on close
- WorkerThread.stop() now calls self.wait() to ensure clean shutdown