updated audio thumbnail and library sync
This commit is contained in:
+63
-12
@@ -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] = {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user