mirror of
https://github.com/Afilmory/afilmory
synced 2026-04-24 23:05:05 +00:00
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:
@@ -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
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -314,6 +314,8 @@ const eagleConfig: EagleConfig = {
|
||||
{ type: 'tag', name: 'Published' },
|
||||
],
|
||||
exclude: [{ type: 'tag', name: 'Private' }],
|
||||
folderAsTag: true,
|
||||
omitTagNamesInMetadata: ['Temp'],
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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 ?? [])
|
||||
}
|
||||
|
||||
@@ -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'],
|
||||
},
|
||||
}))
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user