diff --git a/src/helpers/find-supported-media-files.ts b/src/helpers/find-supported-media-files.ts index dfe3928..01076b8 100644 --- a/src/helpers/find-supported-media-files.ts +++ b/src/helpers/find-supported-media-files.ts @@ -1,5 +1,5 @@ import { existsSync } from 'fs'; -import { basename, extname, resolve } from 'path'; +import { basename, dirname, extname, relative, resolve } from 'path'; import { CONFIG } from '../config'; import { MediaFileInfo } from '../models/media-file-info'; import { doesFileSupportExif } from './does-file-support-exif'; @@ -7,7 +7,7 @@ import { findFilesWithExtensionRecursively } from './find-files-with-extension-r import { generateUniqueOutputFileName } from './generate-unique-output-file-name'; import { getCompanionJsonPathForMediaFile } from './get-companion-json-path-for-media-file'; -export async function findSupportedMediaFiles(inputDir: string, outputDir: string): Promise { +export async function findSupportedMediaFiles(inputDir: string, outputDir: string, preserveStructure: boolean): Promise { const supportedMediaFileExtensions = CONFIG.supportedMediaFileTypes.map(fileType => fileType.extension); const mediaFilePaths = await findFilesWithExtensionRecursively(inputDir, supportedMediaFileExtensions); @@ -16,6 +16,7 @@ export async function findSupportedMediaFiles(inputDir: string, outputDir: strin for (const mediaFilePath of mediaFilePaths) { const mediaFileName = basename(mediaFilePath); + const mediaFolder = relative(inputDir, dirname(mediaFilePath)); const mediaFileExtension = extname(mediaFilePath); const supportsExif = doesFileSupportExif(mediaFilePath); @@ -24,7 +25,8 @@ export async function findSupportedMediaFiles(inputDir: string, outputDir: strin const jsonFileExists = jsonFilePath ? existsSync(jsonFilePath) : false; const outputFileName = generateUniqueOutputFileName(mediaFilePath, allUsedOutputFilesLowerCased); - const outputFilePath = resolve(outputDir, outputFileName); + const outputFilePath = preserveStructure ? resolve(outputDir, mediaFolder, outputFileName) : resolve(outputDir, outputFileName); + const outputFileFolder = dirname(outputFilePath); mediaFiles.push({ mediaFilePath, @@ -36,6 +38,7 @@ export async function findSupportedMediaFiles(inputDir: string, outputDir: strin jsonFileExists, outputFileName, outputFilePath, + outputFileFolder, }); allUsedOutputFilesLowerCased.push(outputFileName.toLowerCase()); } diff --git a/src/index.ts b/src/index.ts index 644e1ef..be21b4d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { Command, flags } from '@oclif/command'; import * as Parser from '@oclif/parser'; -import { existsSync, promises as fspromises } from 'fs'; +import { existsSync, promises as fspromises, mkdirSync } from 'fs'; import { CONFIG } from './config'; import { doesFileHaveExifDate } from './helpers/does-file-have-exif-date'; import { findSupportedMediaFiles } from './helpers/find-supported-media-files'; @@ -32,6 +32,10 @@ class GooglePhotosExif extends Command { description: 'Directory for any files that have bad EXIF data - including the matching metadata files', required: true, }), + preserveStructure: flags.boolean({ + char: 'p', + description: 'The internal structure of the input directory will be copied in the output directory (Defaults to not enabled: media will be stored in the top leve of the output directory)', + }) } static args: Parser.args.Input = [] @@ -43,9 +47,9 @@ class GooglePhotosExif extends Command { try { const directories = this.determineDirectoryPaths(inputDir, outputDir, errorDir); await this.prepareDirectories(directories); - await this.processMediaFiles(directories); + await this.processMediaFiles(directories, flags.preserveStructure); } catch (error) { - this.error(error); + this.error(error as Error); this.exit(1); } @@ -89,15 +93,15 @@ class GooglePhotosExif extends Command { } } else { this.log(`--- Creating directory: ${directoryPath} ---`); - await mkdir(directoryPath); + mkdirSync(directoryPath); } } - private async processMediaFiles(directories: Directories): Promise { + private async processMediaFiles(directories: Directories, preserveStructure: boolean): Promise { // Find media files const supportedMediaFileExtensions = CONFIG.supportedMediaFileTypes.map(fileType => fileType.extension); this.log(`--- Finding supported media files (${supportedMediaFileExtensions.join(', ')}) ---`) - const mediaFiles = await findSupportedMediaFiles(directories.input, directories.output); + const mediaFiles = await findSupportedMediaFiles(directories.input, directories.output, preserveStructure); // Count how many files were found for each supported file extension const mediaFileCountsByExtension = new Map(); @@ -114,6 +118,12 @@ class GooglePhotosExif extends Command { this.log(`--- Processing media files ---`); const fileNamesWithEditedExif: string[] = []; + if (preserveStructure) { + for (let i = 0, mediaFile; mediaFile = mediaFiles[i]; i++) { + this.checkDirIsEmptyAndCreateDirIfNotFound(mediaFile.outputFileFolder, `Could not create subfolder in output directory`); + } + } + for (let i = 0, mediaFile; mediaFile = mediaFiles[i]; i++) { // Copy the file into output directory @@ -128,7 +138,7 @@ class GooglePhotosExif extends Command { const hasExifDate = await doesFileHaveExifDate(mediaFile.mediaFilePath); if (!hasExifDate) { await updateExifMetadata(mediaFile, photoTimeTaken, directories.error); - fileNamesWithEditedExif.push(mediaFile.outputFileName); + fileNamesWithEditedExif.push(mediaFile.outputFilePath); this.log(`Wrote "DateTimeOriginal" EXIF metadata to: ${mediaFile.outputFileName}`); } } diff --git a/src/models/media-file-info.ts b/src/models/media-file-info.ts index 3442b60..52b3e0c 100644 --- a/src/models/media-file-info.ts +++ b/src/models/media-file-info.ts @@ -10,4 +10,5 @@ export interface MediaFileInfo { outputFileName: string; outputFilePath: string; + outputFileFolder: string; }