mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
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:
@@ -109,6 +109,25 @@ impl AnalysisCache {
|
|||||||
Ok(total)
|
Ok(total)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove all `waveform_cache` rows for this logical track (bare id and `stream:` variant).
|
||||||
|
pub fn delete_waveform_for_track_id(&self, track_id: &str) -> Result<u64, String> {
|
||||||
|
if track_id.trim().is_empty() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
let conn = self
|
||||||
|
.conn
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| "analysis_cache lock poisoned".to_string())?;
|
||||||
|
let mut total: u64 = 0;
|
||||||
|
for tid in track_id_cache_variants(track_id) {
|
||||||
|
let n = conn
|
||||||
|
.execute("DELETE FROM waveform_cache WHERE track_id = ?1", params![tid])
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
total = total.saturating_add(n as u64);
|
||||||
|
}
|
||||||
|
Ok(total)
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove all cached waveform rows across all tracks/variants.
|
/// Remove all cached waveform rows across all tracks/variants.
|
||||||
pub fn delete_all_waveforms(&self) -> Result<u64, String> {
|
pub fn delete_all_waveforms(&self) -> Result<u64, String> {
|
||||||
let conn = self
|
let conn = self
|
||||||
|
|||||||
@@ -141,6 +141,14 @@ pub fn analysis_delete_loudness_for_track(
|
|||||||
cache.delete_loudness_for_track_id(&track_id)
|
cache.delete_loudness_for_track_id(&track_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn analysis_delete_waveform_for_track(
|
||||||
|
track_id: String,
|
||||||
|
cache: tauri::State<'_, analysis_cache::AnalysisCache>,
|
||||||
|
) -> Result<u64, String> {
|
||||||
|
cache.delete_waveform_for_track_id(&track_id)
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn analysis_delete_all_waveforms(
|
pub fn analysis_delete_all_waveforms(
|
||||||
cache: tauri::State<'_, analysis_cache::AnalysisCache>,
|
cache: tauri::State<'_, analysis_cache::AnalysisCache>,
|
||||||
|
|||||||
@@ -412,6 +412,7 @@ pub fn run() {
|
|||||||
psysonic_analysis::commands::analysis_get_waveform_for_track,
|
psysonic_analysis::commands::analysis_get_waveform_for_track,
|
||||||
psysonic_analysis::commands::analysis_get_loudness_for_track,
|
psysonic_analysis::commands::analysis_get_loudness_for_track,
|
||||||
psysonic_analysis::commands::analysis_delete_loudness_for_track,
|
psysonic_analysis::commands::analysis_delete_loudness_for_track,
|
||||||
|
psysonic_analysis::commands::analysis_delete_waveform_for_track,
|
||||||
psysonic_analysis::commands::analysis_delete_all_waveforms,
|
psysonic_analysis::commands::analysis_delete_all_waveforms,
|
||||||
psysonic_analysis::commands::analysis_enqueue_seed_from_url,
|
psysonic_analysis::commands::analysis_enqueue_seed_from_url,
|
||||||
psysonic_analysis::commands::analysis_prune_pending_to_track_ids,
|
psysonic_analysis::commands::analysis_prune_pending_to_track_ids,
|
||||||
|
|||||||
@@ -814,8 +814,8 @@ function QueuePanelHostOrSolo() {
|
|||||||
setLufsTgtOpen(false);
|
setLufsTgtOpen(false);
|
||||||
void reanalyzeLoudnessForTrack(currentTrack.id);
|
void reanalyzeLoudnessForTrack(currentTrack.id);
|
||||||
}}
|
}}
|
||||||
data-tooltip="Clear cached loudness and re-analyze this track"
|
data-tooltip={t('queue.clearCachedLoudnessWaveform')}
|
||||||
aria-label="Clear cached loudness and re-analyze this track"
|
aria-label={t('queue.clearCachedLoudnessWaveform')}
|
||||||
>
|
>
|
||||||
{liveGainLabel}
|
{liveGainLabel}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1184,6 +1184,8 @@ export const deTranslation = {
|
|||||||
sourceOffline: 'Wiedergabe aus der Offline-Bibliothek',
|
sourceOffline: 'Wiedergabe aus der Offline-Bibliothek',
|
||||||
sourceHot: 'Wiedergabe aus dem Cache',
|
sourceHot: 'Wiedergabe aus dem Cache',
|
||||||
sourceStream: 'Wiedergabe aus dem Netzwerkstream',
|
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: {
|
miniPlayer: {
|
||||||
showQueue: 'Warteschlange einblenden',
|
showQueue: 'Warteschlange einblenden',
|
||||||
|
|||||||
@@ -1189,6 +1189,8 @@ export const enTranslation = {
|
|||||||
sourceOffline: 'Playing from offline library',
|
sourceOffline: 'Playing from offline library',
|
||||||
sourceHot: 'Playing from cache',
|
sourceHot: 'Playing from cache',
|
||||||
sourceStream: 'Playing from network stream',
|
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: {
|
miniPlayer: {
|
||||||
showQueue: 'Show queue',
|
showQueue: 'Show queue',
|
||||||
|
|||||||
@@ -1166,6 +1166,8 @@ export const esTranslation = {
|
|||||||
sourceOffline: 'Reproducción desde la biblioteca sin conexión',
|
sourceOffline: 'Reproducción desde la biblioteca sin conexión',
|
||||||
sourceHot: 'Reproducción desde la caché',
|
sourceHot: 'Reproducción desde la caché',
|
||||||
sourceStream: 'Reproducción desde la transmisión en red',
|
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: {
|
miniPlayer: {
|
||||||
showQueue: 'Mostrar cola',
|
showQueue: 'Mostrar cola',
|
||||||
|
|||||||
@@ -1162,6 +1162,8 @@ export const frTranslation = {
|
|||||||
sourceOffline: 'Lecture depuis la bibliothèque hors ligne',
|
sourceOffline: 'Lecture depuis la bibliothèque hors ligne',
|
||||||
sourceHot: 'Lecture depuis le cache',
|
sourceHot: 'Lecture depuis le cache',
|
||||||
sourceStream: 'Lecture depuis le flux réseau',
|
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: {
|
miniPlayer: {
|
||||||
showQueue: 'Afficher la file',
|
showQueue: 'Afficher la file',
|
||||||
|
|||||||
@@ -1161,6 +1161,8 @@ export const nbTranslation = {
|
|||||||
sourceOffline: 'Spiller fra offlinebibliotek',
|
sourceOffline: 'Spiller fra offlinebibliotek',
|
||||||
sourceHot: 'Spiller fra cache',
|
sourceHot: 'Spiller fra cache',
|
||||||
sourceStream: 'Spiller fra nettverksstrøm',
|
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: {
|
miniPlayer: {
|
||||||
showQueue: 'Vis kø',
|
showQueue: 'Vis kø',
|
||||||
|
|||||||
@@ -1161,6 +1161,8 @@ export const nlTranslation = {
|
|||||||
sourceOffline: 'Afspelen vanuit offlinebibliotheek',
|
sourceOffline: 'Afspelen vanuit offlinebibliotheek',
|
||||||
sourceHot: 'Afspelen vanuit cache',
|
sourceHot: 'Afspelen vanuit cache',
|
||||||
sourceStream: 'Afspelen vanuit netwerkstream',
|
sourceStream: 'Afspelen vanuit netwerkstream',
|
||||||
|
clearCachedLoudnessWaveform: 'Clear cached loudness and waveform, then re-analyze this track',
|
||||||
|
recalculatingLoudnessWaveform: 'Recalculating loudness and waveform for this track…',
|
||||||
},
|
},
|
||||||
miniPlayer: {
|
miniPlayer: {
|
||||||
showQueue: 'Wachtrij tonen',
|
showQueue: 'Wachtrij tonen',
|
||||||
|
|||||||
@@ -1214,6 +1214,8 @@ export const ruTranslation = {
|
|||||||
sourceOffline: 'Играет из офлайн-библиотеки',
|
sourceOffline: 'Играет из офлайн-библиотеки',
|
||||||
sourceHot: 'Играет из кэша',
|
sourceHot: 'Играет из кэша',
|
||||||
sourceStream: 'Играет из сетевого потока',
|
sourceStream: 'Играет из сетевого потока',
|
||||||
|
clearCachedLoudnessWaveform: 'Сбросить кэш громкости (LUFS) и формы волны и заново проанализировать трек',
|
||||||
|
recalculatingLoudnessWaveform: 'Пересчёт громкости и формы волны для этого трека…',
|
||||||
},
|
},
|
||||||
miniPlayer: {
|
miniPlayer: {
|
||||||
showQueue: 'Показать очередь',
|
showQueue: 'Показать очередь',
|
||||||
|
|||||||
@@ -1156,6 +1156,8 @@ export const zhTranslation = {
|
|||||||
sourceOffline: '正在从离线库播放',
|
sourceOffline: '正在从离线库播放',
|
||||||
sourceHot: '正在从缓存播放',
|
sourceHot: '正在从缓存播放',
|
||||||
sourceStream: '正在从网络流播放',
|
sourceStream: '正在从网络流播放',
|
||||||
|
clearCachedLoudnessWaveform: 'Clear cached loudness and waveform, then re-analyze this track',
|
||||||
|
recalculatingLoudnessWaveform: 'Recalculating loudness and waveform for this track…',
|
||||||
},
|
},
|
||||||
miniPlayer: {
|
miniPlayer: {
|
||||||
showQueue: '显示队列',
|
showQueue: '显示队列',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { persist, createJSONStorage } from 'zustand/middleware';
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import { showToast } from '../utils/toast';
|
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 { 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 { resolvePlaybackUrl, streamUrlTrackId, getPlaybackSourceKind, type PlaybackSourceKind } from '../utils/resolvePlaybackUrl';
|
||||||
import { redactSubsonicUrlForLog } from '../utils/redactSubsonicUrl';
|
import { redactSubsonicUrlForLog } from '../utils/redactSubsonicUrl';
|
||||||
@@ -1051,6 +1052,10 @@ async function reseedLoudnessForTrackId(trackId: string) {
|
|||||||
if (!trackId) return;
|
if (!trackId) return;
|
||||||
const auth = useAuthStore.getState();
|
const auth = useAuthStore.getState();
|
||||||
if (auth.normalizationEngine !== 'loudness') return;
|
if (auth.normalizationEngine !== 'loudness') return;
|
||||||
|
bumpWaveformRefreshGen(trackId);
|
||||||
|
if (usePlayerStore.getState().currentTrack?.id === trackId) {
|
||||||
|
usePlayerStore.setState({ waveformBins: null });
|
||||||
|
}
|
||||||
clearLoudnessCacheStateForTrackId(trackId);
|
clearLoudnessCacheStateForTrackId(trackId);
|
||||||
resetLoudnessBackfillStateForTrackId(trackId);
|
resetLoudnessBackfillStateForTrackId(trackId);
|
||||||
if (auth.normalizationEngine === 'loudness') {
|
if (auth.normalizationEngine === 'loudness') {
|
||||||
@@ -1060,6 +1065,11 @@ async function reseedLoudnessForTrackId(trackId: string) {
|
|||||||
normalizationEngineLive: 'loudness',
|
normalizationEngineLive: 'loudness',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
await invoke('analysis_delete_waveform_for_track', { trackId });
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[psysonic] analysis_delete_waveform_for_track failed:', e);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await invoke('analysis_delete_loudness_for_track', { trackId });
|
await invoke('analysis_delete_loudness_for_track', { trackId });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -3573,7 +3583,7 @@ export const usePlayerStore = create<PlayerState>()(
|
|||||||
|
|
||||||
reanalyzeLoudnessForTrack: async (trackId: string) => {
|
reanalyzeLoudnessForTrack: async (trackId: string) => {
|
||||||
try {
|
try {
|
||||||
showToast('Recalculating loudness for this track…', 2000, 'info');
|
showToast(i18n.t('queue.recalculatingLoudnessWaveform'), 2000, 'info');
|
||||||
} catch {
|
} catch {
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user