feat: fetch and backfill cover art

This commit is contained in:
2026-07-21 14:23:07 +08:00
parent f4f4ab092f
commit b21a5d0ed1
21 changed files with 1156 additions and 46 deletions
+3
View File
@@ -19,6 +19,9 @@ export interface IngestStatusResponse {
lyricsFound: number;
lyricsMissing: number;
lyricsFailed: number;
coversFound: number;
coversMissing: number;
coversFailed: number;
message: string;
}
+6
View File
@@ -27,6 +27,12 @@ export interface ProgressMessage {
unreadableFiles?: number;
conversionFailedFiles?: number;
otherRejectedFiles?: number;
lyricsFound?: number;
lyricsMissing?: number;
lyricsFailed?: number;
coversFound?: number;
coversMissing?: number;
coversFailed?: number;
}
export function getProgress(taskId: string): Promise<ProgressMessage | null> {
+36 -7
View File
@@ -215,6 +215,12 @@
</div>
</div>
<!-- 封面统计 -->
<div class="stats-grid stats-secondary lyric-stats">
<div class="stat-cell-sm"><span class="stat-name-sm">有封面</span><span class="stat-num-sm">{{ coverStats.found }}</span></div>
<div class="stat-cell-sm"><span class="stat-name-sm">无封面</span><span class="stat-num-sm">{{ coverStats.missing }}</span></div>
<div class="stat-cell-sm"><span class="stat-name-sm">封面失败</span><span class="stat-num-sm">{{ coverStats.failed }}</span></div>
</div>
<!-- 歌词统计歌词有无绝不影响音频入库仅观测 -->
<div class="stats-grid stats-secondary lyric-stats">
<div class="stat-cell-sm">
@@ -339,6 +345,19 @@ import { useWebSocket } from '../composables/useWebSocket';
import type { ProgressMessage } from '../composables/useWebSocket';
import { useAppState } from '../composables/useAppState';
interface IngestProgressMessage extends ProgressMessage {
lyricsFound?: number;
lyricsMissing?: number;
lyricsFailed?: number;
coversFound?: number;
coversMissing?: number;
coversFailed?: number;
}
function isIngestMessage(msg: ProgressMessage): msg is IngestProgressMessage {
return msg.type === 'ingest' || 'coversFound' in msg || 'lyricsFound' in msg;
}
const {
state: appState,
isConfigured,
@@ -380,22 +399,27 @@ const canStart = computed(() => isConfigured.value && !submitting.value);
* 歌词统计(独立于 ProgressMessage 类型,避免修改共享类型)。
* 歌词的有/无/失败绝不影响音频入库,仅作观测展示。
*/
const coverStats = reactive<{ found: number; missing: number; failed: number }>({ found: 0, missing: 0, failed: 0 });
const lyricStats = reactive<{ found: number; missing: number; failed: number }>({
found: 0,
missing: 0,
failed: 0
});
type LyricCarrier = { lyricsFound?: number; lyricsMissing?: number; lyricsFailed?: number };
function applyLyricStats(src: ProgressMessage | null | undefined) {
if (!src || !isIngestMessage(src)) return;
function applyLyricStats(src: LyricCarrier | null | undefined) {
if (!src) return;
if (typeof src.lyricsFound === 'number') lyricStats.found = src.lyricsFound;
if (typeof src.lyricsMissing === 'number') lyricStats.missing = src.lyricsMissing;
if (typeof src.lyricsFailed === 'number') lyricStats.failed = src.lyricsFailed;
if (typeof src.coversFound === 'number') coverStats.found = src.coversFound;
if (typeof src.coversMissing === 'number') coverStats.missing = src.coversMissing;
if (typeof src.coversFailed === 'number') coverStats.failed = src.coversFailed;
}
function resetLyricStats() {
coverStats.found = 0; coverStats.missing = 0; coverStats.failed = 0;
lyricStats.found = 0;
lyricStats.missing = 0;
lyricStats.failed = 0;
@@ -600,7 +624,7 @@ async function scrollConsoleIfPinned() {
function handleProgressMessage(msg: ProgressMessage) {
Object.assign(progress, msg);
applyLyricStats(msg as unknown as LyricCarrier);
applyLyricStats(msg);
recordActivity(msg);
if (typeof msg.running === 'boolean') {
@@ -619,7 +643,7 @@ const { connect: wsConnect, disconnect: wsDisconnect } = useWebSocket(taskId, ha
let pollTimer: ReturnType<typeof setInterval> | null = null;
function statusToProgress(resp: IngestStatusResponse): ProgressMessage {
function statusToProgress(resp: IngestStatusResponse): IngestProgressMessage {
return {
taskId: resp.taskId,
type: 'ingest',
@@ -636,7 +660,13 @@ function statusToProgress(resp: IngestStatusResponse): ProgressMessage {
missingMetadataFiles: resp.missingMetadataFiles,
unreadableFiles: resp.unreadableFiles,
conversionFailedFiles: resp.conversionFailedFiles,
otherRejectedFiles: resp.otherRejectedFiles
otherRejectedFiles: resp.otherRejectedFiles,
lyricsFound: resp.lyricsFound,
lyricsMissing: resp.lyricsMissing,
lyricsFailed: resp.lyricsFailed,
coversFound: resp.coversFound,
coversMissing: resp.coversMissing,
coversFailed: resp.coversFailed
};
}
@@ -647,7 +677,6 @@ function startPolling() {
try {
const resp = await getIngestStatus(taskId.value);
if (resp) {
applyLyricStats(resp);
handleProgressMessage(statusToProgress(resp));
if (resp.completed || !resp.running) {
stopPolling();