Hi @dublove I have written a script that I hope does what you ask. Please give it a try and let me know how it goes. I hope you can follow along in the script to see what I am doing: I locate the caption based on it's proximity to the bottom left of the graphic frame, then rename the link.
- Mark
/**
* @file Rename Link With Caption.js
*
* Usage:
* 1. Select one or more graphic frames
* (non-graphics will be ignored).
* 2. Run script
*
* If the linked file's name contains "@" symbol it
* Will rename the linked file, replacing text after
* the "@" with the caption of the image, if found.
*
* @author m1b
* @discussion https://community.adobe.com/t5/indesign-discussions/can-you-help-me-modify-this-script-change-the-file-name-of-picture-directly-to-its-note/m-p/14763414
*/
function main() {
if (0 === app.documents.length)
return alert('Please open a document and try again.');
var doc = app.activeDocument,
items = doc.selection,
stats = { total: 0, counter: 0 };
for (var i = items.length - 1, item; i >= 0; i--)
items[i] = renameLinkWithCaption(items[i], items, stats) || items[i];
// alert('Renamed ' + stats.counter + ' out of ' + stats.total + ' graphics.');
};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Rename Linked File By Caption');
/**
* Renames the linked graphic file
* incorporating the graphic's caption.
* @author m1b
* @version 2024-07-29
* @param {Graphic} item - the linked graphic.
* @param {Array<PageItem>} [items] - the items to search for caption frame (default: all items on page).
* @param {Object} stats - an object for collecting statistics.
* @returns {Graphic} - the updated graphic.
*/
function renameLinkWithCaption(item, items, stats) {
stats = stats || { total: 0, counter: 0 };
if (
item
&& !item.hasOwnProperty('itemLink')
&& item.allGraphics
&& item.allGraphics.length
)
item = item.allGraphics[0];
if (
!item
|| !item.hasOwnProperty('itemLink')
)
return;
stats.total++;
if (LinkStatus.LINK_MISSING === item.itemLink.status)
return alert('The graphic is missing. Please re-link and try again.');
// get the caption for this linked graphic
var captionFrame = getCaptionTextFrame(item, items);
if (!captionFrame)
return alert('Could not locate caption for this link.');
// clean up the caption suitable for filenaming
var link = item.itemLink,
forbiddenChars = /[:\/\\]/g,
afterTheAtSymbol = /\@(.*)(\.[^\.]+)$/,
newName = captionFrame.contents
// sanitize filename
.replace(forbiddenChars, '')
// remove file extension, if any
.replace(/\.[^\.]+$/, '');
if (!afterTheAtSymbol.test(link.name))
return alert('Linked file name did not match the expected format (no @ symbol).');
// incorporate into the existing name
newName = link.name.replace(afterTheAtSymbol, '@' + newName + '$2');
if (newName === link.name)
return;
// rename the link, get new reference to link and item
link = renameLink(link, newName);
if (
!link
|| !link.isValid
)
return;
item = link.parent;
stats.counter++;
return item;
};
/**
* Renames the a link's *file* and
* re-links to the renamed file.
* @author m1b
* @version 2024-07-28
* @param {Link} link - the link to rename.
* @param {String} newName - the name to apply.
* @return {Link?} - the updated link.
*/
function renameLink(link, newName) {
var file = File(link.filePath);
if (!file.exists)
return;
var newPath = link.filePath.replace(link.name, newName);
file.rename(newName);
link.relink(File(newPath));
if (link.isValid)
return link;
};
/**
* Returns a caption frame, given a page item.
* @author m1b
* @version 2024-07-29
* @param {Page Item} graphic - a linked graphic.
* @param {Array<PageItem>} [items] - the items to search for caption frame (default: all items on page).
* @param {Anchor} [graphicAnchor] - an Indesign Anchor (default: AnchorPoint.BOTTOM_CENTER_ANCHOR).
* @param {Anchor} [captionAnchor] - an Indesign Anchor (default: AnchorPoint.TOP_CENTER_ANCHOR).
* @returns {TextFrame?}
*/
function getCaptionTextFrame(graphic, items, graphicAnchor, captionAnchor) {
items = items.slice() || graphic.parent.parentPage.allPageItems;
graphicAnchor = graphicAnchor || AnchorPoint.BOTTOM_CENTER_ANCHOR;
captionAnchor = captionAnchor || AnchorPoint.TOP_CENTER_ANCHOR;
var point = getPointFromBounds(graphic.parent.geometricBounds, graphicAnchor),
captionFrame;
// sort by distance from bottom left of graphic frame
items.sort(sortByDistanceFrom(point, captionAnchor));
// bypass items with no contents
do {
captionFrame = items.shift();
if (!captionFrame)
return;
} while (
'TextFrame' !== captionFrame.constructor.name
|| !captionFrame.contents
|| captionFrame === graphic.parent
);
return captionFrame;
};
/**
* Returns a sort function which sorts page items by the
* distance from `point` to an item's anchor point.
* @param {point} point - [x,y].
* @param {Anchor} [anchor] - an Indesign Anchor (default: top-left).
* @returns {Number}
*/
function sortByDistanceFrom(point, anchor) {
anchor = anchor || AnchorPoint.TOP_LEFT_ANCHOR;
return function sortByDistanceFromPoint(a, b) {
var distanceA = mDist(getPointFromBounds(a.geometricBounds, anchor), point),
distanceB = mDist(getPointFromBounds(b.geometricBounds, anchor), point);
return distanceA - distanceB;
};
};
/**
* Returns the "Manhattan" distance between two points.
* @param {point} p0 - a point array [x,y].
* @param {point} p1 - a point array [x,y].
* @returns {Number}
*/
function mDist(p0, p1) {
var dx = Math.abs(p1[0] - p0[0]),
dy = Math.abs(p1[1] - p0[1]);
return dx + dy;
};
/**
* Returns a point, given `bounds` and an `anchor`.
* @author m1b
* @version 2024-07-28
* @param {Array<Number>} bounds - bounds [T, L, B, R].
* @param {Anchor} anchor - an Indesign Anchor.
* @returns {point} - [x, y].
*/
function getPointFromBounds(bounds, anchor) {
switch (anchor) {
case AnchorPoint.TOP_LEFT_ANCHOR:
return [bounds[1], bounds[0]];
case AnchorPoint.TOP_CENTER_ANCHOR:
return [bounds[1] + (bounds[3] - bounds[1]) / 2, bounds[0]];
case AnchorPoint.TOP_RIGHT_ANCHOR:
return [bounds[3], bounds[0]];
case AnchorPoint.CENTER_ANCHOR:
return [bounds[1] + (bounds[3] - bounds[1]) / 2, bounds[0] + (bounds[2] - bounds[0]) / 2];
case AnchorPoint.LEFT_CENTER_ANCHOR:
return [bounds[1], bounds[0] + (bounds[2] - bounds[0]) / 2];
case AnchorPoint.RIGHT_CENTER_ANCHOR:
return [bounds[3], bounds[0] + (bounds[2] - bounds[0]) / 2];
case AnchorPoint.BOTTOM_CENTER_ANCHOR:
return [bounds[1] + (bounds[3] - bounds[1]) / 2, bounds[2]];
case AnchorPoint.BOTTOM_LEFT_ANCHOR:
return [bounds[1], bounds[2]];
case AnchorPoint.BOTTOM_RIGHT_ANCHOR:
return [bounds[3], bounds[2]];
default:
break;
}
};
Edit 2024-07-28: changed structure slightly so that multiple graphics could be selected and renamed at once, and added feedback alert.
Edit 2024-07-28: added `getPointFromBounds` function to make it easier for OP to set the measurement points as desired. Edit this line:
bottomCenter = getPointFromBounds(bounds, AnchorPoint.BOTTOM_CENTER_ANCHOR),
and/or this line:
items.sort(sortByDistanceFrom(bottomCenter, AnchorPoint.TOP_CENTER_ANCHOR));
Edit 2024-07-29: Based on OP feedback, I turned the error alerts back on, I turned off the final stats alert, and now, if the caption contains a file extension, it will remove it before renaming link. Also in this version you must select the caption(s) along with the graphic(s)—script will no longer search for captions outside of the selected page items. To revert this behaviour, change this line:
items[i] = renameLinkWithCaption(items[i], items, stats) || items[i];
to:
items[i] = renameLinkWithCaption(items[i], undefined, stats) || items[i];
Edit 2024-07-30: fixed bug where `items` array was being mutated by `getCaptionTextFrame`.