23 Commits
Author SHA1 Message Date
luciano 75e3da5f82 charts: modulo iTunes + Last.fm + Spotify + M2O con cache SQLite
Nuovo core/charts.py con quattro sorgenti classifiche:
- fetch_itunes(country): Top 100 iTunes RSS per paese (it/us/gb/ww)
- fetch_lastfm(decade, genre): top tracks Last.fm per tag combinato
  (es. "90s dance", "italo disco") - richiede API key (demo hardcoded,
  sovrascrivibile via config `lastfm_api_key`)
- fetch_spotify(playlist_key): brani da 6 playlist chart editoriali
  Spotify (Top 50 / Viral 50 per Global/Italy/USA/UK) via Client
  Credentials gia' configurate
- fetch_m2o(): scraping HTML della chart dance italiana settimanale

Tutte con cache SQLite (~/Library/Application Support/MusicTools/
charts_cache.db) TTL 6h, key per sorgente+params, invalidazione via
force=True.

26 test coprono parsing, cache hit/miss, TTL scaduta (freezegun),
gestione errori HTTP/JSON e assenza credenziali.
2026-08-28 13:37:26 +02:00
luciano 9f9e4281b7 catalog: modulo AcoustID lookup + move to <year>/<genre>/ 2026-08-27 10:00:32 +02:00
luciano e7d11d0235 feat: Dedup enhancements (streaming, retry, filename mode, scroll, auto-select)
Iterazioni sul feature Dedup (v1.10.0) da subito-post-scaffolding:
- compute_fingerprint espone il vero errore invece del generico None,
  worker propaga l'err_msg via progress_callback esteso
- Retry automatico: -length 30 su "invalid data / decoding frame",
  -length 60 + -algorithm 1 su "fingerprint vuoto"
- Nuovo metodo "filename" (Jaccard sui token nome file) con algoritmo
  incrementale O(N·K) — utile per file corrotti o per anteprima veloce
- Streaming groups: sia fingerprint che filename emettono `dedup:group`
  appena un gruppo raggiunge >=2 file; JS accumula in Map per update
  in-place. Progress bar avanza in tempo reale
- Fix shape bug che dava "NaN duplicati" nella summary line
- .dedup-groups-scroll: max-height 55vh + overflow-y auto per non
  perdere l'header/footer scrollando molti gruppi
- Bottone "Seleziona tutti i consigliati" nel footer: ripristina la
  selezione di default (tutti tranne il TIENI marcati per cancellazione)
- .gitignore: dedup_cache.db (SQLite locale per macchina utente)
2026-08-02 13:59:39 +02:00
luciano fb13b0f70e dedup: modulo audio fingerprinting via fpcalc + cache SQLite
Nuovo modulo core.dedup per rilevare brani audio duplicati usando
Chromaprint (fpcalc): scan cartella (opzionalmente ricorsivo),
fingerprint acustico via fpcalc -json, cache SQLite in
_get_config_dir()/dedup_cache.db per non ricalcolare al re-scan,
raggruppamento per fingerprint identico + ordering per bitrate DESC.
move_to_trash() usa send2trash (reversibile via Finder/Explorer).

- core/paths.py: find_fpcalc() con fallback dev su project bundle_bin
- core/dedup.py: scan_folder, compute_fingerprint, move_to_trash
- tests/test_dedup.py: 9 unit test (mock fpcalc + send2trash)
- requirements.txt: send2trash>=1.8.0
- build_macos.py: download fpcalc universal binary da GitHub releases
- build_windows.py: download fpcalc.exe da GitHub releases
2026-08-02 13:22:24 +02:00
luciano d8723f9408 upgrader: _scan_archive + _find_candidates + resolve_callback per match locale
Nuovo flusso: se archive_dir è passato, upgrade_folder cerca versioni HQ
del brano nell'archivio locale prima di scaricare da YouTube. Match via
Jaccard sui token del filename (soglia default 0.5). Se 1 candidato:
copia/converte diretto in mp3 320k. Se >=2 e resolve_callback presente:
chiama la callback bloccante (per la UI) con lista candidati + bitrate
+ similarity, poi rispetta la scelta (use_local / use_youtube / skip).
Fallback su YouTube se nessun match o archive_dir=None.

Test unitari sui 3 helper (_normalize_stem, _scan_archive, _find_candidates).
2026-08-02 11:54:58 +02:00
luciano 88e59ad37f traxsource: fetch_top100 con session curl_cffi + retry + cache 15min 2026-08-01 12:01:00 +02:00
luciano 784b56682f traxsource: _discover_top100_url + _parse_tracks con BeautifulSoup 2026-08-01 11:59:55 +02:00
luciano e1e629f8a2 traxsource: helper _split_title_mix + _format_artists + _large_cover + eccezioni 2026-08-01 11:57:42 +02:00
luciano a95912f78c traxsource: GENRES + TraxsourceTrack + list_genres 2026-08-01 11:55:30 +02:00
luciano 7feda99720 traxsource: fixture HTML + bs4 dep + script refresh generi 2026-08-01 11:54:55 +02:00
luciano 5fc9f8ac2b converter: modulo ffmpeg WAV -> MP3 con VBR/CBR + stop 2026-07-21 17:44:36 +02:00
luciano 0f7cd24703 tagger: modulo mutagen per ID3 tag + cover art da URL 2026-07-15 12:54:26 +02:00
luciano 0949df031a youtube: search_youtube via yt-dlp ytsearchN + parse JSON 2026-07-14 17:18:54 +02:00
luciano 2771fa8075 spotify: search_artist_discography con dedupe top-tracks+album 2026-07-14 17:16:19 +02:00
luciano 252d76b907 spotify: search_tracks() free-form con limit configurabile 2026-07-14 17:14:02 +02:00
luciano cc5716e87d spotify: aggiunto search_track() per feature Beatport 2026-07-14 16:26:25 +02:00
luciano 3f31e240be beatport: fetch_top100 con curl_cffi + retry + cache 15min 2026-07-14 16:24:43 +02:00
luciano 13c8e60712 beatport: parse JSON → list[BeatportTrack] 2026-07-14 16:22:21 +02:00
luciano f1e57905c1 beatport: estrazione __NEXT_DATA__ + eccezioni tipizzate 2026-07-14 16:20:21 +02:00
luciano 4221a5992b beatport: BeatportTrack dataclass con display + spotify_query 2026-07-14 16:18:38 +02:00
luciano 70cdfe8983 beatport: GENRES + list_genres() con test 2026-07-14 16:17:09 +02:00
luciano e1aaa83fe9 beatport: fixture HTML + curl_cffi + script refresh generi 2026-07-14 16:14:55 +02:00
luciano c037da4901 test: setup pytest infra per feature Beatport 2026-07-14 16:03:33 +02:00