@@ -4,6 +4,9 @@ import { thumbnailExists } from '../image/thumbnail.js'
44import { logger } from '../logger/index.js'
55import { handleDeletedPhotos , loadExistingManifest , needsUpdate , saveManifest } from '../manifest/manager.js'
66import { CURRENT_MANIFEST_VERSION } from '../manifest/version.js'
7+ import { createStorageKeyNormalizer , runWithPhotoExecutionContext } from '../photo/execution-context.js'
8+ import { createGeocodingProvider , parseGPSCoordinates } from '../photo/geocoding.js'
9+ import { createPhotoProcessingLoggers } from '../photo/logger-adapter.js'
710import type { PhotoProcessorOptions } from '../photo/processor.js'
811import { processPhoto } from '../photo/processor.js'
912import type { PluginRunState } from '../plugins/manager.js'
@@ -18,7 +21,7 @@ import type { StorageConfig } from '../storage/index.js'
1821import { StorageFactory , StorageManager } from '../storage/index.js'
1922import type { BuilderConfig , UserBuilderSettings } from '../types/config.js'
2023import type { AfilmoryManifest , CameraInfo , LensInfo } from '../types/manifest.js'
21- import type { PhotoManifestItem , ProcessPhotoResult } from '../types/photo.js'
24+ import type { LocationInfo , PhotoManifestItem , ProcessPhotoResult } from '../types/photo.js'
2225import { ClusterPool } from '../worker/cluster-pool.js'
2326import type { TaskCompletedPayload } from '../worker/pool.js'
2427import { WorkerPool } from '../worker/pool.js'
@@ -417,6 +420,13 @@ export class AfilmoryBuilder {
417420 } )
418421 }
419422
423+ const locationRetryStats = await this . retryMissingLocations ( manifest )
424+ if ( locationRetryStats . attempted > 0 ) {
425+ logger . main . info (
426+ `📍 为 ${ locationRetryStats . attempted } 张缺失位置信息的照片尝试补全,成功 ${ locationRetryStats . updated } 张` ,
427+ )
428+ }
429+
420430 await this . emitPluginEvent ( runState , 'afterProcessTasks' , {
421431 options,
422432 tasks : tasksToProcess ,
@@ -669,6 +679,78 @@ export class AfilmoryBuilder {
669679 return this . storageManager
670680 }
671681
682+ private async retryMissingLocations ( manifest : PhotoManifestItem [ ] ) : Promise < { attempted : number ; updated : number } > {
683+ const processingSettings = this . config . system . processing
684+ if ( ! processingSettings . enableGeocoding ) {
685+ return { attempted : 0 , updated : 0 }
686+ }
687+
688+ const provider = createGeocodingProvider (
689+ processingSettings . geocodingProvider || 'auto' ,
690+ processingSettings . mapboxToken ,
691+ processingSettings . nominatimBaseUrl ,
692+ )
693+
694+ if ( ! provider ) {
695+ return { attempted : 0 , updated : 0 }
696+ }
697+
698+ const hasCandidate = manifest . some (
699+ ( item ) =>
700+ ! item . location && item . exif && item . exif . GPSLatitude !== undefined && item . exif . GPSLongitude !== undefined ,
701+ )
702+
703+ if ( ! hasCandidate ) {
704+ return { attempted : 0 , updated : 0 }
705+ }
706+
707+ const storageManager = this . ensureStorageManager ( )
708+ const storageConfig = this . getStorageConfig ( )
709+ const normalizeStorageKey = createStorageKeyNormalizer ( storageConfig )
710+ const loggers = createPhotoProcessingLoggers ( 0 , logger )
711+
712+ return await runWithPhotoExecutionContext (
713+ {
714+ builder : this ,
715+ storageManager,
716+ storageConfig,
717+ normalizeStorageKey,
718+ loggers,
719+ } ,
720+ async ( ) => {
721+ const coordinateCache = new Map < string , LocationInfo | null > ( )
722+ let attempted = 0
723+ let updated = 0
724+
725+ for ( const item of manifest ) {
726+ if ( item . location || ! item . exif ) {
727+ continue
728+ }
729+
730+ const { latitude, longitude } = parseGPSCoordinates ( item . exif )
731+ if ( latitude === undefined || longitude === undefined ) {
732+ continue
733+ }
734+
735+ const cacheKey = `${ latitude . toFixed ( 4 ) } ,${ longitude . toFixed ( 4 ) } `
736+ let locationInfo = coordinateCache . get ( cacheKey )
737+ if ( locationInfo === undefined ) {
738+ locationInfo = await provider . reverseGeocode ( latitude , longitude )
739+ coordinateCache . set ( cacheKey , locationInfo ?? null )
740+ }
741+
742+ attempted ++
743+ if ( locationInfo ) {
744+ item . location = locationInfo
745+ updated ++
746+ }
747+ }
748+
749+ return { attempted, updated }
750+ } ,
751+ )
752+ }
753+
672754 private getUserSettings ( ) : UserBuilderSettings {
673755 if ( ! this . config . user ) {
674756 throw new Error ( 'User configuration is missing. 请配置 system/user 设置。' )
0 commit comments