diff --git a/configs.mjs b/configs.mjs index f9c8fd9..8a7b3c0 100644 --- a/configs.mjs +++ b/configs.mjs @@ -1,9 +1,11 @@ -export default class config_rangement{ - log_level ='debug' // [ 'debug' 'none'] +class config_rangement{ + log_level ='debug' // 'debug' | 'warn' |'info' version = '1.0.0' tagSeparator = '1.0.0' tagSectionSeparator = '1.0.0' + keepFreeText= true + keepTags= true enableTestsLocally= false reportStatistics= false base_archive_folder= '/home/poule/encrypted/stockage-syncable/' @@ -39,7 +41,13 @@ export default class config_rangement{ } , } } -export const tagSeparator = ' ' -export const tagSectionSeparator = '--' -export const enableTestsLocally = false -export const reportStatistics = false \ No newline at end of file +const rangement_instance = new config_rangement(); + +export const tagSeparator = rangement_instance.tagSeparator +export const tagSectionSeparator = rangement_instance.tagSectionSeparator +export const enableTestsLocally = rangement_instance.enableTestsLocally +export const reportStatistics = rangement_instance.reportStatistics + + +export default rangement_instance + diff --git a/finder.mjs b/finder.mjs index 68fe9c6..350b9f5 100644 --- a/finder.mjs +++ b/finder.mjs @@ -1,10 +1,12 @@ /** * la classe qui repère des patterns */ -import { tagSectionSeparator, tagSeparator } from './configs.mjs' +import rangement_instance , { tagSectionSeparator, tagSeparator} from './configs.mjs' import exifr from 'exifr' import moment from 'moment' -import path from 'path' +import log from "loglevel"; +log.setLevel(rangement_instance.log_level) + /** * finds patterns for file name @@ -16,7 +18,7 @@ export default class finder { } static reportStatistics () { - console.log('statistics', + log.info('statistics', this.statistics) } @@ -26,7 +28,7 @@ export default class finder { static findFormattedDate (filepath) { let match = filepath.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/ig) - // console.log('match findFormattedDate', match) + // log.debug('match findFormattedDate', match) let result = '' if (match && match[0]) { result = match[0] @@ -49,7 +51,7 @@ export default class finder { let boom = fileName.split(tagSectionSeparator) if (boom.length) { let freeTextPart = boom[0].trim() - console.log('freeTextPart', freeTextPart) + log.debug('freeTextPart', freeTextPart) return freeTextPart } return fileName.trim() @@ -68,24 +70,24 @@ export default class finder { if (extensionFile) { extensionFile = extensionFile[0] } else { - console.log('no extensionFile', extensionFile, inputString) + log.debug('no extensionFile', extensionFile, inputString) extensionFile = '' } inputString = inputString.replace(extensionFile, '') - // console.log('extensionFile', extensionFile) + // log.debug('extensionFile', extensionFile) if (inputString.includes(tagSectionSeparator)) { - // console.log('inputString', inputString) + // log.debug('inputString', inputString) if (inputString.length) { let boom = inputString.split(tagSectionSeparator) - // console.log('boom', boom) + // log.debug('boom', boom) if (boom.length) { let fileSectionsName = boom.splice(tagSeparator) listOfTags = [...fileSectionsName[1].trim().split(tagSeparator)] - // console.log('listOfTags', listOfTags) + // log.debug('listOfTags', listOfTags) } else { - console.log('no boom', boom) + log.debug('no boom', boom) } } } @@ -106,15 +108,15 @@ export default class finder { static searchAndRenameScreenshots (fileName) { if (finder.findScreenshot(fileName)) { let tags = this.findTagSectionInString(fileName) - console.log('tags', tags) + log.debug('tags', tags) if (!tags.includes('screenshot')) { fileName = this.addTagInFileName('screenshot', fileName) fileName = this.searchAndReplaceInFileName('Screenshot', '', fileName) - console.log('screenShotMockFileName:', fileName) + log.debug('screenShotMockFileName:', fileName) return this.cleanSpaces(fileName) } - console.log('is a screenshot, remove screenshot in name, and add tag screenshot') + log.debug('is a screenshot, remove screenshot in name, and add tag screenshot') } else { return null } @@ -164,17 +166,17 @@ export default class finder { let moments = [] - // console.log('exif data : ', exifData) // Do something with your data! + // log.debug('exif data : ', exifData) // Do something with your data! if (exifData.DateTimeOriginal) { - // console.log('image créée le : DateTimeOriginal : ', exifData.DateTimeOriginal) // Do something with your data! + // log.debug('image créée le : DateTimeOriginal : ', exifData.DateTimeOriginal) // Do something with your data! moments.push(exifData.DateTimeOriginal) } if (exifData.ModificationDateTime) { - // console.log('image créée le : ModificationDateTime : ', exifData.ModificationDateTime) // Do something with your data! + // log.debug('image créée le : ModificationDateTime : ', exifData.ModificationDateTime) // Do something with your data! moments.push(exifData.ModificationDateTime) } if (exifData.ModifyDate) { - // console.log('image créée le : ModifyDate : ', exifData.ModifyDate) // Do something with your data! + // log.debug('image créée le : ModifyDate : ', exifData.ModifyDate) // Do something with your data! moments.push(exifData.ModifyDate) } if (exifData.FileAccessDateTime) { @@ -184,11 +186,11 @@ export default class finder { moments.push(exifData.FileInodeChangeDateTime) } if (exifData.FileModificationDateTime) { - // console.log('image créée le : FileModificationDateTime : ', exifData.FileModificationDateTime) // Do something with your data! + // log.debug('image créée le : FileModificationDateTime : ', exifData.FileModificationDateTime) // Do something with your data! moments.push(exifData.FileModificationDateTime) } if (exifData.CreateDate) { - // console.log('image créée le : CreateDate : ', exifData.CreateDate) // Do something with your data! + // log.debug('image créée le : CreateDate : ', exifData.CreateDate) // Do something with your data! moments.push(exifData.CreateDate) } @@ -198,12 +200,12 @@ export default class finder { }) let minDate = moment.min(moments) - // console.log('minDate :::::::::', minDate) - console.log('minDate :::::::::', minDate.format('yyyy-MM-DDTHH:mm:ss')) + // log.debug('minDate :::::::::', minDate) + log.debug('minDate :::::::::', minDate.format('yyyy-MM-DDTHH:mm:ss')) return minDate.format('yyyy-MM-DDTHH:mm:ss') } else { - console.log('pas de exif data') + log.debug('pas de exif data') return '' } } @@ -214,11 +216,11 @@ export default class finder { */ static async findExifCreationDate (filepath) { - console.log('filepath', filepath) + log.debug('filepath', filepath) let dateAlreadyInFileName = finder.findFormattedDate(filepath) if (dateAlreadyInFileName) { - console.log('------ dateAlreadyInFileName', dateAlreadyInFileName) + log.debug('------ dateAlreadyInFileName', dateAlreadyInFileName) } return await exifr.parse(filepath) @@ -230,8 +232,8 @@ export default class finder { let fileName = folders.pop() folders = filePath.replace(fileName, '') - console.log('\n - folders', folders) - console.log(' - fileName', fileName, '\n') + log.debug('\n - folders', folders) + log.debug(' - fileName', fileName, '\n') return [folders, fileName] } diff --git a/index.mjs b/index.mjs index 86baaa9..6c0f07b 100644 --- a/index.mjs +++ b/index.mjs @@ -2,143 +2,222 @@ * @name tykayn Rangement * @description Rangement sorts and rename files depending on their exif data * @contact contact@cipherbliss.com - --------------------- */ + --------------------- */ /** --------------------- - libs - --------------------- */ + libs + --------------------- */ import fs from 'node-fs' import minimist from 'minimist' import log from 'loglevel'; - +import path from "node:path"; /** --------------------- - custom utilities and configuration + custom utilities and configuration --------------------- */ -import { enableTestsLocally, reportStatistics,tagSectionSeparator, tagSeparator } from './configs.mjs' +import rangement_instance from './configs.mjs' import { - TestFindFormattedDate, - TestScreenShotIsFoundAndRenamed, - TestTagsAreDetectedInFileName + TestFindFormattedDate, + TestScreenShotIsFoundAndRenamed, + TestTagsAreDetectedInFileName } from './testFunctions.mjs' import finder from './finder.mjs' +import exiftool from "node-exiftool"; + let mini_arguments -log.setLevel('info') + +log.setLevel(rangement_instance.log_level) log.info(' ') -function parseArguments () { - mini_arguments = minimist(process.argv.slice(2)) - log.info('arguments', mini_arguments) +function parseArguments() { + mini_arguments = minimist(process.argv.slice(2)) + log.debug('arguments', mini_arguments) } -parseArguments() +function addOriginalFileNameIfMissing(originalFileName, fileMixedNewName) { -function renameFile (originalFileName, fileMixedNewName) { - fs.rename(originalFileName, fileMixedNewName, function (err) { - log.info('name changed', fileMixedNewName) - if (err) log.info('rename ERROR: ' + err) - }) + const ep = new exiftool.ExiftoolProcess() + + ep + .open(fileMixedNewName) + .then(() => ep.writeMetadata(fileMixedNewName, { + 'OriginalFileName+': originalFileName, + })) + .then(console.log, console.error) + .then(() => ep.close()) + .catch(console.error) } -function appendFileName (fileProperties, newText) { - fileProperties.freeText = finder.cleanSpaces(fileProperties.freeText + ' ' + newText) - return fileProperties +function renameFile(originalFileName, fileMixedNewName) { + fs.rename(originalFileName, fileMixedNewName, function (err) { + log.info('name changed', fileMixedNewName) + if (err) { + log.info('rename ERROR: ' + err) + } else { + addOriginalFileNameIfMissing(originalFileName, fileMixedNewName) + rangement_instance.statistics['filesModified']++ + } + }) } -function prependFileName (fileProperties, newText) { - fileProperties.freeText = finder.cleanSpaces(newText + ' ' + fileProperties.freeText) - return fileProperties +function appendFileName(fileProperties, newText) { + fileProperties.freeText = finder.cleanSpaces(fileProperties.freeText + ' ' + newText) + return fileProperties +} + +function prependFileName(fileProperties, newText) { + fileProperties.freeText = finder.cleanSpaces(newText + ' ' + fileProperties.freeText) + return fileProperties } function makeFileNameFromProperties(fileProperties) { - let tagPlace = '' - if (fileProperties.tags.length) { - tagPlace = ' ' + tagSectionSeparator + ' ' - } - // return finder.cleanSpaces(fileProperties.dateStamp + ' ' + fileProperties.freeText + tagPlace + fileProperties.tags.join(tagSeparator) + fileProperties.extension).replace(+' ' + tagSectionSeparator + ' ' + '.', '.') - return ''+fileProperties.dateStampExif + ' ' + fileProperties.freeText + tagPlace + fileProperties.tags.join(tagSeparator) + fileProperties.extension + let tagPlace = '' + if (fileProperties.tags.length) { + tagPlace = ' ' + rangement_instance.tagSectionSeparator + ' ' + } + return '' + fileProperties.dateStampExif + ' ' + fileProperties.freeText + tagPlace + fileProperties.tags.join(tagSeparator) + fileProperties.extension } -function shouldWeChangeName (structureForFile) { - log.info(' ______ allez hop fini la recherche on fait un nouveau nom') - log.info('structureForFile', structureForFile) - let newName = makeFileNameFromProperties(structureForFile) - if (structureForFile.fileNameOriginal !== newName) { +function shouldWeChangeName(structureForFile) { + log.info(' ______ allez hop fini la recherche on fait un nouveau nom') + log.info('structureForFile', structureForFile) + let newName = makeFileNameFromProperties(structureForFile) + if (structureForFile.fileNameOriginal !== newName) { - log.info('\n ancien nom :', structureForFile.fileNameOriginal) - // log.info(' nouveau nom:', foundDate +structureForFile.freeText + structureForFile.tags.join(tagSeparator) + structureForFile.extension ) - log.info(' nouveau nom:', newName) - if(! mini_arguments.dryRun){ - renameFile(structureForFile.fullPath, structureForFile.folderPath + newName) - } - else{ - log.info('no renaming for real, this is a dry run') - } - } else { - log.info(' rien à changer') - } + log.info('\n ancien nom :', structureForFile.fileNameOriginal) + + log.info(' nouveau nom:', newName) + if (!mini_arguments['dry-run']) { + renameFile(structureForFile.fullPath, structureForFile.folderPath + newName) + } else { + log.info('no renaming for real, this is a dry run') + } + } else { + log.info(' rien à changer') + } } -async function guessFileNameOnAllFilesFromArguments () { +/** + * guess file name on one file which is not a directory + * @param fullPath + */ +function guessFileNameOnOnefile(fullPath) { - // parcourir les dossiers - // parcourir les fichiers + fs.stat(fullPath, (err, stats) => { - log.info('liste des fichiers', mini_arguments._) - let fileList = mini_arguments._ + if (err) { + log.error('échec fichier', err) + log.error('ce fichier n existe pas: ', fullPath) + return; + } else { - fileList.forEach(fullPath => { + let structureForFile = finder.destructurateFileName(fullPath) - let structureForFile = finder.destructurateFileName(fullPath) + // examiner les infos exif de chaque fichier pour proposer un nouveau nom + if (!structureForFile.dateStampInFileNameOriginal) { + log.debug(' le nom de fichier ne contient pas de date formatée au début') - // examiner les infos exif de chaque fichier pour proposer un nouveau nom - if (!structureForFile.dateStampInFileNameOriginal) { - log.info(' le nom de fichier ne contient pas de date formatée au début') + finder.findExifCreationDate(structureForFile.fullPath) + .then(data => { + log.debug(' ... chercher la date de création') + let foundDate = finder.findEarliestDateInExifData(data) - finder.findExifCreationDate(structureForFile.fullPath) - .then(data => { - log.info(' ... chercher la date de création') - let foundDate = finder.findEarliestDateInExifData(data) + log.info(' =>>>>>>> foundDate : ', foundDate) + if (foundDate) { + structureForFile.dateStampExif = foundDate + shouldWeChangeName(structureForFile) - log.info(' =>>>>>>> foundDate : ', foundDate) - if (foundDate) { + } else { + log.info('pas de date trouvée dans le nom') + } + + } + , + (error) => { + log.warn('/////////// Error in reading exif of file: ' + error.message) + return '' + }) + } + + } + }) +} + +let expandedFileList = [] +let cwd = path.dirname(process.cwd()) + '/' + path.basename(process.cwd()); +console.log('cwd', cwd) + +function guessFileNameOnAllFilesFromArguments() { - // finder.findEarliestDateInExifData(fullPath).then(response => { - // log.info(' ... trouvée') - // if (response) { - structureForFile.dateStampExif = foundDate + // parcourir les fichiers + log.debug('liste des fichiers', mini_arguments._) + let fileList = mini_arguments._ - shouldWeChangeName(structureForFile) - // } - // }) - } else { - log.info('pas de date trouvée dans le nom') - } + // test file exists + fileList.forEach(fullPath => { + // parcourir les dossiers + isFolderOrFile(`${fullPath}`) + } + ) - } - , - (error) => { - log.info('/////////// Error in reading exif of file: ' + error.message) - return '' - }) - } - } - ) + log.info('expanded file list :', expandedFileList) + expandedFileList.forEach(filePath => guessFileNameOnOnefile(filePath)) } +function readSubdirectories(baseDir) { + const newGlob = baseDir; + let fileList = []; + fs.readdir(baseDir, (err, files) => { + if (err) throw err; + + console.log('files', files) + files.forEach((subDirOrFile) => { + const newFullPath = path.resolve(baseDir, subDirOrFile); + + if (fs.existsSync(newFullPath)) { + const s = fs.statSync(newFullPath); + + if (s.isFile()) { + fileList.push(cwd+'/'+subDirOrFile) + } + } + }); + return fileList + }); + return fileList +} + +function isFolderOrFile(fileName) { + const stat = fs.statSync(cwd + '/' + fileName); + + if (stat.isDirectory()) { + let fileList = readSubdirectories(fileName); + console.log('fileList in directory ',fileName, '\n', fileList) + if (fileList) { + expandedFileList.push(...fileList) + } + } + else if (stat.isFile()) { + expandedFileList.push(cwd + '/' + fileName) + } +} + +parseArguments() + guessFileNameOnAllFilesFromArguments() -// run tests -if (enableTestsLocally) { - TestTagsAreDetectedInFileName() - TestFindFormattedDate() - TestScreenShotIsFoundAndRenamed() +// run tests +if (rangement_instance.enableTestsLocally) { + + TestTagsAreDetectedInFileName() + TestFindFormattedDate() + TestScreenShotIsFoundAndRenamed() } -if (reportStatistics || mini_arguments.stats) { - finder.reportStatistics() +if (rangement_instance.reportStatistics || mini_arguments.stats) { + finder.reportStatistics() } diff --git a/main.test.js b/main.test.js index be3d3c6..d930614 100644 --- a/main.test.js +++ b/main.test.js @@ -1,5 +1,5 @@ -import finder from "./finders.mjs"; -// const finders = require('./finders.mjs') +// import finder from "./finders.mjs"; +const finders = require('./finders.mjs') describe('rangement file name', () => { diff --git a/package-lock.json b/package-lock.json index b3a07ae..f17b9f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "i18next": "^23.2.6", "minimist": "^1.2.8", "moment": "^2.29.4", + "node-exiftool": "^2.3.0", "node-fs": "^0.1.7" }, "devDependencies": { @@ -2742,6 +2743,11 @@ } ] }, + "node_modules/catchment": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/catchment/-/catchment-1.0.0.tgz", + "integrity": "sha512-6sTXWtTXI+MTHrHVnvZnOM8q2ujbQ+qJF0Mu8NM3jifMBCiZssjol/nBHQqOEiKSZKf1e4+wCrgmTXzWzvZs5w==" + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4249,6 +4255,11 @@ "tmpl": "1.0.5" } }, + "node_modules/makepromise": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/makepromise/-/makepromise-1.0.0.tgz", + "integrity": "sha512-M1q31Q56rlA+gwvmfWj7QFDMIJOuj527fQx/rN3dlzUtsopC9ZjtyekIBW4Fw0HZP6O6Azuw3/Vtk5O59bXz7A==" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -4317,6 +4328,24 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-exiftool": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-exiftool/-/node-exiftool-2.3.0.tgz", + "integrity": "sha512-UZd+k07thqXqp/1udsZghAFrjrsYfP8Kg48I9uv02FZAmT1Wasr8aAPMCObnYkJiWoD/V8UrGjiSzgfNQy9zBw==", + "dependencies": { + "is-stream": "1.1.0", + "restream": "1.2.0", + "wrote": "0.6.1" + } + }, + "node_modules/node-exiftool/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/node-fs": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/node-fs/-/node-fs-0.1.7.tgz", @@ -4818,6 +4847,11 @@ "node": ">=10" } }, + "node_modules/restream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/restream/-/restream-1.2.0.tgz", + "integrity": "sha512-RpnOjVCRrc/jO6j1U+E0X9mPjMUUitjEms/IaR18wb8AdPHg46UBmB2aEA6JKgpGbhOcHsDLyurmeZhd4kQXgg==" + }, "node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -5313,6 +5347,15 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/wrote": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/wrote/-/wrote-0.6.1.tgz", + "integrity": "sha512-5vDwEuRou4yCOzR08bUdY+n/HuOA21L7A4tVsm/kEu2R2APN8tYWvxQbM1hFJk49lR7x47KZMtCu2k8S9WISUA==", + "dependencies": { + "catchment": "1.0.0", + "makepromise": "1.0.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 884faed..b831358 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "i18next": "^23.2.6", "minimist": "^1.2.8", "moment": "^2.29.4", + "node-exiftool": "^2.3.0", "node-fs": "^0.1.7" }, "devDependencies": { diff --git a/setup.mjs b/setup.mjs index d10bca2..f01eac3 100644 --- a/setup.mjs +++ b/setup.mjs @@ -1,9 +1,8 @@ /** création de la config */ -import config from './configs.mjs' -import i18next from 'i18next' -let base_archive_folder = config.base_archive_folder +// import i18next from 'i18next' +import config_rangement from "./configs"; const { stdin, stdout } = process; @@ -20,7 +19,8 @@ function prompt(question) { async function main() { try { - const name = await prompt(i18next.t("home.title")) + // const name = await prompt(i18next.t("home.title")) + const name = await prompt(`squoi le dossier de base des archives? [${config_rangement.base_archive_folder}]`) // const age = await prompt("What's your age? "); // const email = await prompt("What's your email address? "); // const user = { name, age, email }; diff --git a/testFiles/2020-06-21T14:27:19 2sqdf45s5g456ghdf.jpg b/testFiles/2020-06-21T14:27:19 2020-0 image.jpg similarity index 100% rename from testFiles/2020-06-21T14:27:19 2sqdf45s5g456ghdf.jpg rename to testFiles/2020-06-21T14:27:19 2020-0 image.jpg diff --git a/testFiles/meme/2023-06-14T13.29.22 FyB8cZnWIAc21rw -- meme.jpg b/testFiles/meme/2023-06-14T13.29.22 FyB8cZnWIAc21rw -- meme.jpg new file mode 100644 index 0000000..ed40ffb Binary files /dev/null and b/testFiles/meme/2023-06-14T13.29.22 FyB8cZnWIAc21rw -- meme.jpg differ diff --git a/testFiles/meme/2023-06-26T18.53.59 FzjSx2YWcAEqsde -- meme.jpg b/testFiles/meme/2023-06-26T18.53.59 FzjSx2YWcAEqsde -- meme.jpg new file mode 100644 index 0000000..b150550 Binary files /dev/null and b/testFiles/meme/2023-06-26T18.53.59 FzjSx2YWcAEqsde -- meme.jpg differ diff --git a/testFiles/un_document.pdf b/testFiles/un_document.pdf deleted file mode 100644 index e69de29..0000000 diff --git a/testFiles/une_photo.jpg b/testFiles/une_photo.jpg deleted file mode 100644 index e69de29..0000000 diff --git a/testFiles/une_photo_pas_pareil.jpg b/testFiles/une_photo_pas_pareil.jpg deleted file mode 100644 index e69de29..0000000 diff --git a/testFiles/une_photo_pas_pareil.png b/testFiles/une_photo_pas_pareil.png deleted file mode 100644 index e69de29..0000000