updated audio thumbnail and library sync

This commit is contained in:
2026-04-22 18:23:16 -07:00
parent e928181855
commit 199914a9b2
4 changed files with 118 additions and 21 deletions
+63 -12
View File
@@ -19,6 +19,7 @@ export type ConnectivityResult = {
export type HydrusMediaInfo = {
mimeType?: string
isVideo?: boolean
hasThumbnail?: boolean
}
export type HydrusFileDetails = HydrusMediaInfo & {
@@ -142,6 +143,33 @@ export class HydrusClient {
return res.json().catch(() => null)
}
private async getFilesMetadataPayload(fileIds: number[], signal?: AbortSignal) {
if (!fileIds || fileIds.length === 0) return []
const url = this.buildApiUrl('/get_files/file_metadata', {
file_ids: JSON.stringify(fileIds),
include_services_object: 'false',
}, this.cfg.forceApiKeyInQuery ?? false)
const headers = this.getHeaders(!(this.cfg.forceApiKeyInQuery ?? false))
const res = await this.fetchWithAuthRetry(url, { method: 'GET', headers, signal })
if (res.status === 404) {
console.warn('[HydrusClient] getFilesMetadata 404', { url, status: res.status, fileCount: fileIds.length })
return []
}
if (!res.ok) {
console.warn('[HydrusClient] getFilesMetadata Response Error', { status: res.status, statusText: res.statusText, fileCount: fileIds.length })
return []
}
const data = await res.json().catch(() => null)
if (Array.isArray(data?.metadata)) return data.metadata
if (Array.isArray(data?.file_metadata)) return data.file_metadata
if (Array.isArray(data)) return data
return []
}
private getFileMetadataEntry(data: any, fileId: number) {
if (!data || typeof data !== 'object') return null
@@ -198,6 +226,7 @@ export class HydrusClient {
let height = 0
let frameCount = 0
let hasDuration = false
let hasThumbnail = false
const mimeCandidates: string[] = []
const visit = (value: any, keyHint = '') => {
@@ -215,6 +244,7 @@ export class HydrusClient {
const lowerKey = keyHint.toLowerCase()
if (lowerKey === 'width') width = Math.max(width, value)
else if (lowerKey === 'height') height = Math.max(height, value)
else if ((lowerKey === 'thumbnail_width' || lowerKey === 'thumbnail_height') && value > 0) hasThumbnail = true
else if (lowerKey.includes('frame')) frameCount = Math.max(frameCount, value)
else if (lowerKey.includes('duration')) hasDuration = hasDuration || value > 0
return
@@ -240,23 +270,25 @@ export class HydrusClient {
visit(metadata)
const withThumbnail = (info: HydrusMediaInfo): HydrusMediaInfo => hasThumbnail ? { ...info, hasThumbnail: true } : info
for (const candidate of mimeCandidates) {
const mimeType = this.normalizeMimeType(candidate)
if (mimeType) {
return {
return withThumbnail({
mimeType,
isVideo: mimeType.startsWith('video/') || mimeType === 'application/vnd.apple.mpegurl'
}
})
}
const lower = candidate.toLowerCase()
if (lower.includes('video')) return { isVideo: true }
if (lower.includes('audio')) return { isVideo: false }
if (lower.includes('video')) return withThumbnail({ isVideo: true })
if (lower.includes('audio')) return withThumbnail({ isVideo: false })
}
if ((width > 0 || height > 0) && (frameCount > 1 || hasDuration)) return { isVideo: true }
if (hasDuration) return { isVideo: false }
if ((width > 0 || height > 0) && (frameCount > 1 || hasDuration)) return withThumbnail({ isVideo: true })
if (hasDuration) return withThumbnail({ isVideo: false })
return {}
return withThumbnail({})
}
private extractTagsFromMetadata(data: any, fileId: number): string[] {
@@ -657,20 +689,39 @@ export class HydrusClient {
const out: Record<number, HydrusMediaInfo> = {}
if (!fileIds || fileIds.length === 0) return out
const uniqueFileIds = Array.from(new Set(fileIds.filter((fileId) => Number.isFinite(fileId))))
const batchSize = 128
const batches: number[][] = []
for (let index = 0; index < uniqueFileIds.length; index += batchSize) {
batches.push(uniqueFileIds.slice(index, index + batchSize))
}
let idx = 0
const workers = new Array(Math.min(concurrency, fileIds.length)).fill(null).map(async () => {
const workers = new Array(Math.min(concurrency, batches.length)).fill(null).map(async () => {
while (true) {
if (signal?.aborted) throw createAbortError()
const i = idx
if (i >= fileIds.length) break
if (i >= batches.length) break
idx++
const fid = fileIds[i]
const batch = batches[i]
try {
out[fid] = await this.getFileMediaInfo(fid, signal)
const entries = await this.getFilesMetadataPayload(batch, signal)
const entryMap = new Map<number, any>()
for (const entry of entries) {
const entryFileId = Number(entry?.file_id)
if (Number.isFinite(entryFileId)) entryMap.set(entryFileId, entry)
}
for (const fid of batch) {
const entry = entryMap.get(fid)
out[fid] = entry ? this.extractMediaInfoFromMetadata(entry, fid) : {}
}
} catch (error: any) {
if (error?.name === 'AbortError') throw error
out[fid] = {}
for (const fid of batch) out[fid] = {}
}
}
})