La causa root del "Failed to resolve Python.Runtime.Loader.Initialize"
era il Mark of the Web (Zone.Identifier ADS) che Windows aggiunge ai
file estratti da uno zip scaricato dal browser. .NET Framework
rifiuta SILENZIOSAMENTE di risolvere i symbol export degli assembly
managed con MOTW.
Fix multilivello:
- main.py: al primo avvio cancella :Zone.Identifier da ogni
.dll/.exe/.pyd nel bundle (via ctypes kernel32.DeleteFileW)
- CI: Unblock-File su dist/ pre-zip
- requirements: pythonnet==3.0.5 + clr-loader==0.2.7.post0 pinnati
Vedi: https://github.com/r0x0r/pywebview/issues/1215https://github.com/pyinstaller/pyinstaller/issues/7412
Preload esplicito di pythonnet.load("netfx") in main.py PRIMA di
importare webview: il lazy-import del 'clr' fallisce sotto PyInstaller
perche' Python.Runtime.dll viene bundlata ma il loader netfx non sa
risolverla.
Aggiunti:
- main.py: bootstrap pythonnet con PYTHONNET_RUNTIME=netfx + load()
- build_windows.py: --clean rebuild, --collect-data pythonnet/clr_loader
(per Python.Runtime.runtimeconfig.json e altri non-Python files),
hidden-import clr_loader.netfx
PyInstaller non bundlava i file di supporto di pythonnet/clr_loader
(Python.Runtime.dll, nethost.dll, runtimeconfig.json), per cui al
caricamento di webview.platforms.winforms.py l'import 'clr' moriva.
Aggiunti --collect-all pythonnet/clr_loader + copy-metadata.
Pinnata pythonnet a 3.0.3 (l'ultima compatibile con PyInstaller
senza patch aggiuntive).
Macos ora viene distribuito come .dmg invece di .zip:
- contiene MusicTools.app + alias Applicazioni + "Fix Gatekeeper.command"
- doppio click sullo script rimuove com.apple.quarantine senza
Terminal manuale -> niente piu' "App non verificata"
Backend: /api/download serve .dmg con Content-Type
application/x-apple-diskimage. Landing /download aggiornata
con il nuovo flusso (3 step).
Icona: rigenerati icon.icns (macOS multi-size) e icon.ico (Windows
multi-size 16-256) dalla sorgente assets/icon_1024.png.
Landing: nuovo box "Per sfruttare al massimo MusicTools serve un
account Spotify" + FAQ esplicita su cosa funziona senza setup
e dove servono le chiavi.
Spotify: per i singoli brani ora basta incollare il link, l'app
estrae name+artist dai meta og:title/og:description pubblici e
risolve via YouTube. Album/playlist continuano a richiedere
le credenziali (gratis, 2 min).
Landing /download: bottoni Mac/Windows ora navigano nella stessa
tab via JS handler + feedback "avvio in corso" invece del fragile
target="_blank".
Il selettore Impostazioni > Tema salvava il valore ma nessuno applicava
data-theme al <html>, e mancavano gli override CSS per il tema chiaro.
Aggiunte custom properties light + applyTheme() su init/change/save.
Pricing model: subscription mensili (Basic 2.99 / Pro 5.99 / Premium 9.99)
piu' Annual 49.90 one-time. Feature gating per piano (Basic = solo audio,
Pro+ = tutto). Quota giornaliera (10/30/150) con reset mezzanotte Europe/Rome.
Server: tabella daily_usage, endpoint /api/usage/consume e /status,
webhook LS gestisce subscription_* events + mappatura variant_id -> plan
via .env (LS_VARIANT_BASIC|PRO|PREMIUM|ANNUAL).
Client: gating tab in sidebar in base ai features del piano, contatore
"X/Y oggi" + bottone Upgrade, modal blocco con CTA verso landing.
Sul sistema dell'utente il comando ffmpeg che usa l'app funziona
correttamente (testato come subprocess identico), ma il vecchio
reporting era confuso quando l'errore era transitorio (BlackHole
non pronto, clock drift, ecc.) o quando la parola 'abort' non
compariva nello stderr.
Cambiamenti:
core/recorder.py:
- _extract_friendly_error riconosce ora piu casi: input/output error
(BlackHole non pronto -> suggerisce 'sudo killall coreaudiod'),
device not configured, permission denied, no such device
- rc=-6 su macOS ritorna SEMPRE il messaggio sul permesso Microfono
(prima richiedeva 'abort' nello stderr, non sempre presente)
- Fallback finale mostra anche le ultime 3 righe di stderr
- Nuova funzione get_last_stderr() per debugging
api/bridge.py:
- Nuovo metodo get_recorder_log() che ritorna stderr per la UI
webui:
- Nuova card di errore persistente con bottoni
'Mostra log tecnici' / 'Copia log'
- Si resetta automaticamente all'avvio di una nuova registrazione
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
L'input URL ora accetta anche query libere: se non sembra un link
(http/https/spotify:/dominio noto), il testo viene parsato come
{artist, name} e scaricato via download_playlist con una traccia.
Riusa lo stesso flusso ytsearch1: gia in uso per le tracklist da file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Aggiunto helper subprocess_flags() in core/paths.py che ritorna
CREATE_NO_WINDOW su Windows. Applicato a tutte le subprocess.run/Popen
in downloader, upgrader e recorder per evitare il flash di console nera
ad ogni traccia scaricata.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- main.py: titolo finestra pywebview -> 'MusicTools'
- core/config.py: APP_NAME='MusicTools', cartella config bundled
ora ~/Library/Application Support/MusicTools/ e %APPDATA%/MusicTools/.
Migrazione one-shot: se non esiste la nuova ma esiste la legacy
'MusicDownload', il config.json viene copiato cosi' le impostazioni
non vanno perse dopo l'aggiornamento
- core/recorder.py: messaggio errore permesso microfono
- api/bridge.py: User-Agent update check + guida Spotify menzionano
MusicTools
- build_macos.py / build_windows.py / .github/workflows/build.yml:
prodotti rinominati (MusicTools.app, MusicTools.exe,
MusicTools-macOS.zip, MusicTools-Windows.zip, Release 'Build MusicTools')
- webui: <title>, brand-name sidebar, intestazioni commenti
- README: tutti i riferimenti aggiornati + nota che il repo Git
conserva il nome storico 'musicdownload' (URL stabili per
release gia' pubblicate)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Aggiunto toggle Video/Solo audio nella tab Video. In modalita audio
yt-dlp scarica solo bestaudio ed estrae in MP3 al bitrate impostato
in Impostazioni (default 320 kbps), con embed thumbnail come la tab
Scarica.
- core/downloader.py: download_video() accetta audio_only e
audio_bitrate; quando True usa format 'bestaudio/best' e
--extract-audio --audio-format mp3
- api/bridge.py: start_video_download passa audio_only nel worker;
log mostra 'Solo audio MP3 320K' vs 'Video 1080p'
- webui: segmented control 'Video (MP4)' / 'Solo audio (MP3)' rosa,
selettore qualita nascosto in modalita audio, hint aggiornato
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Il vecchio worker non drenava lo stderr di ffmpeg: dopo ~64KB il
pipe si bloccava e quando ffmpeg falliva subito (es. permesso
microfono negato su macOS) si vedeva solo 'ffmpeg exit -6' senza
contesto.
- Thread dedicato _drain_stderr() che legge stderr in continuo e
mantiene le ultime 50 righe in un buffer protetto
- _extract_friendly_error() riconosce i casi tipici e produce
messaggi italiani:
* exit -6 su macOS -> 'permesso Microfono mancante'
* Input/Output error -> 'dispositivo non disponibile'
* 'no such device' -> 'premere Aggiorna'
- Pre-flight check: aspetta che ffmpeg stampi 'Press [q]' (segnale
di device aperto) prima di emettere 'started'. Se ffmpeg muore
prima, l'errore viene segnalato senza falso 'recording in corso'
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Aggiunta sezione per registrare in MP3 320 kbps da qualsiasi
dispositivo di input audio del sistema (mixer, scheda audio
esterna, microfono interno, BlackHole / driver virtuali per audio
di sistema).
- core/recorder.py: list_input_devices() parsa l'output di
ffmpeg -list_devices su AVFoundation (macOS) e DirectShow (Win),
marca come 'virtual' i driver loopback noti (BlackHole, Soundflower,
Loopback, VB-Cable, Stereo Mix, OBS). start_recording() lancia
ffmpeg in thread e emette eventi tick/started/stopped/error.
- api/bridge.py: list_audio_inputs, start_audio_recording,
stop_audio_recording, is_audio_recording
- webui: hero rosso, dropdown dispositivi con badge 'loopback',
selettore bitrate, timer HH:MM:SS animato, bottone REC con
pulse animation, card 'Ultima registrazione' con durata+path
- Hint visibile: per registrare audio di sistema serve BlackHole
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
kMDItemWhereFroms non e un tag ID3/MP4/Vorbis: e' un extended
attribute filesystem di macOS che Safari/Mail/AirDrop e altri tool
impostano per ricordare l'origine di un file scaricato.
- core/metadata.py: _read_where_from / _write_where_from via subprocess
xattr + plistlib (lettura/scrittura del plist binario embedded
nell'attributo)
- read_metadata espone 'where_from' (lista URL) e 'is_macos'
- write_metadata applica l'xattr se data['where_from'] e fornito;
lista vuota -> rimuove l'attributo
- webui: textarea 'Origine' visibile solo su macOS (is_macos=true),
una URL per riga
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mutagen.wave legge solo i tag ID3 embedded nel WAV, ma molti file
WAV usano il chunk RIFF LIST/INFO originale (es. ffmpeg, vari
encoder semplici).
Aggiunto _read_wav_info_chunk() che parsa manualmente il blocco
RIFF e mappa i campi standard (INAM, IART, IPRD, ICRD, IGNR,
ICMT, ITRK, IBPM, ...) sui campi della UI.
In _read_wav() ora si legge prima il chunk INFO come fallback, poi
l'ID3 sovrascrive i valori se presenti (ID3 ha priorita per i WAV
moderni).
In scrittura si continua a usare ID3 (standard piu ricco e supportato
dai player moderni); i campi INFO esistenti restano nel file ma
diventano ridondanti.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
WAV puo' contenere un blocco ID3v2 embedded oltre al PCM raw.
Mutagen.wave.WAVE espone gli stessi frame TIT2/TPE1/APIC etc.
del MP3.
- core/metadata.py: SUPPORTED_EXTS include '.wav', read/write via
WAVE.tags (con add_tags() se mancante)
- bridge.py: file picker include *.wav nel filter audio
Round-trip verificato: titolo/artista/album/anno/track/bpm/key/
commento + copertina preservati.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Nuova sezione 'Metadati' nella sidebar con editor completo dei tag:
titolo, artista, album, album artist, anno, traccia, genere, BPM,
key, commento, copertina.
- core/metadata.py: read/write metadati via mutagen, supporto ID3
(MP3), MP4 atoms (M4A/AAC) e Vorbis comments (FLAC)
- api/bridge.py: pick_audio_file, pick_image_file, read_metadata,
save_metadata. Cover passata come base64 al frontend per preview
- webui: hero ambra, form a griglia 2 colonne, anteprima cover 180x180,
bottoni cambia/rimuovi copertina, ricarica file
- requirements.txt: aggiunto mutagen>=1.47.0
- build_macos.py / build_windows.py: --collect-all mutagen
Modifiche correlate (in attesa di decisione utente sulla
distribuzione degli aggiornamenti):
- VERSION bumpato a v1.2.2 in core/config.py
- check_update ora interroga l'API GitHub Releases invece di
djluza.com/version.json (funzionera quando il repo sara pubblico)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- core/downloader.py: download_video() con format selector qualita
(best / 1080p / 720p / 480p), merge MP4, tracking .downloaded_videos
- api/bridge.py: start_video_download / stop_video_download / _video_worker.
Guard _any_job_running() per evitare concorrenza tra audio e video
(condividono lo stesso _stop_event)
- webui: nuova nav item, view con hero rosa (gradient pink->black),
selettore qualita, hint cookies per Instagram/Facebook privati
- CSS: palette pink, .hero-pink, .btn-pink, .progress-fill.pink,
componente .hint riusabile
- JS: handlers tab Video, canale bridge video:progress/video:done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>