feat(queue): show ReplayGain metadata in current-track tech strip

Display track gain, album gain, and peak from file tags next to format,
bitrate, and sample rate so missing ReplayGain tags are obvious.

Refs: https://github.com/Psychotoxical/psysonic/issues/195
This commit is contained in:
Maxim Isaev
2026-04-16 23:15:13 +03:00
parent b6812de26b
commit 572eb81927
9 changed files with 57 additions and 16 deletions
+33 -16
View File
@@ -20,6 +20,21 @@ function formatTime(seconds: number): string {
return `${m}:${s.toString().padStart(2, '0')}`; return `${m}:${s.toString().padStart(2, '0')}`;
} }
function formatQueueReplayGainParts(track: Track, t: TFunction): string[] {
const parts: string[] = [];
const fmtDb = (db: number) => `${db >= 0 ? '+' : ''}${db.toFixed(1)}`;
if (track.replayGainTrackDb != null) {
parts.push(t('queue.rgTrack', { db: fmtDb(track.replayGainTrackDb) }));
}
if (track.replayGainAlbumDb != null) {
parts.push(t('queue.rgAlbum', { db: fmtDb(track.replayGainAlbumDb) }));
}
if (track.replayGainPeak != null) {
parts.push(t('queue.rgPeak', { pk: track.replayGainPeak.toFixed(3) }));
}
return parts;
}
function renderStars(rating?: number) { function renderStars(rating?: number) {
if (!rating) return null; if (!rating) return null;
const stars = []; const stars = [];
@@ -420,22 +435,24 @@ export default function QueuePanel() {
{currentTrack && ( {currentTrack && (
<div className="queue-current-track"> <div className="queue-current-track">
{(currentTrack.suffix || currentTrack.bitRate || currentTrack.samplingRate || currentTrack.bitDepth) && ( {(() => {
<div className="queue-current-tech"> const baseParts = [
{[ currentTrack.suffix?.toUpperCase(),
currentTrack.suffix?.toUpperCase(), currentTrack.bitRate ? `${currentTrack.bitRate} kbps` : undefined,
currentTrack.bitRate ? `${currentTrack.bitRate} kbps` : undefined, (() => {
(() => { const bd = currentTrack.bitDepth;
const bd = currentTrack.bitDepth; const sr = currentTrack.samplingRate ? `${currentTrack.samplingRate / 1000} kHz` : '';
const sr = currentTrack.samplingRate ? `${currentTrack.samplingRate / 1000} kHz` : ''; if (bd && sr) return `${bd}/${sr}`;
if (bd && sr) return `${bd}/${sr}`; if (bd) return `${bd}-bit`;
if (bd) return `${bd}-bit`; if (sr) return sr;
if (sr) return sr; return undefined;
return undefined; })(),
})(), ].filter(Boolean) as string[];
].filter(Boolean).join(' · ')} const rgParts = formatQueueReplayGainParts(currentTrack, t);
</div> const techLine = [...baseParts, ...rgParts].join(' · ');
)} if (!techLine) return null;
return <div className="queue-current-tech">{techLine}</div>;
})()}
<div className="queue-current-track-body"> <div className="queue-current-track-body">
<div className="queue-current-cover"> <div className="queue-current-cover">
{currentTrack.coverArt ? ( {currentTrack.coverArt ? (
+3
View File
@@ -850,6 +850,9 @@ export const deTranslation = {
trackPlural: 'Titel', trackPlural: 'Titel',
showRemaining: 'Restzeit anzeigen', showRemaining: 'Restzeit anzeigen',
showTotal: 'Gesamtzeit anzeigen', showTotal: 'Gesamtzeit anzeigen',
rgTrack: 'T {{db}} dB',
rgAlbum: 'A {{db}} dB',
rgPeak: 'Peak {{pk}}',
}, },
statistics: { statistics: {
title: 'Statistiken', title: 'Statistiken',
+3
View File
@@ -852,6 +852,9 @@ export const enTranslation = {
trackPlural: 'tracks', trackPlural: 'tracks',
showRemaining: 'Show remaining time', showRemaining: 'Show remaining time',
showTotal: 'Show total time', showTotal: 'Show total time',
rgTrack: 'T {{db}} dB',
rgAlbum: 'A {{db}} dB',
rgPeak: 'Peak {{pk}}',
}, },
statistics: { statistics: {
title: 'Statistics', title: 'Statistics',
+3
View File
@@ -853,6 +853,9 @@ export const esTranslation = {
trackPlural: 'pistas', trackPlural: 'pistas',
showRemaining: 'Mostrar tiempo restante', showRemaining: 'Mostrar tiempo restante',
showTotal: 'Mostrar tiempo total', showTotal: 'Mostrar tiempo total',
rgTrack: 'T {{db}} dB',
rgAlbum: 'A {{db}} dB',
rgPeak: 'Pico {{pk}}',
}, },
statistics: { statistics: {
title: 'Estadísticas', title: 'Estadísticas',
+3
View File
@@ -848,6 +848,9 @@ export const frTranslation = {
trackPlural: 'pistes', trackPlural: 'pistes',
showRemaining: 'Afficher le temps restant', showRemaining: 'Afficher le temps restant',
showTotal: 'Afficher la durée totale', showTotal: 'Afficher la durée totale',
rgTrack: 'T {{db}} dB',
rgAlbum: 'A {{db}} dB',
rgPeak: 'Pic {{pk}}',
}, },
statistics: { statistics: {
title: 'Statistiques', title: 'Statistiques',
+3
View File
@@ -847,6 +847,9 @@ export const nbTranslation = {
trackPlural: 'spor', trackPlural: 'spor',
showRemaining: 'Vis gjenværende tid', showRemaining: 'Vis gjenværende tid',
showTotal: 'Vis total tid', showTotal: 'Vis total tid',
rgTrack: 'T {{db}} dB',
rgAlbum: 'A {{db}} dB',
rgPeak: 'Topp {{pk}}',
}, },
statistics: { statistics: {
title: 'Statistikk', title: 'Statistikk',
+3
View File
@@ -847,6 +847,9 @@ export const nlTranslation = {
trackPlural: 'nummers', trackPlural: 'nummers',
showRemaining: 'Resterende tijd tonen', showRemaining: 'Resterende tijd tonen',
showTotal: 'Totale tijd tonen', showTotal: 'Totale tijd tonen',
rgTrack: 'T {{db}} dB',
rgAlbum: 'A {{db}} dB',
rgPeak: 'Piek {{pk}}',
}, },
statistics: { statistics: {
title: 'Statistieken', title: 'Statistieken',
+3
View File
@@ -898,6 +898,9 @@ export const ruTranslation = {
trackPlural: 'треков', trackPlural: 'треков',
showRemaining: 'Осталось', showRemaining: 'Осталось',
showTotal: 'Всего', showTotal: 'Всего',
rgTrack: 'Т {{db}} дБ',
rgAlbum: 'А {{db}} дБ',
rgPeak: 'Пик {{pk}}',
}, },
statistics: { statistics: {
title: 'Статистика', title: 'Статистика',
+3
View File
@@ -843,6 +843,9 @@ export const zhTranslation = {
trackPlural: '首曲目', trackPlural: '首曲目',
showRemaining: '显示剩余时间', showRemaining: '显示剩余时间',
showTotal: '显示总时间', showTotal: '显示总时间',
rgTrack: '曲目 {{db}} dB',
rgAlbum: '专辑 {{db}} dB',
rgPeak: '峰值 {{pk}}',
}, },
statistics: { statistics: {
title: '统计', title: '统计',