feat(analysis): re-analyze waveform when clearing loudness cache

Add analysis_delete_waveform_for_track, invoke it from loudness reseed,
clear waveformBins in the UI, and extend queue strings for tooltips/toast.
This commit is contained in:
Maxim Isaev
2026-05-10 01:34:40 +03:00
parent 7a0dd93f3e
commit 308eb36f05
13 changed files with 57 additions and 3 deletions
+2 -2
View File
@@ -814,8 +814,8 @@ function QueuePanelHostOrSolo() {
setLufsTgtOpen(false);
void reanalyzeLoudnessForTrack(currentTrack.id);
}}
data-tooltip="Clear cached loudness and re-analyze this track"
aria-label="Clear cached loudness and re-analyze this track"
data-tooltip={t('queue.clearCachedLoudnessWaveform')}
aria-label={t('queue.clearCachedLoudnessWaveform')}
>
{liveGainLabel}
</button>
+2
View File
@@ -1184,6 +1184,8 @@ export const deTranslation = {
sourceOffline: 'Wiedergabe aus der Offline-Bibliothek',
sourceHot: 'Wiedergabe aus dem Cache',
sourceStream: 'Wiedergabe aus dem Netzwerkstream',
clearCachedLoudnessWaveform: 'Clear cached loudness and waveform, then re-analyze this track',
recalculatingLoudnessWaveform: 'Recalculating loudness and waveform for this track…',
},
miniPlayer: {
showQueue: 'Warteschlange einblenden',
+2
View File
@@ -1189,6 +1189,8 @@ export const enTranslation = {
sourceOffline: 'Playing from offline library',
sourceHot: 'Playing from cache',
sourceStream: 'Playing from network stream',
clearCachedLoudnessWaveform: 'Clear cached loudness and waveform, then re-analyze this track',
recalculatingLoudnessWaveform: 'Recalculating loudness and waveform for this track…',
},
miniPlayer: {
showQueue: 'Show queue',
+2
View File
@@ -1166,6 +1166,8 @@ export const esTranslation = {
sourceOffline: 'Reproducción desde la biblioteca sin conexión',
sourceHot: 'Reproducción desde la caché',
sourceStream: 'Reproducción desde la transmisión en red',
clearCachedLoudnessWaveform: 'Clear cached loudness and waveform, then re-analyze this track',
recalculatingLoudnessWaveform: 'Recalculating loudness and waveform for this track…',
},
miniPlayer: {
showQueue: 'Mostrar cola',
+2
View File
@@ -1162,6 +1162,8 @@ export const frTranslation = {
sourceOffline: 'Lecture depuis la bibliothèque hors ligne',
sourceHot: 'Lecture depuis le cache',
sourceStream: 'Lecture depuis le flux réseau',
clearCachedLoudnessWaveform: 'Clear cached loudness and waveform, then re-analyze this track',
recalculatingLoudnessWaveform: 'Recalculating loudness and waveform for this track…',
},
miniPlayer: {
showQueue: 'Afficher la file',
+2
View File
@@ -1161,6 +1161,8 @@ export const nbTranslation = {
sourceOffline: 'Spiller fra offlinebibliotek',
sourceHot: 'Spiller fra cache',
sourceStream: 'Spiller fra nettverksstrøm',
clearCachedLoudnessWaveform: 'Clear cached loudness and waveform, then re-analyze this track',
recalculatingLoudnessWaveform: 'Recalculating loudness and waveform for this track…',
},
miniPlayer: {
showQueue: 'Vis kø',
+2
View File
@@ -1161,6 +1161,8 @@ export const nlTranslation = {
sourceOffline: 'Afspelen vanuit offlinebibliotheek',
sourceHot: 'Afspelen vanuit cache',
sourceStream: 'Afspelen vanuit netwerkstream',
clearCachedLoudnessWaveform: 'Clear cached loudness and waveform, then re-analyze this track',
recalculatingLoudnessWaveform: 'Recalculating loudness and waveform for this track…',
},
miniPlayer: {
showQueue: 'Wachtrij tonen',
+2
View File
@@ -1214,6 +1214,8 @@ export const ruTranslation = {
sourceOffline: 'Играет из офлайн-библиотеки',
sourceHot: 'Играет из кэша',
sourceStream: 'Играет из сетевого потока',
clearCachedLoudnessWaveform: 'Сбросить кэш громкости (LUFS) и формы волны и заново проанализировать трек',
recalculatingLoudnessWaveform: 'Пересчёт громкости и формы волны для этого трека…',
},
miniPlayer: {
showQueue: 'Показать очередь',
+2
View File
@@ -1156,6 +1156,8 @@ export const zhTranslation = {
sourceOffline: '正在从离线库播放',
sourceHot: '正在从缓存播放',
sourceStream: '正在从网络流播放',
clearCachedLoudnessWaveform: 'Clear cached loudness and waveform, then re-analyze this track',
recalculatingLoudnessWaveform: 'Recalculating loudness and waveform for this track…',
},
miniPlayer: {
showQueue: '显示队列',
+11 -1
View File
@@ -3,6 +3,7 @@ import { persist, createJSONStorage } from 'zustand/middleware';
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
import { showToast } from '../utils/toast';
import i18n from '../i18n';
import { buildCoverArtUrl, buildStreamUrl, getPlayQueue, savePlayQueue, reportNowPlaying, scrobbleSong, SubsonicSong, getSong, getRandomSongs, getSimilarSongs2, getTopSongs, InternetRadioStation, setRating, getAlbumInfo2 } from '../api/subsonic';
import { resolvePlaybackUrl, streamUrlTrackId, getPlaybackSourceKind, type PlaybackSourceKind } from '../utils/resolvePlaybackUrl';
import { redactSubsonicUrlForLog } from '../utils/redactSubsonicUrl';
@@ -1051,6 +1052,10 @@ async function reseedLoudnessForTrackId(trackId: string) {
if (!trackId) return;
const auth = useAuthStore.getState();
if (auth.normalizationEngine !== 'loudness') return;
bumpWaveformRefreshGen(trackId);
if (usePlayerStore.getState().currentTrack?.id === trackId) {
usePlayerStore.setState({ waveformBins: null });
}
clearLoudnessCacheStateForTrackId(trackId);
resetLoudnessBackfillStateForTrackId(trackId);
if (auth.normalizationEngine === 'loudness') {
@@ -1060,6 +1065,11 @@ async function reseedLoudnessForTrackId(trackId: string) {
normalizationEngineLive: 'loudness',
});
}
try {
await invoke('analysis_delete_waveform_for_track', { trackId });
} catch (e) {
console.error('[psysonic] analysis_delete_waveform_for_track failed:', e);
}
try {
await invoke('analysis_delete_loudness_for_track', { trackId });
} catch (e) {
@@ -3573,7 +3583,7 @@ export const usePlayerStore = create<PlayerState>()(
reanalyzeLoudnessForTrack: async (trackId: string) => {
try {
showToast('Recalculating loudness for this track…', 2000, 'info');
showToast(i18n.t('queue.recalculatingLoudnessWaveform'), 2000, 'info');
} catch {
// no-op
}