Script: Rename and move photo

I am trying to rename a photo in a smartrule and move it to the appropriate subfolder. Unfortunately, when I run the move command, I always get the error “Error: Error: parameters missing”. Which parameters are missing? There are two mandatory parameters listed in the API reference.

function performsmartrule(records) {
	const app = Application("DEVONThink 3");
    const img = Application("Image Events");
	const database = app.databases["Fotos"];

    records.forEach(record => {
		let imgFile = img.open(record.path());
		let creationString = imgFile.metadataTags.byName("creation").value();

		let year = creationString.substr(0,4);
		let month = creationString.substr(5,2);
		let day = creationString.substr(8,2);

		record.name = `${creationString.substr(0,10).replace(/:/g,"-")}-${creationString.substr(11,2)}h${creationString.substr(14,2)}m${creationString.substr(17,2)}s`;

		let creationDate = new Date(`${creationString.substr(0,10).replace(/:/g,"-")}T${creationString.substr(11,8)}`);

		record.creationDate = creationDate;
		record.date = creationDate;

		let group = app.getRecordAt(`/${year}/${year}-${month}`, database);

		app.move(record, group);
    });
}

Thank you for the support!

Norman

You should check the syntax in the scripting dictionary. The correct method call is something like app.move({record: record, to: group})

Alternative approach

If I understand your code correctly (which would btw be easier if you’d given some details on what you want to achieve or commented the code), you’re accessing the “metadata tag” creation (which seems to be provided by Image Events, but is not really documented, afaict) that is of the form ‘YYYY:MM:DD HH:MM:SS’. You seem to be massaging that string into something like “YYYY-MM-DD-HHhMMmSSs” and using that to rename the DT record as well as set its creationDate and date properties.

I’d suggest to use named capturing groups in a Regular Expression instead of the substr method calls. That would probably make the code cleaner and easier to read (and hopefully less repetitive, too).

Here’s what the match does: It stores the year, month, day, hour, minute and second parts of the creation metadatatag in the match.groups properties of the same name. In addition, it stores the whole time expression in this object’s hms property. In the following code, the constant g is used as an abbreviation for match.groups, which should make the code (slightly) more legible. Also, the string “YYYY-MM-DD” is build only once and stored in the constant ymd.

const dateMatch = creationString.match(/(?<year>\d{4}):(?<month>\d\d):(?<day>\d\d)\s+(?<hms>(?<hour>\d\d):(?<minute>\d\d):(?<second>\d\d))/);
const g = dateMatch.groups; /* for brevity only */
const ymd = `${g.year}-${g.month}-${g.day}`; /* build YYYY-MM-DD string only once */
record.name = `${ymd}-${g.hour}h${g.minute}m${g.second}s`;
const creationDate = new Date(`${ymd}T${g.hms}`);
record.creationDate = record.date = creationDate;
/* NOTE: You have to provide an object with a property "in" to select a database! */
const group = app.getRecordAt(`/${g.year}/${year}-${month}`/, {in: database}); 
app.move({record: record, to: group});

Question: Why do you use a group hierarchy of the form “YYYY/YYYY-MM” instead of “YYYY/MM”?

Remark: There’s no need to use let in the loop (unless you’re modifying a variable, const works fine in this context.

Thank you for your help, the helpful comments and suggestions for improvement. Until now I didn’t know that you can name capture groups. Great tip.

What I want to achieve with the script is to use DEVTONthink as an archive for photos. This way my wife and I can put our favorite photos in the “Fotos” database inbox and the smart rule renames the photos based on the date they were taken and sorts them into the target folder.

The script is a first draft to test if the workflow is possible at all.

Unfortunately I still can’t manage to execute the move function. The documentation says:

move method : Move all instances of a record to a different group. Specify the "from" group to to move a single instance to a different group.
move
record: Record : The record or a list of records to move.
to: Record : The destination group which doesn't have to be in the same database.
[from: Record] : The source group. Only applicable if record and destination group are in the same database
→ Record

so I use the following call:

app.move(record, group);

which always fails. If I comment out this line, the script terminates without error and renames the photo.
The variables used should be of the correct type, so I am a bit at a loss as to what I am doing wrong at this point.

Do you have an idea / a tip?

The current script (with your helpful changes) looks like this:

function performsmartrule(records) {
	const app = Application("DEVONThink 3");
    const img = Application("Image Events");
	const database = app.databases["Fotos"];

    records.forEach(record => {
		let imgFile = img.open(record.path());
		let creationString = imgFile.metadataTags.byName("creation").value();

		const dateMatch = creationString.match(/(?<year>\d{4}):(?<month>\d\d):(?<day>\d\d)\s+(?<hms>(?<hour>\d\d):(?<minute>\d\d):(?<second>\d\d))/);
		
		const g = dateMatch.groups; /* for brevity only */
		
		const ymd = `${g.year}-${g.month}-${g.day}`; /* build YYYY-MM-DD string only once */
		
		record.name = `${ymd}-${g.hour}h${g.minute}m${g.second}s`;
		const creationDate = new Date(`${ymd}T${g.hms}`);
		record.creationDate = record.date = creationDate;
/* NOTE: You have to provide an object with a property "in" to select a database! */
		const group = app.getRecordAt(`/${g.year}/${g.year}-${g.month}/`, database); 
		app.move(record,group);	
    });
}

Thanks for the help!

Translated with www.DeepL.com/Translator (free version)

Interestingly, you chose to ignore the more important parts I said about the getRecordAt and the move methods, including the sample code.

I’ve said all I can about that script and don’t see a point in repeating myself.

II misinterpreted the documentation, thinking “record” and “to” were named parameters of the move function. I tried the changes you listed, but got a type conversion error. Now I understand the documentation better and just need to analyze myself as to why I am not using the correct type.
So far I have not worked much with javascript as I only program java and c++. Thanks for the help.

The script is finally running. The problem was the records array, which seems to contain objects that are not of the type Record. After I have explicitly determined the record again, the renaming and moving works. Now I also know how to read the API documentation. :laughing:

function performsmartrule(records) {
	const app = Application("DEVONThink 3");
    const img = Application("Image Events");
	const database = app.databases["Fotos"];

	records.forEach(r => {
		let record = app.getRecordWithUuid(r.uuid(), {in: database});

		const imgFile = img.open(record.path());
		const creationString = imgFile.metadataTags.byName("creation").value();

		const dateMatch = creationString.match(/(?<year>\d{4}):(?<month>\d\d):(?<day>\d\d)\s+(?<hms>(?<hour>\d\d):(?<minute>\d\d):(?<second>\d\d))/);
		
		const g = dateMatch.groups;
		
		const ymd = `${g.year}-${g.month}-${g.day}`;
		
		record.name = `${ymd}-${g.hour}h${g.minute}m${g.second}s`;
		const creationDate = new Date(`${ymd}T${g.hms}`);
		record.creationDate = record.date = creationDate;

		const group = app.getRecordAt(`/${g.year}/${g.year}-${g.month}/`, {in: database}); 

		app.move({record: record, to: group});	
    });
}
1 Like

:+1:

Although this

records.forEach(r => {
let record = app.getRecordWithUuid(r.uuid(), {in: database});

makes no sense at all. You already have a record in the loop, namely r. Using getRecordWithUuid with this record’s UUID will only return the exact same record, r (unless something is severely broken or you’re running the code on a quantum computer). Just use r throughout the loop and everything is fine.

Yes, i know but if I use r then the following error occurs in the move method:

on performSmarRule (Error: Error: Typen können nicht konvertiert werden.)

I will check if there is something like “instanceof” in java script because without debugging and the untyped language it’s hard to find the root cause.

I tested it and

app.logMessage(`Record: ${record instanceof app.Record}`);

returns

Record: false

strange…

Introspection does not really work in JXA. There’s some stuff on that on

I suppose that the error message you’re seeing is caused by some glitch in the underlying code. @cgrunenberg would be the judge of that.