feat(builder): enhance eagle storage plugin to read and inject image metadata (#141)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Whitewater
2025-11-03 12:56:58 +08:00
committed by GitHub
parent 823a272974
commit 22f058aee3
6 changed files with 83 additions and 13 deletions

View File

@@ -1,5 +1,5 @@
import type { EagleConfig } from '../../storage/interfaces.js'
import { EagleStorageProvider } from '../../storage/providers/eagle-provider.js'
import { EagleStorageProvider, getEagleFolderIndex, readImageMetadata } from '../../storage/providers/eagle-provider.js'
import type { BuilderPlugin } from '../types.js'
export interface EagleStoragePluginOptions {
@@ -17,6 +17,51 @@ export default function eagleStoragePlugin(options: EagleStoragePluginOptions =
return new EagleStorageProvider(config as EagleConfig)
})
},
/**
* Inject Eagle image metadata (name, tags) into manifest items before saving.
* This only applies when the configured storage provider is 'eagle'.
*/
beforeAddManifestItem: async ({ config, payload, logger, runShared }) => {
const { storage } = config
if (!storage || storage.provider !== 'eagle') return
const eagleConfig = storage
const key = payload.item.s3Key
const meta = await readImageMetadata(eagleConfig.libraryPath, key)
// Append folder names as tags if enabled
if (eagleConfig.folderAsTag) {
try {
const indexCacheKey = 'afilmory:eagle:folderIndex'
let folderIndex = runShared.get(indexCacheKey) as Map<string, string[]> | undefined
if (!folderIndex) {
folderIndex = await getEagleFolderIndex(eagleConfig.libraryPath)
runShared.set(indexCacheKey, folderIndex)
}
const folderNames = (meta.folders ?? [])
.map((id) => folderIndex?.get(id))
.filter((p): p is string[] => Array.isArray(p) && p.length > 0)
.map((p) => p.at(-1) as string) // take leaf folder name
if (folderNames.length > 0) {
const merged = new Set([...(meta.tags ?? []), ...folderNames])
meta.tags = Array.from(merged)
}
} catch (e) {
logger.main.warn(`eagle: failed to append folder tags for key=${key}: ${String(e)}`)
}
}
// Apply omitTagNamesInMetadata filter
const omit = new Set(eagleConfig.omitTagNamesInMetadata ?? [])
if (omit.size > 0 && meta.tags) {
meta.tags = meta.tags.filter((t) => !omit.has(t))
}
meta.tags?.sort((a, b) => a.localeCompare(b))
// Overwrite title and tags with Eagle metadata when available
if (meta.name) payload.item.title = meta.name
if (meta.tags) payload.item.tags = meta.tags
},
},
}
}

View File

@@ -164,6 +164,16 @@ export type EagleConfig = {
baseUrl?: string
include?: EagleRule[]
exclude?: EagleRule[]
/**
* When enabled, also add Eagle folder names as tags for each image.
* Defaults to false.
*/
folderAsTag?: boolean
/**
* Omit these tag names only from the manifest display (metadata) output.
* Exact match, case-sensitive. Does not affect which images are included/excluded.
*/
omitTagNamesInMetadata?: string[]
}
export type StorageConfig = S3Config | GitHubConfig | EagleConfig | LocalConfig

View File

@@ -314,6 +314,8 @@ const eagleConfig: EagleConfig = {
{ type: 'tag', name: 'Published' },
],
exclude: [{ type: 'tag', name: 'Private' }],
folderAsTag: true,
omitTagNamesInMetadata: ['Temp'],
}
```

View File

@@ -34,7 +34,7 @@ interface EagleLibraryMetadata {
applicationVersion: '4.0.0'
}
interface EagleImageMetadata {
export interface EagleImageMetadata {
id: string
name: string
size: number
@@ -62,6 +62,8 @@ const defaultEagleConfig = {
baseUrl: '/originals/',
include: [],
exclude: [],
folderAsTag: false,
omitTagNamesInMetadata: [],
} satisfies Required<EagleConfig>
export class EagleStorageProvider implements StorageProvider {
@@ -113,15 +115,7 @@ export class EagleStorageProvider implements StorageProvider {
logger.main.info(`EagleStorageProvider: 已创建 distPath 目录:${this.config.distPath}`)
}
const libraryMetadata = await readEagleLibraryMetadata(this.config.libraryPath)
logger.main.info(`EagleStorageProvider: 检测到 Eagle 版本:${libraryMetadata.applicationVersion}`)
if (Number(libraryMetadata.applicationVersion.at(0)) !== Number(EAGLE_VERSION.at(0))) {
logger.main.warn(
`EagleStorageProvider: 当前支持 Eagle ${EAGLE_VERSION} 版本的库,检测到的版本为:${libraryMetadata.applicationVersion},可能会导致兼容性问题。`,
)
}
this.folderIndex = buildFolderIndexes(libraryMetadata.folders ?? [])
this.folderIndex = await getEagleFolderIndex(this.config.libraryPath)
}
async getFile(key: string): Promise<Buffer | null> {
@@ -319,7 +313,7 @@ async function readEagleLibraryMetadata(libraryPath: string): Promise<EagleLibra
}
}
async function readImageMetadata(libraryPath: string, key: string): Promise<EagleImageMetadata> {
export async function readImageMetadata(libraryPath: string, key: string): Promise<EagleImageMetadata> {
const metadataPath = path.join(libraryPath, 'images', `${key}.info`, 'metadata.json')
const content = await fs.readFile(metadataPath, 'utf-8')
return JSON.parse(content) as EagleImageMetadata
@@ -341,3 +335,18 @@ function buildFolderIndexes(folders: EagleFolderNode[]) {
}
return map
}
/**
* Public helper for plugins to retrieve the folder index (folderId -> path segments)
* from the Eagle library metadata.
*/
export async function getEagleFolderIndex(libraryPath: string): Promise<Map<string, string[]>> {
const metadata = await readEagleLibraryMetadata(libraryPath)
logger.main.info(`EagleStorageProvider: 检测到 Eagle 版本:${metadata.applicationVersion}`)
if (Number(metadata.applicationVersion.at(0)) !== Number(EAGLE_VERSION.at(0))) {
logger.main.warn(
`EagleStorageProvider: 当前支持 Eagle ${EAGLE_VERSION} 版本的库,检测到的版本为:${metadata.applicationVersion},可能会导致兼容性问题。`,
)
}
return buildFolderIndexes(metadata.folders ?? [])
}

View File

@@ -2,7 +2,7 @@
title: Storage providers
description: Afilmory can work with multiple storage providers, including S3, Git, Eagle, and local file system
createdAt: 2025-08-12T15:09:08+08:00
lastModified: 2025-10-28T19:48:05+08:00
lastModified: 2025-11-01T19:31:16+08:00
---
# Storage Providers
@@ -117,6 +117,8 @@ export default defineBuilderConfig(() => ({
{ type: 'tag', name: 'Featured' },
],
exclude: [{ type: 'tag', name: 'Private' }],
folderAsTag: true,
omitTagNamesInMetadata: ['Temp'],
},
}))
```