convert to ts finder and config files
This commit is contained in:
parent
d9cbbb9076
commit
5d01542193
|
@ -1,45 +0,0 @@
|
||||||
import mainTemplates from './mainTemplates.mjs'
|
|
||||||
import constants from './folders.js'
|
|
||||||
import cv from './controlled_vocabulary.js'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* configuration générale à importer dans les utilitaires
|
|
||||||
*/
|
|
||||||
class config_rangement {
|
|
||||||
log_level = 'debug' // 'debug' | 'warn' |'info'
|
|
||||||
|
|
||||||
version = '1.0.0'
|
|
||||||
iso_date_format = 'yyyy-MM-DDTHH.mm.ss' // target format for dates in file names
|
|
||||||
tagSeparator = ' '
|
|
||||||
tagSectionSeparator = '--'
|
|
||||||
keepFreeText = true
|
|
||||||
keepTags = true
|
|
||||||
replaceUnderscoreWithSpaces = true
|
|
||||||
renameFolders = false
|
|
||||||
enableTestsLocally = false
|
|
||||||
reportStatistics = true
|
|
||||||
controlled_vocabulary = cv
|
|
||||||
constants = constants
|
|
||||||
base_archive_folder = constants.base_archive_folder
|
|
||||||
templates = mainTemplates;
|
|
||||||
statistics= {};
|
|
||||||
keepOriginalNameInRename=true;
|
|
||||||
log_actions=true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* override config if we want
|
|
||||||
* @param overridingConfig
|
|
||||||
*/
|
|
||||||
constructor (overridingConfig) {
|
|
||||||
let keys = Object.keys(overridingConfig)
|
|
||||||
let self = this;
|
|
||||||
keys.forEach(elem=>{
|
|
||||||
if(self[key]){
|
|
||||||
self[key] = elem
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rangement_instance = new config_rangement({});
|
|
||||||
export default rangement_instance
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
import mainTemplates from './mainTemplates'
|
||||||
|
import constants from './folders'
|
||||||
|
import cv from './controlled_vocabulary'
|
||||||
|
|
||||||
|
|
||||||
|
import log, {LogLevelDesc} from 'loglevel'
|
||||||
|
|
||||||
|
interface ConfigOverride {
|
||||||
|
iso_date_format?: string,
|
||||||
|
log_actions?: string,
|
||||||
|
log_level?: 'debug' | 'warn' | 'info',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* configuration générale à importer dans les utilitaires
|
||||||
|
*/
|
||||||
|
class config_rangement {
|
||||||
|
log_level: LogLevelDesc = 'debug' // 'debug' | 'warn' |'info'
|
||||||
|
|
||||||
|
version = '1.0.0'
|
||||||
|
iso_date_format = 'yyyy-MM-DDTHH.mm.ss' // target format for dates in file names
|
||||||
|
tagSeparator = ' '
|
||||||
|
tagSectionSeparator = '--'
|
||||||
|
keepFreeText = true
|
||||||
|
keepTags = true
|
||||||
|
replaceUnderscoreWithSpaces = true
|
||||||
|
renameFolders = false
|
||||||
|
enableTestsLocally = false
|
||||||
|
reportStatistics = true
|
||||||
|
controlled_vocabulary = cv
|
||||||
|
constants = constants
|
||||||
|
base_archive_folder = constants.base_archive_folder
|
||||||
|
templates = mainTemplates;
|
||||||
|
statistics = {};
|
||||||
|
keepOriginalNameInRename = true;
|
||||||
|
log_actions = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* override config if we want
|
||||||
|
* @param overridingConfig
|
||||||
|
*/
|
||||||
|
constructor(overridingConfig?: ConfigOverride) {
|
||||||
|
|
||||||
|
|
||||||
|
log.setLevel('info')
|
||||||
|
if (overridingConfig) {
|
||||||
|
log.debug('configuration is overrided')
|
||||||
|
let keys = Object.keys(overridingConfig)
|
||||||
|
let self: any = this;
|
||||||
|
keys.forEach((elem, index) => {
|
||||||
|
if (self[keys[index]]) {
|
||||||
|
self[keys[index]] = elem
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rangement_instance = new config_rangement({});
|
||||||
|
export default rangement_instance
|
|
@ -1,7 +1,22 @@
|
||||||
import constants from './folders.js'
|
import constants from './folders'
|
||||||
|
interface fileDetectionTemplate{
|
||||||
|
'example': string,
|
||||||
|
'pattern': any,
|
||||||
|
'auto_tag'?:string,
|
||||||
|
'auto_sort_folder'?:string
|
||||||
|
}
|
||||||
|
interface AssemblyOfFileDetection {
|
||||||
|
downloaded_pic: fileDetectionTemplate,
|
||||||
|
telegram_pic: fileDetectionTemplate,
|
||||||
|
open_camera_default: fileDetectionTemplate,
|
||||||
|
open_camera_custom: fileDetectionTemplate,
|
||||||
|
screenshot: fileDetectionTemplate,
|
||||||
|
gopro_front: fileDetectionTemplate,
|
||||||
|
gopro_back : fileDetectionTemplate
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
const setOfFileDetections : AssemblyOfFileDetection= {
|
||||||
'downloaded_pic': {
|
'downloaded_pic':{
|
||||||
'example': 'FyB8cZnWIAc21rw.jpg',
|
'example': 'FyB8cZnWIAc21rw.jpg',
|
||||||
'pattern': /^\-\w{15}\.jpg/,
|
'pattern': /^\-\w{15}\.jpg/,
|
||||||
'auto_tag':'',
|
'auto_tag':'',
|
||||||
|
@ -43,4 +58,6 @@ export default {
|
||||||
'auto_tag':'gopro-back gopro',
|
'auto_tag':'gopro-back gopro',
|
||||||
'auto_sort_folder': constants.gopro_capture_folder
|
'auto_sort_folder': constants.gopro_capture_folder
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default setOfFileDetections;
|
|
@ -4,7 +4,7 @@
|
||||||
# this should work for dev purposes
|
# this should work for dev purposes
|
||||||
# alias should be set in you bash aliases files:
|
# alias should be set in you bash aliases files:
|
||||||
#
|
#
|
||||||
# alias range="node /home/poule/encrypted/stockage-syncable/www/development/html/rangement/index.mjs --dry-run=true "
|
# alias range="ts-node /home/poule/encrypted/stockage-syncable/www/development/html/rangement/index.ts --dry-run=true "
|
||||||
# -------------------------
|
# -------------------------
|
||||||
range /home/poule/encrypted/stockage-syncable/www/development/html/rangement/testFiles/misnamed_file/2000-01-01T01.01.01\ FyB8cZnWIAc21rw\ --\ meme.jpg
|
range /home/poule/encrypted/stockage-syncable/www/development/html/rangement/testFiles/misnamed_file/2000-01-01T01.01.01\ FyB8cZnWIAc21rw\ --\ meme.jpg
|
||||||
|
|
||||||
|
|
232
index.mjs
232
index.mjs
|
@ -1,231 +1 @@
|
||||||
/**---------------------
|
console.log('hello index.mjs')
|
||||||
* @name tykayn Rangement
|
|
||||||
* @description Rangement sorts and rename files depending on their exif data
|
|
||||||
* @contact contact@cipherbliss.com
|
|
||||||
--------------------- */
|
|
||||||
/** ---------------------
|
|
||||||
libs
|
|
||||||
--------------------- */
|
|
||||||
import fs from 'node-fs'
|
|
||||||
import minimist from 'minimist'
|
|
||||||
import log from 'loglevel'
|
|
||||||
import path from 'node:path'
|
|
||||||
/** ---------------------
|
|
||||||
custom utilities and configuration
|
|
||||||
--------------------- */
|
|
||||||
import rangement_instance from './conf/configs.js'
|
|
||||||
import {
|
|
||||||
TestFindFormattedDate,
|
|
||||||
TestScreenShotIsFoundAndRenamed,
|
|
||||||
TestTagsAreDetectedInFileName
|
|
||||||
} from './utils/testFunctions.mjs'
|
|
||||||
import finder from './utils/finder.mjs'
|
|
||||||
|
|
||||||
let mini_arguments
|
|
||||||
|
|
||||||
log.setLevel(rangement_instance.log_level)
|
|
||||||
log.info(' ')
|
|
||||||
|
|
||||||
function parseArguments () {
|
|
||||||
mini_arguments = minimist(process.argv.slice(2))
|
|
||||||
log.debug('arguments', mini_arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* if there is no original file name free text into the new name, append it to the free text part
|
|
||||||
* @param originalFileName
|
|
||||||
* @param fileMixedNewName
|
|
||||||
*/
|
|
||||||
function addOriginalFileNameIfMissing (originalFileName, fileMixedNewName) {
|
|
||||||
|
|
||||||
if (!fileMixedNewName.includes(originalFileName)) {
|
|
||||||
let properties = finder.destructurateFileName(fileMixedNewName)
|
|
||||||
return properties.freeText + ' ' + originalFileName
|
|
||||||
} else {
|
|
||||||
return fileMixedNewName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renameFile (originalFileName, fileMixedNewName) {
|
|
||||||
if (rangement_instance.keepOriginalNameInRename) {
|
|
||||||
fileMixedNewName = addOriginalFileNameIfMissing(originalFileName, fileMixedNewName)
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.rename(originalFileName, fileMixedNewName, function (err) {
|
|
||||||
log.info('name changed', fileMixedNewName)
|
|
||||||
if (err) {
|
|
||||||
log.info('rename ERROR: ' + err)
|
|
||||||
} else {
|
|
||||||
// otherRenames.push(originalFileName)
|
|
||||||
otherRenames.push(fileMixedNewName)
|
|
||||||
|
|
||||||
finder.statistics['filesModified']++
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeFileNameFromProperties (fileProperties) {
|
|
||||||
|
|
||||||
let tagPlace = ''
|
|
||||||
log.info('fileProperties.tags', fileProperties.tags)
|
|
||||||
if (fileProperties.tags.length && rangement_instance.keepTags) {
|
|
||||||
tagPlace = ' ' + rangement_instance.tagSectionSeparator + ' ' + fileProperties.tags.join(rangement_instance.tagSeparator)
|
|
||||||
}
|
|
||||||
console.log('fileProperties.dateStampExif', fileProperties.dateStampExif)
|
|
||||||
let newName = ''
|
|
||||||
+ fileProperties.dateStampExif
|
|
||||||
+ ' '
|
|
||||||
+ (rangement_instance.keepFreeText ? fileProperties.freeText : '')
|
|
||||||
+ tagPlace
|
|
||||||
+ fileProperties.extension
|
|
||||||
|
|
||||||
if (rangement_instance.replaceUnderscoreWithSpaces) {
|
|
||||||
newName = newName.replace('_', ' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
newName = finder.cleanSpaces(newName)
|
|
||||||
|
|
||||||
return newName
|
|
||||||
}
|
|
||||||
|
|
||||||
let otherRenames = []
|
|
||||||
|
|
||||||
function shouldWeChangeName (structureForFile) {
|
|
||||||
log.info(' ______ shouldWeChangeName ', structureForFile.fileNameOriginal)
|
|
||||||
let newName = makeFileNameFromProperties(structureForFile)
|
|
||||||
log.debug('newName', newName)
|
|
||||||
if (structureForFile.fileNameOriginal !== newName && !otherRenames.includes(newName)) {
|
|
||||||
|
|
||||||
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')
|
|
||||||
finder.statistics['filesNotModified']++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.info(' rien à changer')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* guess file name on one file which is not a directory
|
|
||||||
* @param fullPath
|
|
||||||
*/
|
|
||||||
function guessFileNameOnOnefile (fullPath) {
|
|
||||||
|
|
||||||
log.info('go guess file name on file: ', fullPath)
|
|
||||||
fs.stat(fullPath, (err, stats) => {
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
log.error('échec fichier', err)
|
|
||||||
log.error('ce fichier n existe pas: ', fullPath)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
|
|
||||||
let structureForFile = finder.destructurateFileName(fullPath)
|
|
||||||
|
|
||||||
// examiner les infos exif de chaque fichier pour proposer un nouveau nom
|
|
||||||
if (!structureForFile.dateStampInFileNameOriginal) {
|
|
||||||
log.info(' le nom de fichier "' + structureForFile.freeText + '" ne contient pas de date formatée au début')
|
|
||||||
|
|
||||||
finder.findExifCreationDate(structureForFile.fullPath)
|
|
||||||
.then(data => {
|
|
||||||
log.info(' ... chercher la date de création : "' + structureForFile.freeText + '"')
|
|
||||||
let foundDate = finder.findEarliestDateInExifData(data)
|
|
||||||
|
|
||||||
log.info(' =>>>>>>> foundDate : ', foundDate)
|
|
||||||
if (foundDate) {
|
|
||||||
structureForFile.dateStampExif = foundDate
|
|
||||||
} else {
|
|
||||||
log.info('pas de date trouvée dans le nom')
|
|
||||||
}
|
|
||||||
shouldWeChangeName(structureForFile)
|
|
||||||
}
|
|
||||||
,
|
|
||||||
(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 () {
|
|
||||||
|
|
||||||
// parcourir les fichiers
|
|
||||||
log.debug('liste des fichiers', mini_arguments._)
|
|
||||||
let fileList = mini_arguments._
|
|
||||||
|
|
||||||
// test file exists
|
|
||||||
fileList.forEach(fullPath => {
|
|
||||||
log.debug('file list element: ', fullPath)
|
|
||||||
// parcourir les dossiers
|
|
||||||
isFolderOrFile(`${fullPath}`)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
log.info('expanded file list :', expandedFileList)
|
|
||||||
expandedFileList.forEach(filePath => guessFileNameOnOnefile(filePath))
|
|
||||||
|
|
||||||
if (rangement_instance.reportStatistics || mini_arguments.stats) {
|
|
||||||
finder.reportStatistics()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function readSubdirectories (baseDir) {
|
|
||||||
const newGlob = baseDir
|
|
||||||
fs.readdir(baseDir, (err, files) => {
|
|
||||||
if (err) throw err
|
|
||||||
|
|
||||||
console.log('readSubdirectories - files', files)
|
|
||||||
files.forEach((subDirOrFile) => {
|
|
||||||
const newFullPath = cwd + '/' + subDirOrFile
|
|
||||||
|
|
||||||
if (fs.existsSync(newFullPath)) {
|
|
||||||
const s = fs.statSync(newFullPath)
|
|
||||||
|
|
||||||
if (s.isFile()) {
|
|
||||||
expandedFileList.push(cwd + '/' + subDirOrFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFolderOrFile (fileName) {
|
|
||||||
const stat = fs.statSync(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(fileName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parseArguments()
|
|
||||||
|
|
||||||
guessFileNameOnAllFilesFromArguments()
|
|
||||||
|
|
||||||
// run tests
|
|
||||||
if (rangement_instance.enableTestsLocally) {
|
|
||||||
|
|
||||||
TestTagsAreDetectedInFileName()
|
|
||||||
TestFindFormattedDate()
|
|
||||||
TestScreenShotIsFoundAndRenamed()
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**---------------------
|
||||||
|
* @name tykayn Rangement
|
||||||
|
* @description Rangement sorts and rename files depending on their exif data
|
||||||
|
* @contact contact@cipherbliss.com
|
||||||
|
--------------------- */
|
||||||
|
/** ---------------------
|
||||||
|
libs
|
||||||
|
--------------------- */
|
||||||
|
import fs from 'node-fs'
|
||||||
|
import minimist from 'minimist'
|
||||||
|
import log from 'loglevel'
|
||||||
|
import * as path from 'node:path'
|
||||||
|
/** ---------------------
|
||||||
|
custom utilities and configuration
|
||||||
|
--------------------- */
|
||||||
|
import rangement_instance from './conf/configs'
|
||||||
|
import finder from './utils/finder'
|
||||||
|
|
||||||
|
let mini_arguments
|
||||||
|
|
||||||
|
log.setLevel(rangement_instance.log_level)
|
||||||
|
log.info(' ')
|
||||||
|
|
||||||
|
function parseArguments () {
|
||||||
|
mini_arguments = minimist(process.argv.slice(2))
|
||||||
|
log.debug('arguments', mini_arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
parseArguments()
|
||||||
|
|
||||||
|
|
||||||
|
console.log('hello ts', finder)
|
|
@ -7,6 +7,7 @@ module.exports = {
|
||||||
transform: {
|
transform: {
|
||||||
"^.+\\.jsx?$": "babel-jest",
|
"^.+\\.jsx?$": "babel-jest",
|
||||||
"^.+\\.mjs$": "babel-jest",
|
"^.+\\.mjs$": "babel-jest",
|
||||||
|
"^.+\\.ts$": "babel-jest",
|
||||||
},
|
},
|
||||||
testPathIgnorePatterns: ["<rootDir>/build/", "<rootDir>/node_modules/"],
|
testPathIgnorePatterns: ["<rootDir>/build/", "<rootDir>/node_modules/"],
|
||||||
moduleFileExtensions: ["js", "jsx", "mjs"]
|
moduleFileExtensions: ["js", "jsx", "mjs"]
|
||||||
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
/**---------------------
|
||||||
|
* @name tykayn Rangement
|
||||||
|
* @description Rangement sorts and rename files depending on their exif data
|
||||||
|
* @contact contact@cipherbliss.com
|
||||||
|
--------------------- */
|
||||||
|
/** ---------------------
|
||||||
|
libs
|
||||||
|
--------------------- */
|
||||||
|
import fs from 'node-fs'
|
||||||
|
import minimist from 'minimist'
|
||||||
|
import log from 'loglevel'
|
||||||
|
import path from 'node:path'
|
||||||
|
/** ---------------------
|
||||||
|
custom utilities and configuration
|
||||||
|
--------------------- */
|
||||||
|
import rangement_instance from './conf/configs.js'
|
||||||
|
import finder from './utils/finder.js'
|
||||||
|
|
||||||
|
let mini_arguments
|
||||||
|
|
||||||
|
log.setLevel(rangement_instance.log_level)
|
||||||
|
log.info(' ')
|
||||||
|
|
||||||
|
function parseArguments () {
|
||||||
|
mini_arguments = minimist(process.argv.slice(2))
|
||||||
|
log.debug('arguments', mini_arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if there is no original file name free text into the new name, append it to the free text part
|
||||||
|
* @param originalFileName
|
||||||
|
* @param fileMixedNewName
|
||||||
|
*/
|
||||||
|
function addOriginalFileNameIfMissing (originalFileName, fileMixedNewName) {
|
||||||
|
|
||||||
|
if (!fileMixedNewName.includes(originalFileName)) {
|
||||||
|
let properties = finder.destructurateFileName(fileMixedNewName)
|
||||||
|
return properties.freeText + ' ' + originalFileName
|
||||||
|
} else {
|
||||||
|
return fileMixedNewName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renameFile (originalFileName, fileMixedNewName) {
|
||||||
|
if (rangement_instance.keepOriginalNameInRename) {
|
||||||
|
fileMixedNewName = addOriginalFileNameIfMissing(originalFileName, fileMixedNewName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.rename(originalFileName, fileMixedNewName, function (err) {
|
||||||
|
log.info('name changed', fileMixedNewName)
|
||||||
|
if (err) {
|
||||||
|
log.info('rename ERROR: ' + err)
|
||||||
|
} else {
|
||||||
|
// otherRenames.push(originalFileName)
|
||||||
|
otherRenames.push(fileMixedNewName)
|
||||||
|
|
||||||
|
finder.statistics['filesModified']++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeFileNameFromProperties (fileProperties) {
|
||||||
|
|
||||||
|
let tagPlace = ''
|
||||||
|
log.info('fileProperties.tags', fileProperties.tags)
|
||||||
|
if (fileProperties.tags.length && rangement_instance.keepTags) {
|
||||||
|
tagPlace = ' ' + rangement_instance.tagSectionSeparator + ' ' + fileProperties.tags.join(rangement_instance.tagSeparator)
|
||||||
|
}
|
||||||
|
log.debug('fileProperties.dateStampExif', fileProperties.dateStampExif)
|
||||||
|
let newName = ''
|
||||||
|
+ fileProperties.dateStampExif
|
||||||
|
+ ' '
|
||||||
|
+ (rangement_instance.keepFreeText ? fileProperties.freeText : '')
|
||||||
|
+ tagPlace
|
||||||
|
+ fileProperties.extension
|
||||||
|
|
||||||
|
if (rangement_instance.replaceUnderscoreWithSpaces) {
|
||||||
|
newName = newName.replace('_', ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
newName = finder.cleanSpaces(newName)
|
||||||
|
|
||||||
|
return newName
|
||||||
|
}
|
||||||
|
|
||||||
|
let otherRenames = []
|
||||||
|
|
||||||
|
function shouldWeChangeName (structureForFile) {
|
||||||
|
log.info(' ______ shouldWeChangeName ', structureForFile.fileNameOriginal)
|
||||||
|
let newName = makeFileNameFromProperties(structureForFile)
|
||||||
|
log.debug('newName', newName)
|
||||||
|
if (structureForFile.fileNameOriginal !== newName && !otherRenames.includes(newName)) {
|
||||||
|
|
||||||
|
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')
|
||||||
|
finder.statistics['filesNotModified']++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.info(' rien à changer')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* guess file name on one file which is not a directory
|
||||||
|
* @param fullPath
|
||||||
|
*/
|
||||||
|
function guessFileNameOnOnefile (fullPath) {
|
||||||
|
|
||||||
|
log.info('go guess file name on file: ', fullPath)
|
||||||
|
fs.stat(fullPath, (err, stats) => {
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
log.error('échec fichier', err)
|
||||||
|
log.error('ce fichier n existe pas: ', fullPath)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
|
||||||
|
let structureForFile = finder.destructurateFileName(fullPath)
|
||||||
|
|
||||||
|
// examiner les infos exif de chaque fichier pour proposer un nouveau nom
|
||||||
|
if (!structureForFile.dateStampInFileNameOriginal) {
|
||||||
|
log.info(' le nom de fichier "' + structureForFile.freeText + '" ne contient pas de date formatée au début')
|
||||||
|
|
||||||
|
finder.findExifCreationDate(structureForFile.fullPath)
|
||||||
|
.then(data => {
|
||||||
|
log.info(' ... chercher la date de création : "' + structureForFile.freeText + '"')
|
||||||
|
let foundDate = finder.findEarliestDateInExifData(data)
|
||||||
|
|
||||||
|
log.info(' =>>>>>>> foundDate : ', foundDate)
|
||||||
|
if (foundDate) {
|
||||||
|
structureForFile.dateStampExif = foundDate
|
||||||
|
} else {
|
||||||
|
log.info('pas de date trouvée dans le nom')
|
||||||
|
}
|
||||||
|
shouldWeChangeName(structureForFile)
|
||||||
|
}
|
||||||
|
,
|
||||||
|
(error) => {
|
||||||
|
log.warn('/////////// Error in reading exif of file: ' + error.message)
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let expandedFileList = []
|
||||||
|
let cwd = path.dirname(process.cwd()) + '/' + path.basename(process.cwd())
|
||||||
|
log.debug('cwd', cwd)
|
||||||
|
|
||||||
|
function guessFileNameOnAllFilesFromArguments () {
|
||||||
|
|
||||||
|
// parcourir les fichiers
|
||||||
|
log.debug('liste des fichiers', mini_arguments._)
|
||||||
|
let fileList = mini_arguments._
|
||||||
|
|
||||||
|
// test file exists
|
||||||
|
fileList.forEach(fullPath => {
|
||||||
|
log.debug('file list element: ', fullPath)
|
||||||
|
// parcourir les dossiers
|
||||||
|
isFolderOrFile(`${fullPath}`)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
log.info('expanded file list :', expandedFileList)
|
||||||
|
expandedFileList.forEach(filePath => guessFileNameOnOnefile(filePath))
|
||||||
|
|
||||||
|
if (rangement_instance.reportStatistics || mini_arguments.stats) {
|
||||||
|
finder.reportStatistics()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function readSubdirectories (baseDir) {
|
||||||
|
const newGlob = baseDir
|
||||||
|
fs.readdir(baseDir, (err, files) => {
|
||||||
|
if (err) throw err
|
||||||
|
|
||||||
|
log.debug('readSubdirectories - files', files)
|
||||||
|
files.forEach((subDirOrFile) => {
|
||||||
|
const newFullPath = cwd + '/' + subDirOrFile
|
||||||
|
|
||||||
|
if (fs.existsSync(newFullPath)) {
|
||||||
|
const s = fs.statSync(newFullPath)
|
||||||
|
|
||||||
|
if (s.isFile()) {
|
||||||
|
expandedFileList.push(cwd + '/' + subDirOrFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFolderOrFile (fileName) {
|
||||||
|
const stat = fs.statSync(fileName)
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
let fileList = readSubdirectories(fileName)
|
||||||
|
log.debug('fileList in directory ', fileName, '\n', fileList)
|
||||||
|
if (fileList) {
|
||||||
|
expandedFileList.push(...fileList)
|
||||||
|
}
|
||||||
|
} else if (stat.isFile()) {
|
||||||
|
expandedFileList.push(fileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parseArguments()
|
||||||
|
|
||||||
|
guessFileNameOnAllFilesFromArguments()
|
|
@ -21,11 +21,14 @@
|
||||||
"@babel/preset-env": "^7.22.5",
|
"@babel/preset-env": "^7.22.5",
|
||||||
"@babel/preset-typescript": "^7.22.5",
|
"@babel/preset-typescript": "^7.22.5",
|
||||||
"@jest/globals": "^29.5.0",
|
"@jest/globals": "^29.5.0",
|
||||||
|
"@types/node": "^20.4.2",
|
||||||
"babel-jest": "^29.5.0",
|
"babel-jest": "^29.5.0",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"loglevel": "^1.8.1",
|
"loglevel": "^1.8.1",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"ts-node": "^10.9.1"
|
"ts-node": "^10.9.1",
|
||||||
|
"tslib": "^2.6.0",
|
||||||
|
"typescript": "^5.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
|
@ -2365,9 +2368,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.3.3",
|
"version": "20.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz",
|
||||||
"integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==",
|
"integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/prettier": {
|
"node_modules/@types/prettier": {
|
||||||
|
@ -5150,6 +5153,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/type-detect": {
|
"node_modules/type-detect": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||||
|
@ -5176,7 +5185,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
|
||||||
"integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
|
"integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|
15
package.json
15
package.json
|
@ -2,12 +2,12 @@
|
||||||
"name": "rangement",
|
"name": "rangement",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "sorting utilities and file tagging using PIM methodology of Karl Voit",
|
"description": "sorting utilities and file tagging using PIM methodology of Karl Voit",
|
||||||
"main": "index.mjs",
|
"main": "index.ts",
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node index.mjs",
|
"start": "ts-node index.ts",
|
||||||
"devine": "node index.mjs",
|
"devine": "ts-node index.ts",
|
||||||
"test": "jest --coverage --watch"
|
"test": "jest --coverage --watch",
|
||||||
|
"tests": "jest --coverage --watch"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -37,10 +37,13 @@
|
||||||
"@babel/preset-env": "^7.22.5",
|
"@babel/preset-env": "^7.22.5",
|
||||||
"@babel/preset-typescript": "^7.22.5",
|
"@babel/preset-typescript": "^7.22.5",
|
||||||
"@jest/globals": "^29.5.0",
|
"@jest/globals": "^29.5.0",
|
||||||
|
"@types/node": "^20.4.2",
|
||||||
"babel-jest": "^29.5.0",
|
"babel-jest": "^29.5.0",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"loglevel": "^1.8.1",
|
"loglevel": "^1.8.1",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"ts-node": "^10.9.1"
|
"ts-node": "^10.9.1",
|
||||||
|
"tslib": "^2.6.0",
|
||||||
|
"typescript": "^5.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import finder from '../utils/finder.mjs'
|
import finder from '../utils/finder.js'
|
||||||
import { makeFileNameFromProperties } from '../index'
|
import { makeFileNameFromProperties } from '../old_index'
|
||||||
import { expect } from '@jest/globals'
|
import { expect } from '@jest/globals'
|
||||||
|
|
||||||
describe('detection in file name', () => {
|
describe('detection in file name', () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import finder from "../utils/finder.mjs";
|
import finder from "../utils/finder.js";
|
||||||
import { Jest as mockUpload } from '@jest/environment'
|
import { Jest as mockUpload } from '@jest/environment'
|
||||||
|
|
||||||
xdescribe('rangement file detection', () => {
|
xdescribe('rangement file detection', () => {
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
// This is an alias to @tsconfig/node16: https://github.com/tsconfig/bases
|
||||||
|
"extends": "ts-node/node16/tsconfig.json",
|
||||||
|
|
||||||
|
// Most ts-node options can be specified here using their programmatic names.
|
||||||
|
"ts-node": {
|
||||||
|
// It is faster to skip typechecking.
|
||||||
|
// Remove if you want ts-node to do typechecking.
|
||||||
|
"transpileOnly": true,
|
||||||
|
|
||||||
|
"files": true,
|
||||||
|
|
||||||
|
"compilerOptions": {
|
||||||
|
// compilerOptions specified here will override those declared below,
|
||||||
|
// but *only* in ts-node. Useful if you want ts-node and tsc to use
|
||||||
|
// different options with a single tsconfig.json.
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compilerOptions": {
|
||||||
|
// typescript options here
|
||||||
|
}
|
||||||
|
}
|
311
utils/finder.mjs
311
utils/finder.mjs
|
@ -1,311 +0,0 @@
|
||||||
/**
|
|
||||||
* la classe qui repère des patterns
|
|
||||||
*/
|
|
||||||
import exifr from 'exifr'
|
|
||||||
import moment from 'moment'
|
|
||||||
import log from 'loglevel'
|
|
||||||
import rangement_instance from '../conf/configs.js'
|
|
||||||
|
|
||||||
log.setLevel(rangement_instance.log_level)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* finds patterns for file name
|
|
||||||
*/
|
|
||||||
export default class finder {
|
|
||||||
|
|
||||||
static statistics = {
|
|
||||||
filesModified: 0,
|
|
||||||
filesNotModified: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
static reportStatistics () {
|
|
||||||
log.info('\n --------- statistics',
|
|
||||||
this.statistics)
|
|
||||||
}
|
|
||||||
|
|
||||||
static findScreenshot (inputString) {
|
|
||||||
return inputString.match(/screenshot/i) || inputString.match(/capture d'écran/i)
|
|
||||||
}
|
|
||||||
|
|
||||||
static findFormattedDate (filepath) {
|
|
||||||
let match = filepath.match(/\d{4}-\d{2}-\d{2}T\d{2}\.\d{2}\.\d{2}/ig)
|
|
||||||
log.debug(' finder - match findFormattedDate', match)
|
|
||||||
let result = ''
|
|
||||||
if (match && match[0]) {
|
|
||||||
result = match[0]
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
static findFileExtension (inputString) {
|
|
||||||
let result = inputString.match(/\.\w{3,4}$/i)
|
|
||||||
if (result.length) {
|
|
||||||
return result[0]
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* find the section of file name which contains the free text to describe the picture
|
|
||||||
* @param fileName
|
|
||||||
* @returns {*|string}
|
|
||||||
*/
|
|
||||||
static findFileNameFreeTextPart (fileName) {
|
|
||||||
fileName = fileName.replace(this.findFileExtension(fileName), '')
|
|
||||||
let boom = fileName.split(rangement_instance.tagSectionSeparator)
|
|
||||||
if (boom.length) {
|
|
||||||
let freeTextPart = boom[0].trim()
|
|
||||||
// remove date
|
|
||||||
let foundDate = this.findFormattedDate(freeTextPart)
|
|
||||||
if (foundDate) {
|
|
||||||
freeTextPart = freeTextPart.replace(foundDate, '').trim()
|
|
||||||
}
|
|
||||||
log.debug(' finder - freeTextPart:', freeTextPart)
|
|
||||||
return freeTextPart
|
|
||||||
}
|
|
||||||
return fileName.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* find an array of tags
|
|
||||||
* @param inputString
|
|
||||||
* @returns {[]}
|
|
||||||
*/
|
|
||||||
static findTagSectionInString (inputString) {
|
|
||||||
let listOfTags = []
|
|
||||||
// remove extension
|
|
||||||
let extensionFile = finder.findFileExtension(inputString)
|
|
||||||
|
|
||||||
if (extensionFile) {
|
|
||||||
|
|
||||||
inputString = inputString.replace(extensionFile, '')
|
|
||||||
} else {
|
|
||||||
log.debug(' finder - no extensionFile', extensionFile, inputString)
|
|
||||||
extensionFile = ''
|
|
||||||
}
|
|
||||||
inputString = inputString.replace(extensionFile, '')
|
|
||||||
|
|
||||||
log.debug(' finder - extensionFile', extensionFile)
|
|
||||||
if (inputString.includes(rangement_instance.tagSectionSeparator)) {
|
|
||||||
log.debug(' finder - inputString', inputString)
|
|
||||||
if (inputString.length) {
|
|
||||||
|
|
||||||
let boom = inputString.split(rangement_instance.tagSectionSeparator)
|
|
||||||
log.debug(' finder - boom', boom)
|
|
||||||
if (boom.length) {
|
|
||||||
let fileSectionsName = boom.splice(rangement_instance.tagSeparator)
|
|
||||||
listOfTags = [...fileSectionsName[1].trim().split(rangement_instance.tagSeparator)]
|
|
||||||
log.debug(' finder - listOfTags', listOfTags)
|
|
||||||
} else {
|
|
||||||
log.debug(' finder - no boom', boom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return listOfTags
|
|
||||||
}
|
|
||||||
|
|
||||||
static removeTagInProperties (properties, tagName) {
|
|
||||||
|
|
||||||
let foundTagNameIndex = properties.tags.indexOf(tagName)
|
|
||||||
if (foundTagNameIndex) {
|
|
||||||
delete properties.tags[foundTagNameIndex]
|
|
||||||
}
|
|
||||||
return properties
|
|
||||||
}
|
|
||||||
|
|
||||||
static cleanSpaces (inputString) {
|
|
||||||
return inputString.trim().replace(/ *g/, ' ').replace(/ /, ' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
static searchAndReplaceInFileName (searchString, replaceString, fileName) {
|
|
||||||
return this.cleanSpaces(fileName.replace(searchString, replaceString))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* search screenshot clues and rename
|
|
||||||
*/
|
|
||||||
static searchAndRenameScreenshots (fileName) {
|
|
||||||
if (finder.findScreenshot(fileName)) {
|
|
||||||
let tags = this.findTagSectionInString(fileName)
|
|
||||||
log.debug(' finder - tags', tags)
|
|
||||||
if (!tags.includes('screenshot')) {
|
|
||||||
|
|
||||||
fileName = this.addTagInFileName('screenshot', fileName)
|
|
||||||
fileName = this.searchAndReplaceInFileName('Screenshot', '', fileName)
|
|
||||||
log.debug(' finder - screenShotMockFileName:', fileName)
|
|
||||||
return this.cleanSpaces(fileName)
|
|
||||||
}
|
|
||||||
log.debug(' finder - is a screenshot, remove screenshot in name, and add tag screenshot')
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* determines if we must add or remove new tags
|
|
||||||
* @param tagCommand
|
|
||||||
* @returns {{tagsToAdd: [], tagCommand: *, tagsToRemove: []}}
|
|
||||||
*/
|
|
||||||
static addOrRemoveTagsParsing (tagCommand) {
|
|
||||||
let tagsToAdd = []
|
|
||||||
let tagsToRemove = []
|
|
||||||
|
|
||||||
// split all tags
|
|
||||||
let listOfTags = tagCommand.split(' ')
|
|
||||||
listOfTags.forEach(elem => {
|
|
||||||
// remove when a minus is present*
|
|
||||||
if (elem.indexOf('-')) {
|
|
||||||
tagsToRemove.push(elem)
|
|
||||||
} else {
|
|
||||||
// add tag otherwise
|
|
||||||
tagsToAdd.push(elem)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return { tagCommand, tagsToAdd, tagsToRemove }
|
|
||||||
}
|
|
||||||
|
|
||||||
static applyTagChangesOnProperties(tagChange, properties){
|
|
||||||
|
|
||||||
properties.tags = [...properties.tags, tagChange.tagsToAdd]
|
|
||||||
properties.tags.filter(elem=>{
|
|
||||||
return tagChange.tagsToRemove.includes(elem)
|
|
||||||
})
|
|
||||||
return properties
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* add a tag and gives new filename with extension
|
|
||||||
* @param tagName
|
|
||||||
* @param fileName
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
static addTagInFileName (tagName, fileName) {
|
|
||||||
|
|
||||||
let tags = this.findTagSectionInString(fileName)
|
|
||||||
let firstPart = this.findFileNameFreeTextPart(fileName)
|
|
||||||
|
|
||||||
tags.push(tagName)
|
|
||||||
let uniqueArray = [...new Set(tags)]
|
|
||||||
|
|
||||||
let newFileName = firstPart + ' ' + rangement_instance.tagSectionSeparator + ' ' + uniqueArray.join(rangement_instance.tagSeparator)
|
|
||||||
newFileName = newFileName.replace(/ {*}/, '') + this.findFileExtension(fileName)
|
|
||||||
return this.cleanSpaces(newFileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* convertit un nom de fichier en une structure décrivant plusieurs parties correspondant au pattern d'archivage
|
|
||||||
* @param fullPath
|
|
||||||
* @returns {{extension: *, dateStamp: string, freeText: (*|string), tags: *[]}}
|
|
||||||
*/
|
|
||||||
static destructurateFileName (fullPath) {
|
|
||||||
let [folderPath, fileNameOriginal] = this.findFolderPath(fullPath)
|
|
||||||
let dateStampInFileNameOriginal = this.findFormattedDate(fileNameOriginal)
|
|
||||||
return {
|
|
||||||
fullPath,
|
|
||||||
folderPath,
|
|
||||||
fileNameOriginal,
|
|
||||||
dateStampInFileNameOriginal,
|
|
||||||
dateStampExif: '',
|
|
||||||
freeText: this.findFileNameFreeTextPart(fileNameOriginal),
|
|
||||||
tags: this.findTagSectionInString(fileNameOriginal),
|
|
||||||
extension: this.findFileExtension(fileNameOriginal),
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* finds the earliest part in several exif date info
|
|
||||||
* @param exifData
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static findEarliestDateInExifData (exifData) {
|
|
||||||
log.debug(' finder - findEarliestDateInExifData')
|
|
||||||
if (exifData) {
|
|
||||||
|
|
||||||
let moments = []
|
|
||||||
|
|
||||||
log.debug(' finder - exif data : ', exifData) // Do something with your data!
|
|
||||||
if (exifData.DateTimeOriginal) {
|
|
||||||
log.debug(' finder - image créée le : DateTimeOriginal : ', exifData.DateTimeOriginal) // Do something with your data!
|
|
||||||
moments.push(exifData.DateTimeOriginal)
|
|
||||||
}
|
|
||||||
if (exifData.ModificationDateTime) {
|
|
||||||
log.debug(' finder - image créée le : ModificationDateTime : ', exifData.ModificationDateTime) // Do something with your data!
|
|
||||||
moments.push(exifData.ModificationDateTime)
|
|
||||||
}
|
|
||||||
if (exifData.ModifyDate) {
|
|
||||||
log.debug(' finder - image créée le : ModifyDate : ', exifData.ModifyDate) // Do something with your data!
|
|
||||||
moments.push(exifData.ModifyDate)
|
|
||||||
}
|
|
||||||
if (exifData.FileAccessDateTime) {
|
|
||||||
moments.push(exifData.FileAccessDateTime)
|
|
||||||
}
|
|
||||||
if (exifData.FileInodeChangeDateTime) {
|
|
||||||
moments.push(exifData.FileInodeChangeDateTime)
|
|
||||||
}
|
|
||||||
if (exifData.FileModificationDateTime) {
|
|
||||||
log.debug(' finder - image créée le : FileModificationDateTime : ', exifData.FileModificationDateTime) // Do something with your data!
|
|
||||||
moments.push(exifData.FileModificationDateTime)
|
|
||||||
}
|
|
||||||
if (exifData.CreateDate) {
|
|
||||||
log.debug(' finder - image créée le : CreateDate : ', exifData.CreateDate) // Do something with your data!
|
|
||||||
moments.push(exifData.CreateDate)
|
|
||||||
}
|
|
||||||
|
|
||||||
moments = moments.map(d => {
|
|
||||||
let newdate = moment(d)
|
|
||||||
return newdate
|
|
||||||
})
|
|
||||||
let minDate = moment.min(moments)
|
|
||||||
|
|
||||||
log.debug(' finder - minDate :::::::::', minDate)
|
|
||||||
log.info(' finder - minDate :::::::::', minDate.format(rangement_instance.iso_date_format))
|
|
||||||
|
|
||||||
return minDate.format(rangement_instance.iso_date_format)
|
|
||||||
} else {
|
|
||||||
log.debug(' finder - /!\\ pas de exif data')
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static appendFileName (fileProperties, newText) {
|
|
||||||
fileProperties.freeText = finder.cleanSpaces(fileProperties.freeText + ' ' + newText)
|
|
||||||
return fileProperties
|
|
||||||
}
|
|
||||||
|
|
||||||
static prependFileName (fileProperties, newText) {
|
|
||||||
fileProperties.freeText = finder.cleanSpaces(newText + ' ' + fileProperties.freeText)
|
|
||||||
return fileProperties
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* examine plusieurs propriétés exif de date et retourne la plus ancienne
|
|
||||||
* @param filepath
|
|
||||||
*/
|
|
||||||
static async findExifCreationDate (filepath) {
|
|
||||||
|
|
||||||
log.debug(' finder - filepath', filepath)
|
|
||||||
let dateAlreadyInFileName = finder.findFormattedDate(filepath)
|
|
||||||
if (dateAlreadyInFileName) {
|
|
||||||
|
|
||||||
log.debug(' finder - ------ dateAlreadyInFileName', dateAlreadyInFileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return await exifr.parse(filepath)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static findFolderPath (filePath) {
|
|
||||||
let folders = filePath.split('/')
|
|
||||||
let fileName = folders.pop()
|
|
||||||
folders = filePath.replace(fileName, '')
|
|
||||||
|
|
||||||
log.debug(' finder - \n - folders', folders)
|
|
||||||
log.debug(' finder - - fileName', fileName, '\n')
|
|
||||||
return [folders, fileName]
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,322 @@
|
||||||
|
/**
|
||||||
|
* la classe qui repère des patterns
|
||||||
|
*/
|
||||||
|
import exifr from 'exifr'
|
||||||
|
import moment from 'moment'
|
||||||
|
import log from 'loglevel'
|
||||||
|
import rangement_instance from '../conf/configs'
|
||||||
|
|
||||||
|
log.setLevel(rangement_instance.log_level)
|
||||||
|
|
||||||
|
interface fileDestructuration {
|
||||||
|
fullPath: string
|
||||||
|
folderPath: string
|
||||||
|
fileNameOriginal: string
|
||||||
|
dateStampInFileNameOriginal: string
|
||||||
|
dateStampExif: string,
|
||||||
|
freeText: string,
|
||||||
|
tags: string[],
|
||||||
|
extension: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* finds patterns for file name
|
||||||
|
*/
|
||||||
|
export default class finder {
|
||||||
|
|
||||||
|
static statistics = {
|
||||||
|
filesModified: 0,
|
||||||
|
filesNotModified: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
static reportStatistics() {
|
||||||
|
log.info('\n --------- statistics',
|
||||||
|
this.statistics)
|
||||||
|
}
|
||||||
|
|
||||||
|
static findScreenshot(inputString: string) {
|
||||||
|
return inputString.match(/screenshot/i) || inputString.match(/capture d'écran/i)
|
||||||
|
}
|
||||||
|
|
||||||
|
static findFormattedDate(filepath: string) {
|
||||||
|
let match = filepath.match(/\d{4}-\d{2}-\d{2}T\d{2}\.\d{2}\.\d{2}/ig)
|
||||||
|
log.debug(' finder - match findFormattedDate', match)
|
||||||
|
let result = ''
|
||||||
|
if (match && match[0]) {
|
||||||
|
result = match[0]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
static findFileExtension(inputString: string): string {
|
||||||
|
let result = inputString.match(/\.\w{3,4}$/i)
|
||||||
|
if (result && result.length) {
|
||||||
|
return result[0]
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find the section of file name which contains the free text to describe the picture
|
||||||
|
* @param fileName
|
||||||
|
* @returns {*|string}
|
||||||
|
*/
|
||||||
|
static findFileNameFreeTextPart(fileName: string) {
|
||||||
|
fileName = fileName.replace(this.findFileExtension(fileName), '')
|
||||||
|
let boom = fileName.split(rangement_instance.tagSectionSeparator)
|
||||||
|
if (boom.length) {
|
||||||
|
let freeTextPart = boom[0].trim()
|
||||||
|
// remove date
|
||||||
|
let foundDate = this.findFormattedDate(freeTextPart)
|
||||||
|
if (foundDate) {
|
||||||
|
freeTextPart = freeTextPart.replace(foundDate, '').trim()
|
||||||
|
}
|
||||||
|
log.debug(' finder - freeTextPart:', freeTextPart)
|
||||||
|
return freeTextPart
|
||||||
|
}
|
||||||
|
return fileName.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find an array of tags
|
||||||
|
* @param inputString
|
||||||
|
* @returns {[]}
|
||||||
|
*/
|
||||||
|
static findTagSectionInString(inputString: string) {
|
||||||
|
let listOfTags: Array<string> = []
|
||||||
|
// remove extension
|
||||||
|
let extensionFile = finder.findFileExtension(inputString)
|
||||||
|
|
||||||
|
if (extensionFile) {
|
||||||
|
|
||||||
|
inputString = inputString.replace(extensionFile, '')
|
||||||
|
} else {
|
||||||
|
log.debug(' finder - no extensionFile', extensionFile, inputString)
|
||||||
|
extensionFile = ''
|
||||||
|
}
|
||||||
|
inputString = inputString.replace(extensionFile, '')
|
||||||
|
|
||||||
|
log.debug(' finder - extensionFile', extensionFile)
|
||||||
|
if (inputString.includes(rangement_instance.tagSectionSeparator)) {
|
||||||
|
log.debug(' finder - inputString', inputString)
|
||||||
|
if (inputString.length) {
|
||||||
|
|
||||||
|
let boom = inputString.split(rangement_instance.tagSectionSeparator)
|
||||||
|
log.debug(' finder - boom', boom)
|
||||||
|
if (boom.length) {
|
||||||
|
listOfTags = [...boom[1].trim().split(rangement_instance.tagSeparator)]
|
||||||
|
log.debug(' finder - listOfTags', listOfTags)
|
||||||
|
} else {
|
||||||
|
log.debug(' finder - no boom', boom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return listOfTags
|
||||||
|
}
|
||||||
|
|
||||||
|
static removeTagInProperties(properties: fileDestructuration, tagName: string) {
|
||||||
|
|
||||||
|
let foundTagNameIndex = properties.tags.indexOf(tagName)
|
||||||
|
if (foundTagNameIndex) {
|
||||||
|
delete properties.tags[foundTagNameIndex]
|
||||||
|
}
|
||||||
|
return properties
|
||||||
|
}
|
||||||
|
|
||||||
|
static cleanSpaces(inputString: string) {
|
||||||
|
return inputString.trim().replace(/ *g/, ' ').replace(/ /, ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
static searchAndReplaceInFileName(searchString: string, replaceString: string, fileName: string): string {
|
||||||
|
return this.cleanSpaces(fileName.replace(searchString, replaceString))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* search screenshot clues and rename
|
||||||
|
*/
|
||||||
|
static searchAndRenameScreenshots(fileName: string) {
|
||||||
|
if (finder.findScreenshot(fileName)) {
|
||||||
|
let tags = this.findTagSectionInString(fileName)
|
||||||
|
log.debug(' finder - tags', tags)
|
||||||
|
if (!tags.includes('screenshot')) {
|
||||||
|
|
||||||
|
fileName = this.addTagInFileName('screenshot', fileName)
|
||||||
|
fileName = this.searchAndReplaceInFileName('Screenshot', '', fileName)
|
||||||
|
log.debug(' finder - screenShotMockFileName:', fileName)
|
||||||
|
return this.cleanSpaces(fileName)
|
||||||
|
}
|
||||||
|
log.debug(' finder - is a screenshot, remove screenshot in name, and add tag screenshot')
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* determines if we must add or remove new tags
|
||||||
|
* @param tagCommand
|
||||||
|
* @returns {{tagsToAdd: [], tagCommand: *, tagsToRemove: []}}
|
||||||
|
*/
|
||||||
|
static addOrRemoveTagsParsing(tagCommand: string): any {
|
||||||
|
let tagsToAdd: Array<any> = []
|
||||||
|
let tagsToRemove: Array<any> = []
|
||||||
|
|
||||||
|
// split all tags
|
||||||
|
let listOfTags = tagCommand.split(' ')
|
||||||
|
listOfTags.forEach(elem => {
|
||||||
|
// remove when a minus is present*
|
||||||
|
if (elem.indexOf('-')) {
|
||||||
|
tagsToRemove.push(elem)
|
||||||
|
} else {
|
||||||
|
// add tag otherwise
|
||||||
|
tagsToAdd.push(elem)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {tagCommand, tagsToAdd, tagsToRemove}
|
||||||
|
}
|
||||||
|
|
||||||
|
static applyTagChangesOnProperties(tagChange: any, properties: fileDestructuration) {
|
||||||
|
|
||||||
|
|
||||||
|
properties.tags = [...properties.tags, tagChange.tagsToAdd]
|
||||||
|
properties.tags.filter(elem => {
|
||||||
|
return tagChange.tagsToRemove.includes(elem)
|
||||||
|
})
|
||||||
|
return properties
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add a tag and gives new filename with extension
|
||||||
|
* @param tagName
|
||||||
|
* @param fileName
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
static addTagInFileName(tagName:string, fileName:string) {
|
||||||
|
|
||||||
|
let tags:any = this.findTagSectionInString(fileName)
|
||||||
|
let firstPart = this.findFileNameFreeTextPart(fileName)
|
||||||
|
|
||||||
|
tags.push(tagName)
|
||||||
|
// @ts-ignore
|
||||||
|
let uniqueArray = [...new Set(tags)]
|
||||||
|
|
||||||
|
let newFileName = firstPart + ' ' + rangement_instance.tagSectionSeparator + ' ' + uniqueArray.join(rangement_instance.tagSeparator)
|
||||||
|
newFileName = newFileName.replace(/ {*}/, '') + this.findFileExtension(fileName)
|
||||||
|
return this.cleanSpaces(newFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convertit un nom de fichier en une structure décrivant plusieurs parties correspondant au pattern d'archivage
|
||||||
|
* @param fullPath
|
||||||
|
* @returns {{extension: *, dateStamp: string, freeText: (*|string), tags: *[]}}
|
||||||
|
*/
|
||||||
|
static destructurateFileName(fullPath:string) {
|
||||||
|
let [folderPath, fileNameOriginal] = this.findFolderPath(fullPath)
|
||||||
|
let dateStampInFileNameOriginal = this.findFormattedDate(fileNameOriginal)
|
||||||
|
return {
|
||||||
|
fullPath,
|
||||||
|
folderPath,
|
||||||
|
fileNameOriginal,
|
||||||
|
dateStampInFileNameOriginal,
|
||||||
|
dateStampExif: '',
|
||||||
|
freeText: this.findFileNameFreeTextPart(fileNameOriginal),
|
||||||
|
tags: this.findTagSectionInString(fileNameOriginal),
|
||||||
|
extension: this.findFileExtension(fileNameOriginal),
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* finds the earliest part in several exif date info
|
||||||
|
* @param exifData
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static findEarliestDateInExifData(exifData:any) {
|
||||||
|
log.debug(' finder - findEarliestDateInExifData')
|
||||||
|
if (exifData) {
|
||||||
|
|
||||||
|
let moments = []
|
||||||
|
|
||||||
|
log.debug(' finder - exif data : ', exifData) // Do something with your data!
|
||||||
|
if (exifData.DateTimeOriginal) {
|
||||||
|
log.debug(' finder - image créée le : DateTimeOriginal : ', exifData.DateTimeOriginal) // Do something with your data!
|
||||||
|
moments.push(exifData.DateTimeOriginal)
|
||||||
|
}
|
||||||
|
if (exifData.ModificationDateTime) {
|
||||||
|
log.debug(' finder - image créée le : ModificationDateTime : ', exifData.ModificationDateTime) // Do something with your data!
|
||||||
|
moments.push(exifData.ModificationDateTime)
|
||||||
|
}
|
||||||
|
if (exifData.ModifyDate) {
|
||||||
|
log.debug(' finder - image créée le : ModifyDate : ', exifData.ModifyDate) // Do something with your data!
|
||||||
|
moments.push(exifData.ModifyDate)
|
||||||
|
}
|
||||||
|
if (exifData.FileAccessDateTime) {
|
||||||
|
moments.push(exifData.FileAccessDateTime)
|
||||||
|
}
|
||||||
|
if (exifData.FileInodeChangeDateTime) {
|
||||||
|
moments.push(exifData.FileInodeChangeDateTime)
|
||||||
|
}
|
||||||
|
if (exifData.FileModificationDateTime) {
|
||||||
|
log.debug(' finder - image créée le : FileModificationDateTime : ', exifData.FileModificationDateTime) // Do something with your data!
|
||||||
|
moments.push(exifData.FileModificationDateTime)
|
||||||
|
}
|
||||||
|
if (exifData.CreateDate) {
|
||||||
|
log.debug(' finder - image créée le : CreateDate : ', exifData.CreateDate) // Do something with your data!
|
||||||
|
moments.push(exifData.CreateDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
moments = moments.map(d => {
|
||||||
|
let newdate = moment(d)
|
||||||
|
return newdate
|
||||||
|
})
|
||||||
|
let minDate = moment.min(moments)
|
||||||
|
|
||||||
|
log.debug(' finder - minDate :::::::::', minDate)
|
||||||
|
log.info(' finder - minDate :::::::::', minDate.format(rangement_instance.iso_date_format))
|
||||||
|
|
||||||
|
return minDate.format(rangement_instance.iso_date_format)
|
||||||
|
} else {
|
||||||
|
log.debug(' finder - /!\\ pas de exif data')
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static appendFileName(fileProperties:fileDestructuration, newText:string) {
|
||||||
|
fileProperties.freeText = finder.cleanSpaces(fileProperties.freeText + ' ' + newText)
|
||||||
|
return fileProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
static prependFileName(fileProperties:fileDestructuration, newText:string) {
|
||||||
|
fileProperties.freeText = finder.cleanSpaces(newText + ' ' + fileProperties.freeText)
|
||||||
|
return fileProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* examine plusieurs propriétés exif de date et retourne la plus ancienne
|
||||||
|
* @param filepath
|
||||||
|
*/
|
||||||
|
static async findExifCreationDate(filepath:string) {
|
||||||
|
|
||||||
|
log.debug(' finder - filepath', filepath)
|
||||||
|
let dateAlreadyInFileName = finder.findFormattedDate(filepath)
|
||||||
|
if (dateAlreadyInFileName) {
|
||||||
|
|
||||||
|
log.debug(' finder - ------ dateAlreadyInFileName', dateAlreadyInFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await exifr.parse(filepath)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static findFolderPath(filePath:string) {
|
||||||
|
let folders:any = filePath.split('/')
|
||||||
|
let fileName:any = folders.pop()
|
||||||
|
folders = filePath.replace(fileName, '')
|
||||||
|
|
||||||
|
log.debug(' finder - \n - folders', folders)
|
||||||
|
log.debug(' finder - - fileName', fileName, '\n')
|
||||||
|
return [folders, fileName]
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import finder from './finder.mjs'
|
import finder from './finder.js'
|
||||||
|
|
||||||
|
|
||||||
const pathFolder = '/home/poule/encrypted/stockage-syncable/photos/a_dispatcher/tout'
|
const pathFolder = '/home/poule/encrypted/stockage-syncable/photos/a_dispatcher/tout'
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import finder from './finders'
|
import finder from './finders'
|
||||||
import fs from 'node-fs'
|
import fs from 'node-fs'
|
||||||
|
import rangement_instance from '../../conf/configs'
|
||||||
|
import { TestFindFormattedDate, TestScreenShotIsFoundAndRenamed, TestTagsAreDetectedInFileName } from '../testFunctions'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
----------------------- parties non réalisées -----------------------
|
----------------------- parties non réalisées -----------------------
|
||||||
|
@ -132,4 +134,16 @@ class notReadyFunctions {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// run tests
|
||||||
|
if (rangement_instance.enableTestsLocally) {
|
||||||
|
|
||||||
|
TestTagsAreDetectedInFileName()
|
||||||
|
TestFindFormattedDate()
|
||||||
|
TestScreenShotIsFoundAndRenamed()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default notReadyFunctions
|
export default notReadyFunctions
|
Loading…
Reference in New Issue