62 Commits

Author SHA1 Message Date
Maksim Totmin
3bfb0ebbfe Fix UI freeze on iPod mount/track-load and add content_hash/sync-metadata
- Move mount_device, get_device_info, and track scanning to background
  WorkerThread (new 'mount_and_load' task type) so the UI stays responsive
- Fix DeviceMonitor auto-poll to run detect_devices() in a background thread
- Refactor _on_device_detection_finished and _on_mount_clicked to use the
  new worker; remove synchronous _set_device_mounted
- Avoid double scan (get_track_count + get_all_tracks) — scan once in worker
- Add content_hash fingerprint to _scan_ipod_files and library_cache for
  track matching
- Add update_track_metadata / _write_databases_from_tracks with artwork
  override support in Nano7Database
- Add 'Sync Metadata' button and context menu action in LibraryTab
2026-06-01 12:37:49 +07:00
Maksim Totmin
25bb8105ac feat: auto-detect iPod connection via QTimer polling + fix AlreadyMounted in _mount_linux 2026-06-01 12:29:43 +07:00
Maksim Totmin
71d162520c refactor: extract main.py into ui/ tabs + app.py, extract search_itunes into services/, remove MusicBrainz 2026-06-01 09:59:00 +07:00
Maksim Totmin
e569f8c731 refactor: remove YouTube download feature, extract TrackInfo dataclass, clean up config and hotkeys 2026-06-01 09:19:14 +07:00
Maksim Totmin
9cdb27dace refactor: replace JumpSlider subclass with QProxyStyle, clamp slider seek bounds 2026-06-01 08:57:55 +07:00
Maksim Totmin
d153d69bfd feat: hide library toolbar buttons option, Ctrl+O hotkey, iTunes-style equalizer animation on playing track 2026-06-01 08:56:03 +07:00
Maksim Totmin
ec797b8074 feat: persist play/pause state — auto-resume playback or pause-on-load on restart 2026-06-01 01:25:34 +07:00
Maksim Totmin
06f6529cbe feat: remember last played track, playback position, and volume across restarts 2026-06-01 01:22:16 +07:00
Maksim Totmin
949783e76c feat: hide library progress bar and status when iPod not connected 2026-06-01 01:14:17 +07:00
Maksim Totmin
61bcaec7a3 feat: hide iPod tab/button/menu when no device + fix settings persistence
- 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
2026-06-01 01:05:05 +07:00
Maksim Totmin
1ae1e90ae3 chore: remove redundant wasmtime install from build script (now a transitive dep of iopenpod) 2026-06-01 00:58:59 +07:00
Maksim Totmin
a0b1aba50e fix: resolve iOpenPod import conflict by removing src/ from sys.path during iop imports
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.
2026-06-01 00:55:12 +07:00
Maksim Totmin
5ba346cc84 fix: add project-local iOpenPod/ fallback so /tmp cleanup won't break the app 2026-06-01 00:46:44 +07:00
Maksim Totmin
e3666c8c64 refactor: dynamic iOpenPod discovery, add to requirements
- 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)
2026-06-01 00:40:06 +07:00
Maksim Totmin
cbc888660f fix: hotkey capture compatibility, add library metadata caching
- 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
2026-06-01 00:19:44 +07:00
Maksim Totmin
ba6935420d fix: use .value for KeyboardModifier/Key enum in keyPressEvent (PyQt6+Python 3.11 compat) 2026-06-01 00:09:23 +07:00
Maksim Totmin
a93bc7907d feat: add metadata editor dialog with cover art and batch editing support
- 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
2026-06-01 00:07:42 +07:00
Maksim Totmin
1ace4ad98c feat: add configurable keyboard shortcut system
- 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
2026-05-31 23:26:41 +07:00
Maksim Totmin
8cc4ef5279 Move cover cache and temp files from /tmp to XDG cache (~/.cache/neo-pod-desktop/)
- 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
2026-05-31 23:16:42 +07:00
Maksim Totmin
6a841afaa0 Add -bitexact to MP3 encoding to suppress ffmpeg timestamp warning 2026-05-31 22:52:30 +07:00
Maksim Totmin
340b0dea9e Switch default output to MP3, fix AAC for iPod Nano 7G, auto-remove gray source rows after convert
- 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
2026-05-31 22:44:02 +07:00
Maksim Totmin
63d72adae6 fix: move iPod track deletion to background thread with progress bar, batch DB sync 2026-05-31 22:43:59 +07:00
Maksim Totmin
3da5e15fe0 fix: add +global_header +genpts ffmpeg flags, preserve cover art streams, gapless metadata for iPod Nano 7 2026-05-31 22:13:47 +07:00
Maksim Totmin
71928d3478 feat: integrate artwork module into sync pipeline for Nano 7G 2026-05-31 21:45:51 +07:00
Maksim Totmin
9da689782a fix: clamp default audio bitrate to 160 kbps for iPod Nano 7 compatibility 2026-05-31 21:45:49 +07:00
Maksim Totmin
0f12c4985f fix: robust iOpenPod imports and sync error handling
_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/.
2026-05-31 21:20:42 +07:00
Maksim Totmin
4f7bd2709c fix: resolve ipod_device import conflict with lazy imports
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.
2026-05-31 21:03:11 +07:00
Maksim Totmin
4c4a19640d feat: integrate iOpenPod SQLiteDB_Writer and iTunesDB_Writer
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)
2026-05-31 20:54:45 +07:00
Maksim Totmin
fba3fe7d05 Add HASHAB signing to iTunesCDB header + fix sync_itunescdb
- Set hashing_scheme=3 at offset 0x30 (iPod Nano 7G requirement)
- Compute and write HASHAB signature at offset 0xAB
- Import compute_hashab from hashab module
- iTunesCDB.ext creation restored (was removed accidentally)
- 0-byte iTunesDB placeholder (firmware expects this file)
2026-05-31 19:10:44 +07:00
Maksim Totmin
01902d252a Fix container, version_info, db_info schemas per iOpenPod reference
- container: 24 columns including smart_is_dynamic, smart_is_filtered
- version_info: full schema (id, major, minor, compatibility, update_level)
- db_info: full schema (pid, audio_language=-1, subtitle_language=-1)
- genre_map: rewritten to match reference schema
- item: add genre_id column
- Music container: smart_is_dynamic=1, smart_is_filtered=1
2026-05-31 19:01:05 +07:00
Maksim Totmin
29e4cc7398 Fix missing DB tables: container, version_info, db_info, genre_map, extra DBs
- 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
2026-05-31 18:55:20 +07:00
Maksim Totmin
75ea0204ff Fix iTunesCDB header: always generate proper mhfd header
- Remove broken header-preservation logic (was reusing all-zero header)
- Always write header with correct total_size at offset 8
- Add version field 0x00020002 at offset 12
- Delete old iTunesDB (iTunesDB.old) to avoid firmware confusion
2026-05-31 18:47:36 +07:00
Maksim Totmin
ab912c2e49 Fix iPod transfer: create Music/F00 dir and iTunesCDB on first run
- 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
2026-05-31 18:39:25 +07:00
Maksim Totmin
cc429eed31 Add _ensure_schema() to create SQLite tables 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
2026-05-31 18:31:26 +07:00
Maksim Totmin
8c9e146a6c Add wasmtime to AppImage build for HASHAB signing 2026-05-31 18:24:07 +07:00
Maksim Totmin
7d371e2fbf Fix ithmb append mode: preserve existing data across transfer sessions
- 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
2026-05-31 18:17:03 +07:00
Maksim Totmin
803f0c4d9a Update README with current features and project structure
- Cover art pipeline, iTunes-style player, Nano 7G DB
- AppImage build, config in ~/.config/, reembed script
- Accurate directory structure
2026-05-31 18:06:43 +07:00
Maksim Totmin
59865169e6 Remove old config.ini (migrated to ~/.config/), add *.AppImage to gitignore, fix build script 2026-05-31 18:02:21 +07:00
Maksim Totmin
8a1f6811c0 Move config to ~/.config/neo-pod-desktop/config.ini with migration
- 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
2026-05-31 18:01:55 +07:00
Maksim Totmin
7a86732953 Fix build-appimage.sh: use venv for PyInstaller, set proper paths
- Create venv with --copies to avoid system pip restrictions (PEP 668)
- Install deps into venv before PyInstaller run
- Use absolute paths for --add-data
2026-05-31 17:48:54 +07:00
Maksim Totmin
81b2ab2173 Add AppImage build script and scripts/ directory
- scripts/build-appimage.sh: builds portable x86_64 AppImage
  via PyInstaller (onedir) + appimagetool
  - Bundles Python + PyQt6 + all dependencies
  - Excludes unused Qt modules to reduce size
  - Auto-downloads appimagetool if missing
  - Generates app icon via Pillow
- .gitignore: add build/
2026-05-31 17:45:51 +07:00
Maksim Totmin
489d99c068 Add cover art pipeline (extract/encode/cache) and iTunes-style player
- 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
2026-05-31 17:45:01 +07:00
Maksim Totmin
8c576232b1 Redesign Library tab and add Download tab toggle
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
2026-05-31 16:35:13 +07:00
Maksim Totmin
885a401ad0 Rebrand project to neo-pod-desktop with updated description and improvements
- Rename project from 'youtube-to-ipod-nano' to 'neo-pod-desktop' across all files
- Update description: 'Desktop application for downloading, converting,
  managing and transferring music to iPod Nano devices'
- Update setup.py: name, description, url, entry_points, author
- Update run.py: docstring and argparse description
- Update src/__init__.py: module docstring
- Update src/main.py: docstring, window title, about label
- Update src/cli.py: docstring, class docstring, argparse description
- Update README.md: title and project description
- Improve duplicate track detection in ipod_nano7_db.py:
  case-insensitive matching, bulk removal with file cleanup
2026-05-31 16:19:15 +07:00
Maksim Totmin
a1881df910 Add iTunes-style playback bar and move Output Directory to Settings
- 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
2026-05-31 15:32:57 +07:00
Maksim Totmin
7477cfba66 Replace Ready to Transfer list with sortable table (QTableWidget)
- 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
2026-05-31 15:26:03 +07:00
Maksim Totmin
11e4d49e2b Auto-hide empty library sections in Library tab
- 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
2026-05-31 15:18:08 +07:00
Maksim Totmin
a2d92b75e0 Fix Add Files flow: manual source files now persist across scans
- 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
2026-05-31 15:15:07 +07:00
Maksim Totmin
ea5f9d038b Fix library remove: delete files from disk, not just from UI
- _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
2026-05-31 15:04:20 +07:00
Maksim Totmin
c68bc3a09a Add GTK theme integration for Linux desktop environments
- 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)
2026-05-31 15:01:17 +07:00