|
|
|
|
@@ -33,12 +33,34 @@ export async function GET(request: NextRequest) {
|
|
|
|
|
? await scanDirectoryRecursive(root, libraryId, subpath)
|
|
|
|
|
: scanDirectory(root, libraryId, subpath)
|
|
|
|
|
|
|
|
|
|
// Annotate image files with hasExtractedText, and directories if any descendant has extracted text
|
|
|
|
|
// Annotate entries with metadata used by search/filtering in mixed view.
|
|
|
|
|
const db = getDb()
|
|
|
|
|
const rows = db
|
|
|
|
|
.prepare('SELECT item_key FROM media_items WHERE library_id = ? AND extracted_text IS NOT NULL')
|
|
|
|
|
.all(libraryId) as { item_key: string }[]
|
|
|
|
|
const withText = new Set(rows.map((r) => r.item_key))
|
|
|
|
|
const metadataRows = db
|
|
|
|
|
.prepare(`
|
|
|
|
|
SELECT item_key, user_rating, ai_description, extracted_text, extracted_text_translated
|
|
|
|
|
FROM media_items
|
|
|
|
|
WHERE library_id = ?
|
|
|
|
|
AND (
|
|
|
|
|
user_rating IS NOT NULL
|
|
|
|
|
OR ai_description IS NOT NULL
|
|
|
|
|
OR extracted_text IS NOT NULL
|
|
|
|
|
OR extracted_text_translated IS NOT NULL
|
|
|
|
|
)
|
|
|
|
|
`)
|
|
|
|
|
.all(libraryId) as {
|
|
|
|
|
item_key: string
|
|
|
|
|
user_rating: number | null
|
|
|
|
|
ai_description: string | null
|
|
|
|
|
extracted_text: string | null
|
|
|
|
|
extracted_text_translated: string | null
|
|
|
|
|
}[]
|
|
|
|
|
|
|
|
|
|
const metadataByItemKey = new Map(metadataRows.map((r) => [r.item_key, r]))
|
|
|
|
|
const withText = new Set(
|
|
|
|
|
metadataRows
|
|
|
|
|
.filter((r) => r.extracted_text !== null)
|
|
|
|
|
.map((r) => r.item_key)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Build a set of all ancestor directory relative paths that contain at least one item with text
|
|
|
|
|
// e.g. item_key "lib:mixed_file:manga%2Fch1%2Fp1.jpg" → ancestors "manga", "manga/ch1"
|
|
|
|
|
@@ -52,19 +74,19 @@ export async function GET(request: NextRequest) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ratingRows = db
|
|
|
|
|
.prepare('SELECT item_key, user_rating FROM media_items WHERE library_id = ? AND user_rating IS NOT NULL')
|
|
|
|
|
.all(libraryId) as { item_key: string; user_rating: number }[]
|
|
|
|
|
const ratingMap = new Map(ratingRows.map((r) => [r.item_key, r.user_rating]))
|
|
|
|
|
|
|
|
|
|
listing.entries = listing.entries.map((e) => {
|
|
|
|
|
if (e.type === 'file') {
|
|
|
|
|
const relPath = subpath ? path.join(subpath, e.name) : e.name
|
|
|
|
|
// Recursive listing already uses full path from library root in e.name.
|
|
|
|
|
const relPath = recursive ? e.name : (subpath ? path.join(subpath, e.name) : e.name)
|
|
|
|
|
const itemKey = `${libraryId}:mixed_file:${encodeURIComponent(relPath)}`
|
|
|
|
|
const metadata = metadataByItemKey.get(itemKey)
|
|
|
|
|
return {
|
|
|
|
|
...e,
|
|
|
|
|
...(e.mediaType === 'image' ? { hasExtractedText: withText.has(itemKey) } : {}),
|
|
|
|
|
userRating: ratingMap.get(itemKey) ?? null,
|
|
|
|
|
userRating: metadata?.user_rating ?? null,
|
|
|
|
|
aiDescription: metadata?.ai_description ?? null,
|
|
|
|
|
extractedText: metadata?.extracted_text ?? null,
|
|
|
|
|
extractedTextTranslated: metadata?.extracted_text_translated ?? null,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (e.type === 'directory') {
|
|
|
|
|
|