Copy link to clipboard
Copied
I saved all my Photoshop linked-Files of my multiple Photoshop Data in a folder. I moved the folder which contains the linked-files to another place.
Now whenever I open a file, I need to relink it and it's not so bad. But... I have lots of photoshop files which their liked-files are moved to another place now (they are all in the same folder as before) and I need to open every single file and relink it again.
Added saving the previously selected path. Try it:
#target photoshop
/*
// BEGIN__HARVEST_EXCEPTION_ZSTRING
<javascriptresource>
<name>Relink files</name>
<category>jazzy</category>
<enableinfo>true</enableinfo>
</javascriptresource>
// END__HARVEST_EXCEPTION_ZSTRING
*/
$.localize = true
const UUID = 'a3e4d053-135c-4225-b741-39bcb6656fd0',
apl = new AM('application'),
doc = new AM('document'),
lr = new AM('layer'),
fn = new CommonFunctions(),
str = new Locale(),
ver =
...
Copy link to clipboard
Copied
Hi! A similar question was discussed in the topic Is there an option that shows me a list with all smart objects paths?
I planned to finish this script and add the ability to work in batch mode with predefined parameters, but so far I do not have time for this task 😞
Copy link to clipboard
Copied
Perfect, but can I use it now as a script for my Problem? (relink file in mupltiple Photoshop Files at once)
Copy link to clipboard
Copied
Nobody is keeping you from creating a Script that meets your needs.
What is giving you problems?
What is the exact scenario? Operate on open files or select files via dialog, relink all linked Smart Objects, only missing linked Smart Objects, what is the exact Folder-structure, …?
Copy link to clipboard
Copied
I had my old structure file order:
1- Photoshop File
2- Photoshop linked-Files
3- My Old Files-Structure / Order
I moved just the photosohp linked files in my (newData) folder:
Now whenever I open a photoshop file, it askes me to relink the missing file. (in every Photoshop file, I have just one linked file)
I have lot of file which now thier linked files are missing. I dont want to open every file and relink its missing link.
Copy link to clipboard
Copied
The main problem is that when opening each file with broken links, Photoshop shows a modal window that stops the script. This completely destroys the ability to batch process multiple files without user interaction.
Copy link to clipboard
Copied
I apologize then; I was mistaken and there is something that does make the creation of such a Script impossible (or hopefully just difficult?).
Copy link to clipboard
Copied
may be creating an action with/without Script to open a file and automaticaly change the link address? Is it possible?
Copy link to clipboard
Copied
Nothing is impossible.
I see four options:
Since working with broken links is more an exception than a rule for my workflow, I planned to simply throw parameters to the script via an action (for working with a standard batch) and manually close notification windows ¯\_(ツ)_/¯
Copy link to clipboard
Copied
I tried your first option. It works like a charm 🙂
but with a (pretty much) long delay 😕
Copy link to clipboard
Copied
Thanks a lot. I dont have to search for the one single file in a bunch of files 🙂
Copy link to clipboard
Copied
Try turning this option off (unless you're using links inside embedded smart objects). This will speed up the document pre-processing time.
Copy link to clipboard
Copied
I've allready tried. but the some reasult.
And also if I triger the script using action, it opens the "Linked files" Dialog and I need to change the sittings again.
Can I set the Target Path in a constaint Path as default so I dont have to select open Folder... and brouse the file?
Copy link to clipboard
Copied
Added saving the previously selected path. Try it:
#target photoshop
/*
// BEGIN__HARVEST_EXCEPTION_ZSTRING
<javascriptresource>
<name>Relink files</name>
<category>jazzy</category>
<enableinfo>true</enableinfo>
</javascriptresource>
// END__HARVEST_EXCEPTION_ZSTRING
*/
$.localize = true
const UUID = 'a3e4d053-135c-4225-b741-39bcb6656fd0',
apl = new AM('application'),
doc = new AM('document'),
lr = new AM('layer'),
fn = new CommonFunctions(),
str = new Locale(),
ver = '0.23';
var allowedExtensions = function (s) { var t = {}; for (var i = 0; i < s.length; i++) t[s[i]] = true; return t }(['PSD', 'PDD', 'PSDT', 'PSB', 'BMP', 'RLE', 'DIB', 'GIF', 'EPS', 'IFF', 'TDI', 'JPG', 'JPEG', 'JPE', 'JPF', 'JPX', 'JP2', 'J2C',
'J2K', 'JPC', 'JPS', 'MPO', 'PCX', 'PDF', 'PDP', 'PXR', 'PNG', 'SCT', 'TGA', 'VDA', 'ICB', 'VST', 'TIFF', 'PBM', 'PGM', 'PPM', 'PNM', 'PFM', 'PAM',
'DCM', 'DC3', 'DIC', 'TIF', 'CRW', 'NEF', 'RAF', 'ORF', 'MRW', 'MOS', 'SRF', 'PEF', 'DCR', 'CR2', 'DNG', 'ERF', 'X3F', 'RAW', 'ARW', 'CR3', 'KDC', '3FR',
'MEF', 'MFW', 'NRW', 'RWL', 'RW2', 'SRW', 'GPR', 'IIQ']),
cfg = (new AM()).getScriptSettings();
main();
function main() {
try {
var runMode = version.split('.')[0] < 21 ? false : true;
if (!runMode) cfg.checkEmbedded = false;
do {
var currentSelection = fn.getSelectedLayersIds(),
smartObjects = fn.buildSmartObjectsTree(),
filesList = fn.buildFilesList(smartObjects);
if (fn.isDitry) app.refresh()
var w = new mainWindow(filesList, runMode),
result = w.show();
if (result == 1) {
activeDocument.suspendHistory(str.relink.toString(), 'fn.relink(filesList, smartObjects)')
if (currentSelection.length) doc.selectLayerByIDList(currentSelection)
}
fn.isDitry = false
} while (result == 3)
} catch (e) {
if (confirm(str.errGlobal, true, str.err)) fn.createReport(smartObjects, filesList, e);
}
}
function AM(target) {
var s2t = stringIDToTypeID,
t2s = typeIDToStringID;
target = target ? s2t(target) : null;
this.getProperty = function (property, id, idxMode) {
r = new ActionReference();
if (property) {
property = s2t(property);
r.putProperty(s2t('property'), property);
}
id != undefined ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id)) :
r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
try { return property ? getDescValue(executeActionGet(r), property) : executeActionGet(r) } catch (e) { return null }
}
this.hasProperty = function (property, id, idxMode) {
property = s2t(property);
(r = new ActionReference()).putProperty(s2t('property'), property);
id ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id))
: r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
try { return executeActionGet(r).hasKey(property) } catch (e) { return false }
}
this.descToObject = function (d, o) {
if (d) {
o = o ? o : {}
for (var i = 0; i < d.count; i++) {
var k = d.getKey(i)
o[t2s(k)] = getDescValue(d, k)
}
return o
}
}
this.relinkCurrentLayer = function (pth) {
try {
(d = new ActionDescriptor()).putPath(s2t('target'), pth);
executeAction(s2t('placedLayerRelinkToFile'), d, DialogModes.NO);
return true;
} catch (e) { return false }
}
this.deleteCurrentHistoryState = function () {
(r = new ActionReference()).putProperty(s2t('historyState'), s2t('currentHistoryState'));
(d = new ActionDescriptor()).putReference(s2t('target'), r);
try { executeAction(s2t('delete'), d, DialogModes.NO); } catch (e) { }
}
this.convertSmartObjectToLayers = function () {
try { executeAction(s2t('placedLayerConvertToLayers'), undefined, DialogModes.NO); return true } catch (e) { return false }
}
this.selectLayerByIDList = function (IDList) {
var ref = new ActionReference()
for (var i = 0; i < IDList.length; i++) {
ref.putIdentifier(s2t('layer'), IDList[i])
}
var desc = new ActionDescriptor()
desc.putReference(s2t('target'), ref)
desc.putBoolean(s2t('makeVisible'), false)
executeAction(s2t('select'), desc, DialogModes.NO)
}
this.editSmartObject = function () {
try {
executeAction(s2t('placedLayerEditContents'), undefined, DialogModes.NO)
return true
} catch (e) { return false }
}
this.closeDocument = function (save) {
save = save != true ? s2t('no') : s2t('yes');
(d = new ActionDescriptor()).putEnumerated(s2t('saving'), s2t('yesNo'), save);
executeAction(s2t('close'), d, DialogModes.NO)
}
this.applyLocking = function (desc, id, idxMode) {
if (!desc) {
desc = new ActionDescriptor();
desc.putBoolean(s2t('protectNone'), true);
}
var r = new ActionReference();
id ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id))
: r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
(d = new ActionDescriptor()).putReference(s2t('null'), r);
d.putObject(s2t('layerLocking'), s2t('layerLocking'), desc);
executeAction(s2t('applyLocking'), d, DialogModes.NO);
}
function getDescValue(d, p) {
switch (d.getType(p)) {
case DescValueType.OBJECTTYPE: return { type: t2s(d.getObjectType(p)), value: d.getObjectValue(p) };
case DescValueType.LISTTYPE: return d.getList(p);
case DescValueType.REFERENCETYPE: return d.getReference(p);
case DescValueType.BOOLEANTYPE: return d.getBoolean(p);
case DescValueType.STRINGTYPE: return d.getString(p);
case DescValueType.INTEGERTYPE: return d.getInteger(p);
case DescValueType.LARGEINTEGERTYPE: return d.getLargeInteger(p);
case DescValueType.DOUBLETYPE: return d.getDouble(p);
case DescValueType.ALIASTYPE: return d.getPath(p);
case DescValueType.CLASSTYPE: return d.getClass(p);
case DescValueType.UNITDOUBLE: return (d.getUnitDoubleValue(p));
case DescValueType.ENUMERATEDTYPE: return { type: t2s(d.getEnumerationType(p)), value: t2s(d.getEnumerationValue(p)) };
default: break;
};
}
this.getScriptSettings = function () {
var d = null;
try { d = getCustomOptions(UUID) } catch (e) { }
return d ? this.descToObject(d, new Config()) : new Config();
}
this.putScriptSettings = function (o) {
var d = objToDesc(o)
putCustomOptions(UUID, d)
function objToDesc(o) {
var d = new ActionDescriptor;
for (var i = 0; i < o.reflect.properties.length; i++) {
var k = o.reflect.properties[i].toString();
if (k == '__proto__' || k == '__count__' || k == '__class__' || k == 'reflect') continue;
var v = o[k];
k = s2t(k);
switch (typeof (v)) {
case 'boolean': d.putBoolean(k, v); break;
case 'number': d.putInteger(k, v); break;
case 'string': d.putString(k, v); break;
}
}
return d;
}
}
}
function Config() {
this.relinkMode = 0
this.matchExtension = false
this.collect = false
this.subfolder = 'assets'
this.groupByExtension = true
this.move = false
this.checkEmbedded = true
this.targetFolder = ''
}
function CommonFunctions() {
this.getSelectedLayersIds = function () {
if (!apl.getProperty('numberOfDocuments')) return []
var targetLayers = doc.hasProperty('targetLayersIDs') ? doc.getProperty('targetLayersIDs') : [],
selection = [];
if (targetLayers) {
for (var i = 0; i < targetLayers.count; i++) {
selection.push(targetLayers.getReference(i).getIdentifier(stringIDToTypeID('layerID')))
}
}
return selection;
}
this.buildSmartObjectsTree = function () {
if (!apl.getProperty('numberOfDocuments')) return null
var smartObjects = getSmartObjectsList(),
len = 0;
if (cfg.checkEmbedded) {
for (var a in smartObjects) if (!(lr.getProperty('smartObject', a).value.getBoolean(stringIDToTypeID('linked')))) len++
}
this.isDitry = Boolean(len)
var progress = progressWindow(str.findEmbedded, '', len)
if (len && cfg.checkEmbedded) progress.show()
enumSmartObjects(smartObjects, len, progress)
progress.close()
return smartObjects.toSource() == '({})' ? null : smartObjects
}
this.buildFilesList = function (smartObjects) {
var filesList = [];
if (smartObjects != null) filesList = findEqualFiles(collectFiles(smartObjects));
function collectFiles(so, filesList) {
filesList = filesList ? filesList : []
for (var a in so) {
if (so[a].fileReference) filesList.push(describeFile(so[a], Number(a))) else collectFiles(so[a], filesList)
}
return filesList
}
function describeFile(o, id) {
o.sameFolder = null
o.missed = null
o.relink = null
o.relinked = null
allowedExtensions[o.link.extension.toUpperCase()] = true
return o
}
filesList.checkFiles = function () {
for (var i = 0; i < this.length; i++) {
with (this[i]) {
var cur = this[i].relink ? relink : link
if (cur instanceof File) {
sameFolder = documentFolder != null ? (decodeURI(cur).toUpperCase().indexOf(decodeURI(documentFolder).toUpperCase()) == 0 ? true : false) : null
missed = !cur.exists
} else {
sameFolder = false
missed = true
}
}
}
return this
}
return filesList
}
this.splitFilename = function (f) {
var s;
if (f) {
if (f instanceof File) { s = decodeURI(f.name) }
else {
s = f.filename ? f.filename : f
f = {}
}
f.filename = s.substr(0, s.lastIndexOf('.'))
f.extension = (s.substr(s.lastIndexOf('.') + 1, s.length))
}
return f
}
this.findLinks = function (parentFolder, files, selected) {
var fileCache = {},
toDo = {},
len = selected ? selected.length : files.length;
for (var i = 0; i < len; i++) {
var index = selected ? (selected[i]).index : i
toDo[index] = files[index]
}
if (len) {
for (var a in toDo) {
if (toDo[a].sameFolder) {
with (toDo[a]) {
relink = null;
var cur = link,//relink ? relink : link,
folders = parentFolder != null && cur instanceof File ? (decodeURI(cur.path).replace(new RegExp(decodeURI(parentFolder), 'i'), '').split('/')) : [];
}
var link = findSimilarFiles(cur.filename, cur.extension, folders, parentFolder);
if (link) {
files[a].relink = decodeURI(link).toUpperCase() == decodeURI(toDo[a].link).toUpperCase() ? null : link;
delete toDo[a]
}
}
}
{
var len = getObjectLength(toDo)
if (len) {
var c = 1;
app.updateProgress(c++, len + 1)
var allFiles = enumAllFiles(parentFolder)
for (var a in toDo) {
var cur = toDo[a]
app.changeProgressText(str.searchFile + cur.fileReference)
app.updateProgress(c++, len + 1)
var hash = stringIDToTypeID(cur.link.filename)
if (allFiles[hash]) {
var cur = allFiles[hash],
link = null;
for (var i = 0; i < cur.length; i++)
if (files[a].link.extension.toUpperCase() == cur[i].extension.toUpperCase()) {
link = cur[i]
break;
}
if (!link && !cfg.matchExtension) link = cur[0]
if (link) {
files[a].relink = decodeURI(link).toUpperCase() == decodeURI(toDo[a].link).toUpperCase() ? null : link
delete toDo[a]
}
}
}
}
}
}
function findSimilarFiles(fle, ext, dir, parent) {
var subPath = '',
len = dir.length;
for (var i = 0; i < len; i++) {
if (dir[i] == '') continue;
subPath = '/' + dir[i] + '/' + subPath
var f = checkFile(fle, ext, parent + subPath)
if (f) return f
}
var f = checkFile(fle, ext, parent)
return f ? f : null
function checkFile(fle, ext, pth) {
var f = new File(pth + '/' + fle + '.' + ext)
if (f.exists) {
return splitFilename(f)
}
else if (!cfg.matchExtension) {
fle = fle.toUpperCase()
var p = new Folder(pth)
if (p.exists) {
if (!fileCache[pth]) fileCache[pth] = p.getFiles()
var cur = fileCache[pth];
for (var i = 0; i < cur.length; i++) {
if (cur[i] instanceof File) {
var f = splitFilename(cur[i]);
if (f.filename.toUpperCase() == fle && allowedExtensions[f.extension.toUpperCase()]) return f
}
}
}
}
return null;
}
}
function enumAllFiles(parent, listOfFiles) {
app.changeProgressText(str.buildFileList + decodeURI(Folder(parent).name))
listOfFiles = listOfFiles ? listOfFiles : {};
var files = Folder(parent).getFiles();
for (var i = 0; i < files.length; i++) {
app.updateProgress(i + 1, files.length)
var cur = files[i];
if (cur instanceof File) {
if (!cur.hidden) {
cur = splitFilename(cur)
if (allowedExtensions[cur.extension.toUpperCase()]) {
var hash = stringIDToTypeID(cur.filename)
if (!listOfFiles[hash]) {
listOfFiles[hash] = [cur]
} else {
listOfFiles[hash].push(cur)
}
}
}
} else if (cur instanceof Folder) {
enumAllFiles(cur, listOfFiles, listOfFiles)
}
}
return listOfFiles
}
}
this.collectAssets = function (parentFolder, files) {
if (!parentFolder) return
var subfolder = '/' + cfg.subfolder.replace(/[~#%&*{}:<>?|\'-]/g, '_') + '/'
if (!subfolder.replace(/[\/ .]/g, '').length) subfolder = '/'
var progress = new progressWindow(str.copyAssets, '', files.length);
progress.show()
for (var i = 0; i < files.length; i++) {
progress.updateProgress(files[i].fileReference)
if (files[i].missed) continue;
var cur = files[i].relink ? files[i].relink : files[i].link,
target = splitFilename(new File(parentFolder + subfolder + (cfg.groupByExtension ? '/' + cur.extension.toUpperCase() + '/' : '') + cur.filename + '.' + cur.extension));
if (target.exists && (decodeURI(target).toUpperCase() == decodeURI(cur).toUpperCase())) {
continue;
} else {
target = createUniqueFileName(target)
}
if (!(Folder(target.path).exists)) Folder(target.path).create()
if (cfg.collect && cfg.move) {
if (documentFolder != null && decodeURI(cur).toUpperCase().indexOf(decodeURI(documentFolder).toUpperCase()) == 0) {
if (!target.exists) {
if (cur.rename(target)) {
files[i].relink = target
} else {
if (cur.copy(target)) {
cur.remove()
files[i].relink = target
}
}
var f = Folder(cur.path)
if (f.getFiles().length == 0) {
f.remove()
do {
f = Folder(f.path)
if (f.getFiles().length) break;
} while (f.remove())
}
}
}
}
if (files[i].relink != target && cur.copy(target)) files[i].relink = target
}
progress.close()
function createUniqueFileName(target) {
var parent = decodeURI(target.path),
f = target,
c = 1;
while (f.exists) {
f = splitFilename(new File(parent + '/' + target.filename + ' (' + c + ').' + target.extension))
c++;
}
return f
}
}
this.relink = function (files, smartObjects) {
var toRelink = [],
linkedObjects = {},
embeddedObjects = {},
locked = unlockLayers();
for (var i = 0; i < files.length; i++) if (files[i].relink) toRelink.push(files[i])
for (var a in smartObjects) {
if (smartObjects[a].link) { linkedObjects[a] = smartObjects[a] }
else { embeddedObjects[a] = flattenObject(smartObjects[a]) }
}
if (toRelink.length) {
var progress = new progressWindow(str.relink, '', toRelink.length)
progress.show()
for (var a in linkedObjects) {
for (var i = 0; i < toRelink.length; i++) {
if (isEqualObject(linkedObjects[a].link, toRelink[i].link)) {
lr.selectLayerByIDList([Number(a)])
progress.updateProgress(lr.getProperty('name', Number(a)))
if (lr.relinkCurrentLayer(toRelink[i].relink)) toRelink[i].relinked = true;
break;
}
}
}
for (var a in embeddedObjects) {
var toDelete = [],
found = false;
for (var i = 0; i < toRelink.length; i++) {
for (var x = 0; x < embeddedObjects[a].length; x++) {
if (isEqualObject(embeddedObjects[a][x].link, toRelink[i].link)) {
found = true;
break;
}
if (found) break;
}
}
if (found) {
for (var i = 0; i < embeddedObjects[a].length; i++) {
if (embeddedObjects[a][i].link instanceof File && embeddedObjects[a][i].link.exists) continue;
var f = createDumbFile(documentFolder, embeddedObjects[a][i].fileReference)
if (f) toDelete.push(f)
}
lr.selectLayerByIDList([Number(a)])
progress.updateProgress(lr.getProperty('name', Number(a)))
if (lr.editSmartObject()) {
relinkEmbedded(toRelink)
doc.closeDocument(true)
}
for (var i = 0; i < toDelete.length; i++) {
toDelete[i].remove()
}
}
}
lockLayers(locked)
}
function relinkEmbedded(files) {
var len = doc.getProperty('numberOfLayers'),
locked = unlockLayers();
if (len) {
for (var i = len; i >= 1; i--) {
if (lr.hasProperty('smartObject', i, true)) {
var id = lr.getProperty('layerID', i, true),
smartObject = lr.descToObject(lr.getProperty('smartObject', i, true).value);
if (smartObject.linked) {
for (var x = 0; x < files.length; x++) {
if (isEqualObject(splitFilename(smartObject.link), files[x].link)) {
doc.selectLayerByIDList([id])
if (lr.relinkCurrentLayer(files[x].relink)) files[x].relinked = true
break;
}
}
} else {
doc.selectLayerByIDList([id])
if (doc.convertSmartObjectToLayers()) {
var cur = flattenObject(enumSmartObjects(getSmartObjectsList(lr.getProperty('itemIndex'), true)))
doc.deleteCurrentHistoryState()
var found = false;
do {
var f = splitFilename((cur.shift()).link)
for (var x = 0; x < files.length; x++) {
if (isEqualObject(f, files[x].link)) {
found = true
break;
}
}
if (found) break;
} while (cur.length)
if (true) {
doc.selectLayerByIDList([id])
if (lr.editSmartObject()) {
relinkEmbedded(files)
doc.closeDocument(true)
}
}
}
}
}
}
}
lockLayers(locked)
}
function flattenObject(o, output) {
output = output ? output : [];
for (var a in o) {
if (o[a].link) {
output.push(o[a])
} else {
flattenObject(o[a], output)
}
}
return output
}
function isEqualObject(a, b) {
for (var i in a) {
if (stringIDToTypeID(a[i]) != stringIDToTypeID(b[i])) return false
}
return true
}
}
this.createReport = function (smartObjects, filesList, err) {
var report = [],
s2t = stringIDToTypeID;
report.push(new Date + '\nSend this report to jazz-y@ya.ru')
if (err) report.push('Error:\n' + err.toSource())
if (smartObjects) report.push('Smart objects:\n' + smartObjects.toSource())
if (filesList) report.push('Files:\n' + filesList.toSource())
report.push('Application:')
try {
(d = new ActionDescriptor()).putObject(s2t('object'), s2t('object'), apl.getProperty());
report.push(executeAction(s2t('convertJSONdescriptor'), d).getString(s2t('json')))
} catch (e) { report.push(e.toSource()) }
report.push('Documents:')
if (len = apl.getProperty('numberOfDocuments')) {
try {
for (var i = 1; i <= len; i++) {
(d = new ActionDescriptor()).putObject(s2t('object'), s2t('object'), doc.getProperty(null, i, true));
report.push(executeAction(s2t('convertJSONdescriptor'), d).getString(s2t('json')))
}
} catch (e) { report.push(e.toSource()) }
}
report.push('Layers of active document:')
if (len = doc.getProperty('numberOfLayers')) {
try {
for (var i = 1; i <= len; i++) {
(d = new ActionDescriptor()).putObject(s2t('object'), s2t('object'), lr.getProperty(null, i, true));
report.push(executeAction(s2t('convertJSONdescriptor'), d).getString(s2t('json')))
}
} catch (e) { report.push(e.toSource()) }
}
var f = new File('~/desktop/' + str.scriptName + ' errorLog.txt');
if (f.saveDlg(str.saveLog)) {
f.open('w')
f.write(report.join('\n\n'))
f.close()
}
}
function unlockLayers() {
var len = doc.getProperty('numberOfLayers'),
layers = {};
if (len) {
for (var i = len; i >= 1; i--) {
if (lr.getProperty('layerSection', i, true).value == 'layerSectionEnd') continue;
var locking = lr.getProperty('layerLocking', i, true).value
if (checkLocking(locking)) {
layers[lr.getProperty('layerID', i, true)] = lr.getProperty('layerLocking', i, true).value;
lr.applyLocking(undefined, i, true)
}
}
}
return layers;
}
function lockLayers(o) {
for (var a in o) {
lr.applyLocking(o[a], a)
}
}
function checkLocking(d) {
var o = lr.descToObject(d);
for (var a in o) if (o[a]) return true
return false
}
function enumSmartObjects(ids, progressLength, w) {
for (var a in ids) {
var cur = doc.descToObject(lr.getProperty('smartObject', a).value)
if (cur.linked && !checkLocking(lr.getProperty('layerLocking', a).value)) {
if (cur.link && cur.link.type && cur.link.type == 'ccLibrariesElement') {
delete ids[a];
continue;
}
ids[a] = describeSmartObject(cur)
} else {
if (cfg.checkEmbedded) {
if (progressLength) w.updateProgress(lr.getProperty('name', a))
doc.selectLayerByIDList([a])
if (doc.convertSmartObjectToLayers()) {
ids[a] = enumSmartObjects(getSmartObjectsList(lr.getProperty('itemIndex'), true))
doc.deleteCurrentHistoryState()
}
}
if (ids[a] == null || ids[a].toSource() == '({})') delete ids[a]
}
}
return ids
function describeSmartObject(o) {
return {
link: splitFilename(!o.link || o.link == '' ? o.fileReference : File(o.link)),
fileReference: o.fileReference,
}
}
}
function getSmartObjectsList(idx, mode) {
var indexFrom = idx ? (doc.getProperty('hasBackgroundLayer') ? --idx : idx) : doc.getProperty('numberOfLayers'),
indexTo = doc.getProperty('hasBackgroundLayer') ? 0 : 1;
return buildSmartObjectsList(enumLayers(indexFrom, indexTo, mode))
function enumLayers(from, to, currentSection, parentItem, group) {
parentItem = parentItem ? parentItem : [];
var isDirty = false;
for (var i = from; i >= to; i--) {
var layerSection = lr.getProperty('layerSection', i, true).value;
if (layerSection == 'layerSectionStart') {
i = enumLayers(i - 1, to, undefined, [], parentItem)
isDirty = true;
if (currentSection) return parentItem[0]
continue;
}
if (currentSection && isDirty) return []
var properties = {};
properties.layerKind = lr.getProperty('layerKind', i, true)
properties.id = lr.getProperty('layerID', i, true)
properties.smartObject = lr.hasProperty('smartObject', i, true)
if (layerSection == 'layerSectionEnd') {
for (o in properties) { parentItem[o] = properties[o] }
group.push(parentItem);
return i;
} else {
parentItem.push(properties)
if (currentSection && !isDirty) return parentItem
}
}
return parentItem
}
function buildSmartObjectsList(layers, list) {
if (!list) list = {}
for (var i = 0; i < layers.length; i++) {
var cur = layers[i]
if (cur.length) {
buildSmartObjectsList(cur, list)
} else {
if (cur.smartObject && cur.layerKind == 5) list[cur.id] = null
}
}
return list
}
}
function getObjectLength(o) {
var i = 0
for (var a in o) i++
return i
}
function findEqualFiles(files, relinkMode) {
var o = [], f = files.slice();
do {
var cur = f.shift()
if (cur) {
for (var i = 0; i < f.length; i++) {
if (!relinkMode) {
if (f[i] instanceof File) {
if (decodeURI(cur.link).toUpperCase() == decodeURI(f[i].link).toUpperCase()) f[i] = null
} else {
if (f[i] && (cur.link.filename + cur.link.extension).toUpperCase() == (f[i].link.filename + f[i].link.extension).toUpperCase()) f[i] = null
}
}
else {
if (f[i] && decodeURI(cur.relink).toUpperCase() == decodeURI(f[i].relink).toUpperCase()) f[i] = null
}
}
o.push(cur)
}
} while (f.length)
return o
}
function createDumbFile(parent, reference) {
var f = new File(parent + '/' + reference);
if (f.exists) return null
f.open('w')
f.close()
return f
}
function progressWindow(title, message, max) {
var w = new Window('palette', title),
bar = w.add('progressbar', undefined, 0, max),
stProgress = w.add('statictext', undefined, message);
stProgress.preferredSize = [350, 20]
stProgress.alignment = 'left'
bar.preferredSize = [350, 20]
w.updateProgress = function (message) {
bar.value++;
if (message) stProgress.text = bar.value + '/' + max + ': ' + message
w.update();
}
return w;
}
splitFilename = this.splitFilename;
documentFolder = this.documentFolder = doc.hasProperty('fileReference') ? Folder(File(doc.getProperty('fileReference').fsName).path) : null;
isDitry = this.isDitry = false;
targetFolder = this.targetFolder = null;
}
function Locale() {
this.ico = {
red: "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\n\x00\x00\x00\n\b\x06\x00\x00\x00\u008D2\u00CF\u00BD\x00\x00\x00;IDAT\x18\u0095c\u00FCci\u00F3\u009F\u0081\b\u00C0\x02R\u00C2((\u0084W\u00E5\u00FF\u00F7\u00EF \n\u00C1\u0080\u0087\x17\u00BB\u00AA/\u009F\u00C1\x14\x131\u00D6\x0E\x15\u0085\b_C}\u0087W!(\u009C\u00F0\x02\x06\x06\x06\x00\x18\u00EF\fO\u0083\b\u00CC\u00FD\x00\x00\x00\x00IEND\u00AEB`\u0082",
green: "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\n\x00\x00\x00\n\b\x06\x00\x00\x00\u008D2\u00CF\u00BD\x00\x00\x00=IDAT\x18\u0095c\u00F4\u00DEo\u00F8\u009F\u0081\b\u00C0\x02R\"\u00C6\u00C9\u008FW\u00E5\u00AB\u00EF\x1F!\nA@\u0090\u009D\x1B\u00AB\u00A2\u00F7?\u00BF\u0082i&b\u00AC\x1D*\n\u00E1\u00BE\u0086\u00F9\x0E\u00AFBP8\u00E1\x05\f\f\f\x000\x1F\x0E\x05z4V\u0094\x00\x00\x00\x00IEND\u00AEB`\u0082",
yellow: "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\n\x00\x00\x00\n\b\x06\x00\x00\x00\u008D2\u00CF\u00BD\x00\x00\x00;IDAT\x18\u0095c\u00FCp@\u00ED?\x03\x11\u0080\x05\u00A4\u0084\u0089\u0093\x1B\u00AF\u00CA\x7F\u00DF\u00BFB\x14\u0082\x15\u00B3s`W\u00F4\u00F3\x07D\u009E\x18k\u0087\u008AB\u00B8\u00AFa\u00BE\u00C3\u00AB\x10\x14Nx\x01\x03\x03\x03\x00+\u00AA\x0E\u00CA*\u0090\u00C3\u00A2\x00\x00\x00\x00IEND\u00AEB`\u0082",
all: "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\n\x00\x00\x00\n\b\x06\x00\x00\x00\u008D2\u00CF\u00BD\x00\x00\x00\u00B5IDAT\x18\u0095\u008D\u0090K\n\u00840\x10D+C\u00FC\u00E5\x06\u00D9\u00B8\u00F7\n\"nt\u00E3\u0089\u00C5\u0095\u00E0!\u00B2\u00CC-\x14\u00F1\u0097\u00A1{\u00B0a\x10\u0086)(:\u00A4_WB\u00AB\u00BE\u00EF\x03\u00FE\u0090&\u00A4\u00AEk!\u0097e\u0081R\nY\u0096\u00C9\u00DD4M\x1F\u0090\u0094\u00A6)\u009Cs\u00F0\u00DE#I\x12\u00E4y\u008E\u00A2(x\x10w\"i]W\u0086\u00BA\u00AE\u00C3\u00BE\u00EF\x18\u00C7\x11\u00D6Z\u00C4q\u00CC\u00FD\u00D7\r\x1E\u00C7\u00C15\u008A\"N$x\u009Egy^\x12\u00E9_\u00D7ua\x18\x06i\u009E\u00E7)gI\f!0\u00DC\u00B6-\u009A\u00A6\u0091\u00E1G\u00A2\u00D6\u009A\u00BDm\x1B\x03dc\u00CC\x13\u00A4\u00C4\u00B2,\u00B9\u0092\u00AB\u00AA\u00E2M|\u0081\u00B4\u00A7\u009F\x02\u00F0\x06\x05\"C\u00AEZ\f\u00AC\x18\x00\x00\x00\x00IEND\u00AEB`\u0082"
}
this.scriptName = 'Relink layers'
this.errNoFiles = { ru: 'Связананные слои не найдены!', en: 'Linked layers not found!' }
this.findEmbedded = { ru: 'Поиск связанных файлов в смарт-объектах', en: 'Find linked files in smart objects' }
this.searchFile = { ru: 'Поиск файла: ', en: 'Search file: ' }
this.buildFileList = { ru: 'Обработка каталога: ', en: 'Build list of files: ' }
this.copyAssets = { ru: 'Сборка ресурсов', en: 'Collect assets' }
this.relink = { ru: 'Связать заново', en: 'Relink layers' }
this.linkedFiles = { ru: 'Связанные файлы', en: 'Linked files' }
this.findInEmbedded = { ru: 'поиск связанных файлов внутри содержимого встроенных смарт-объектов', en: 'find links inside contents of embedded smart objects' }
this.relinkAll = { ru: 'Связать заново', en: 'Relink all' }
this.extension = { ru: 'учитывать расширение', en: 'match file extension' }
this.subfolder = { ru: 'собрать ресурсы в папке:', en: 'collect assets in subfolder:' }
this.groupByExt = { ru: 'группировать по типу', en: 'group by extension' }
this.move = { ru: 'перемещать', en: 'move files' }
this.cancel = { ru: 'Отмена', en: 'Cancel' }
this.currentPath = { ru: 'папка открытого документа', en: 'current document path' }
this.newPath = { ru: 'выбрать папку...', en: 'choose folder...' }
this.relinkSelected = { ru: 'Связать выбранные', en: 'Relink selected' }
this.replaceFor = { ru: 'Заменить связь для ', en: 'Replace link for ' }
this.strRelinkErr1 = { ru: 'Замена связи не может быть выполнена!', en: 'Relink operation cannot be performed!' }
this.strRelinkErr2 = { ru: ' тип файлов не поддерживается!', en: ' files does not supported!' }
this.strRelinkErr3 = { ru: 'Путь недоступен!\n\n- активный документ не сохранен!', en: 'Path not found!\n\n- active document not saved yet!' }
this.err = { ru: 'Ошибка', en: 'Error' }
this.warning = { ru: 'Предупреждение', en: 'Warning' }
this.errGlobal = { ru: 'Произошла ошибка во время выполнения скрипта. Создать файл отчета?', en: 'An error occurred while executing the script. Create a report file? ' }
this.saveLog = { ru: 'Файл отчета', en: 'Report file' }
this.select = { ru: 'Быстрый выбор:', en: 'Quick select:' }
this.target = { ru: 'Путь назначения:', en: 'Target path:' }
}
function mainWindow(fileList, runMode) {
var w = new Window("dialog {text: '" + str.linkedFiles + " " + ver + "'}"),
gSelect = w.add("group{alignChildren: ['left','center'], alignment: ['fill','top']}"),
stSelect = gSelect.add("statictext{text:'" + str.select + "'}"),
iAll = gSelect.add("iconbutton", undefined, str.ico.all, { style: "toolbutton" }),
iGreen = gSelect.add("iconbutton", undefined, str.ico.green, { style: "toolbutton" }),
iYellow = gSelect.add("iconbutton", undefined, str.ico.yellow, { style: "toolbutton" }),
iRed = gSelect.add("iconbutton", undefined, str.ico.red, { style: "toolbutton" }),
l = w.add("listbox", [0, 0, 600, 400], undefined, { multiselect: true }),
gEmbedded = w.add("group{alignChildren: ['left','center'], alignment: ['fill','top']}"),
chEmbedded = gEmbedded.add("checkbox{text: '" + str.findInEmbedded + "', preferredSize: [70, -1]}"),
pnRelink = w.add("panel{text: '" + str.linkedFiles + "', alignChildren: ['left','center'], alignment: ['fill','top'], margins:[10,20,10,10]}"),
gRelink = pnRelink.add("group{alignChildren: ['left','center'], alignment: ['fill','top']}"),
rl = gRelink.add("button{text: '" + str.relinkAll + "', preferredSize: [170, -1]}"),
dl = gRelink.add("dropdownlist", undefined, undefined, { items: [str.currentPath, str.newPath] }),
chMatch = gRelink.add("checkbox{text: '" + str.extension + "', preferredSize: [120, -1]}"),
gPath = pnRelink.add("group{alignChildren: ['left','center'], alignment: ['fill','top']}"),
stPathLabel = gPath.add("statictext{text:'" + str.target + "'}"),
stPath = gPath.add("statictext{preferredSize: [450, -1]}"),
gCollect = w.add("group{alignChildren: ['left','center'], alignment: ['fill','top']}"),
chCollect = gCollect.add("checkbox{text: '" + str.subfolder + "', preferredSize: [170, -1]}"),
gSubCollect = gCollect.add("group{alignChildren: ['left','center'], alignment: ['fill','top']}"),
et = gSubCollect.add("edittext{preferredSize: [100, -1]}"),
chGroup = gSubCollect.add("checkbox{text: '" + str.groupByExt + "', preferredSize: [120, -1]}"),
chMove = gSubCollect.add("checkbox{text: '" + str.move + "', preferredSize: [70, -1]}"),
gButtons = w.add("group"),
bnOk = gButtons.add("button {text:'Ok'}", undefined, undefined, { name: "ok" }),
bnCancel = gButtons.add("button {text:'" + str.cancel + "'}", undefined, undefined, { name: "cancel" });
l.graphics.font = "dialog:12";
l.fillLinksList = function (items) {
var currentSelection = []
if (this.selection) for (var i = 0; i < this.selection.length; i++) currentSelection.push((this.selection[i]).index)
this.removeAll()
for (var i = 0; i < items.length; i++) {
with (items[i]) {
var cur = relink ? relink : link
this.add('item', cur instanceof File ?
((relink ? ' ✎ ' : (missed ? ' ' : ' ✔ ')) + (relink ? (relink).fsName : link.fsName)) :
(' ' + items[i].fileReference));
this.items[i].image = items[i].missed ? str.ico.red : (items[i].sameFolder ? str.ico.green : str.ico.yellow)
}
}
if (currentSelection.length) this.selection = currentSelection
}
l.onClick = function () {
rl.text = l.selection != null ? str.relinkSelected + (l.selection.length > 1 ? ' (' + l.selection.length + ')' : '') : str.relinkAll
}
l.onDoubleClick = function () {
if (l.selection != null) {
if ((l.selection[0]).index >= 0) {
with (fileList[(l.selection[0]).index]) {
var cur = link instanceof File ? link : new File,
f = fn.splitFilename(cur.openDlg(str.replaceFor + (link instanceof File ? decodeURI(link.name) : fileReference), '', false));
if (f && f.exists) {
if (allowedExtensions[f.extension.toUpperCase()]) {
relink = decodeURI(link).toUpperCase() == decodeURI(f).toUpperCase() ? null : f;
l.fillLinksList(fileList.checkFiles())
} else { alert(str.strRelinkErr1 + '\n\n*.' + f.extension + str.strRelinkErr2, str.err, 1) }
}
}
}
}
}
rl.onClick = function () {
if (fn.targetFolder) {
app.doForcedProgress('', 'fn.findLinks(fn.targetFolder, fileList, l.selection)')
l.fillLinksList(fileList.checkFiles())
}
}
dl.onChange = function () {
switch (this.selection.index) {
case 0:
fn.targetFolder = fn.documentFolder ? fn.documentFolder : null;
if (!fn.targetFolder && w.visible) alert(str.strRelinkErr3, str.err, 1)
break;
case 1:
if (w.visible) {
var p = (new Folder(fn.documentFolder ? fn.documentFolder : null)).selectDlg()
fn.targetFolder = p ? p : null;
} else {
fn.targetFolder = cfg.targetFolder != '' && Folder(cfg.targetFolder).exists ? Folder(cfg.targetFolder) : null
}
break;
}
cfg.relinkMode = this.selection.index
cfg.targetFolder = fn.targetFolder ? fn.targetFolder.fsName : ''
rl.enabled = bnOk.enabled = fn.targetFolder && fileList.length ? true : false;
stPath.text = fn.targetFolder ? fn.targetFolder.fsName : ''
}
chCollect.onClick = function () { gSubCollect.enabled = cfg.collect = this.value }
chGroup.onClick = function () { cfg.groupByExtension = this.value }
chMove.onClick = function () { cfg.move = this.value }
bnOk.onClick = function () {
if (fn.targetFolder == null) { rl.onClick(true) }
w.close(1);
(new AM()).putScriptSettings(cfg);
if (fn.targetFolder && cfg.collect) fn.collectAssets(fn.targetFolder, fileList)
}
chEmbedded.onClick = function () {
cfg.checkEmbedded = this.value
w.close(3)
}
et.onChange = function () {
cfg.subfolder = this.text
}
bnCancel.onClick = function () { fileList = []; w.close(2) }
chMatch.onClick = function () { cfg.matchExtension = this.value }
iAll.onClick = function () { qickSelect(0) }
iGreen.onClick = function () { qickSelect(1) }
iYellow.onClick = function () { qickSelect(2) }
iRed.onClick = function () { qickSelect(3) }
w.onShow = function () {
if (!fn.documentFolder) cfg.relinkMode = 1
dl.selection = cfg.relinkMode ? 1 : 0
chMatch.value = cfg.matchExtension
gSubCollect.enabled = chCollect.value = cfg.collect
et.text = cfg.subfolder
chGroup.value = cfg.groupByExtension
chMove.value = cfg.move
chEmbedded.value = cfg.checkEmbedded
chEmbedded.enabled = runMode
stPath.text = fn.targetFolder ? fn.targetFolder.fsName : ''
dl.size.width = w.size.width - rl.size.width - chMatch.size.width - 90
et.size.width = w.size.width - chGroup.size.width - chCollect.size.width - chMove.size.width - 80
w.layout.layout(true)
l.fillLinksList(fileList.checkFiles())
}
function qickSelect(mode) {
var selection = [],
len = fileList.length;
l.selection = selection;
switch (mode) {
case 1: for (var i = 0; i < len; i++) if (fileList[i].sameFolder && !fileList[i].missed) selection.push(i); break;
case 2: for (var i = 0; i < len; i++) if (!fileList[i].sameFolder && !fileList[i].missed) selection.push(i); break;
case 3: for (var i = 0; i < len; i++) if (fileList[i].missed) selection.push(i); break;
default: for (var i = 0; i < len; i++) selection.push(i);
}
l.selection = selection
l.onClick()
}
return w
}
Copy link to clipboard
Copied
to jazz-y
Привет. Прикольный скрипт. Даже страшно разбираться как он работает.
Подскажу идею, может как-то поможет или ускорит. Судя по коду ты её не используешь (а может это я слепой). Не знаю (в код не смотрел) как ты получаешь линки, в т.ч. и из вложенных смарт-объектов, но если в дескриптор для получения "json" документа вставить параметр d.putBoolean(stringIDToTypeID("expandSmartObjects"), true), то вся информация сразу по всем объектам, и вложенным тоже, будет в этом джсоне. Это происходит без явного открытия всех вложенных смарт-обектов.
ЗЫ. насчет парсинга psd тоже идея хорошая, но мне, честно, неохота этим заниматься. )
ЗЫ2. мне неинтересно, потому что у меня CS6. Там линков нет по-определению. Хотя я сделал аналог ещё когда СС и не пахло. Идея: в метаданных слоя хранится линк на файл. Заменить содержимое такого слоя даже с учетом трансформации, типа масштаб и поворот, на содержимое файла дело техники. Там же (в метаданных) хранится таймштапм для файла, чтоб контролировать актуальность того, что показывается в слое.
Copy link to clipboard
Copied
@r-bin оно работает через convert to layers, который появился в CC 2020 (то есть рекурсией раскрываю все объекты и собираю пути). С JSON есть проблема - прекрасно показывает путь и расширение исходного файла когда он на месте, но если речь идет о битой ссылке то подменяет относительный путь на папку temp, а расширение на psb. Для простого восстановления ссылок (по имени файла) это сойдет, но я слегка поздаморочился и писал, чтобы учитывался относительный путь файла и (опционально) его расширение.
Copy link to clipboard
Copied
THANK YOU, It works better now. (Whenever you got time, it would be awesome if you could reduce waiting time after clicking on relink)
Copy link to clipboard
Copied
I just want to re-iterate how amazing this script is. It has saved my day-to-day activities (and when i've moved a whole bunch of files to a new location by accident.)
Its a simple one button fix that Adobe should really think about including with every current version of Photoshop.
If anyone is on PC and can't get theirs to work, let me know and ill send you the exact JSX script and file location that I ended up putting it in. It works a charm!!
Well done Jazz-y!!! Now.. Can you make one for Illustrator?!
Copy link to clipboard
Copied
@jazz-y wrote:
The main problem is that when opening each file with broken links, Photoshop shows a modal window that stops the script. This completely destroys the ability to batch process multiple files without user interaction.
Hmmm... Changing the idforceNotify boolean to false suppresses the dialog and opens the image with the broken link waiting to be updated.
The following proof of concept script works with a single file with a single layer. I'll look into extending this base operation for a batch:
// Select the file
var theFile = File.openDialog("Please select the file to update the missing link:");
// Select the folder to relink to
var theNewFolder = Folder.selectDialog('Please select the new folder to relink to:');
// Open the file suppressing the missing link dialog
var idopen = stringIDToTypeID("open");
var desc567 = new ActionDescriptor();
var iddontRecord = stringIDToTypeID( "dontRecord" );
desc567.putBoolean( iddontRecord, false );
var idforceNotify = stringIDToTypeID( "forceNotify" );
desc567.putBoolean( idforceNotify, false );
var idnull = stringIDToTypeID( "null" );
desc567.putPath( idnull, new File( theFile ) ); // the active doc
var iddocumentID = stringIDToTypeID( "documentID" );
desc567.putInteger( iddocumentID, 1081 );
executeAction(idopen, desc567, DialogModes.NO);
// Get the missing link path
/*
Based on:
https://stackoverflow.com/questions/63010107/get-a-smart-objects-layers-files-directory-source-in-jsx
*/
ref = new ActionReference();
ref.putProperty(charIDToTypeID("Prpr"), stringIDToTypeID("smartObject"));
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
var so = executeActionGet(ref).getObjectValue(stringIDToTypeID("smartObject"));
so.getBoolean(stringIDToTypeID("linked"));
so.getString(stringIDToTypeID("fileReference"));
var missingLinkPath = so.getPath(stringIDToTypeID("link"));
var missingFile = missingLinkPath.toString().replace(/(^~.+\/)(.+)/, '$2');
// Relink the missing asset
var idplacedLayerRelinkToFile = stringIDToTypeID( "placedLayerRelinkToFile" );
var desc579 = new ActionDescriptor();
var idnull = stringIDToTypeID( "null" );
desc579.putPath( idnull, new File( theNewFolder + "/" + missingFile ) );
executeAction(idplacedLayerRelinkToFile, desc579, DialogModes.NO);
Copy link to clipboard
Copied
Thanks for the example! I use this option so rarely that I completely forgot to check. Indeed, with idforceNotify = false, the dialog with the relink offer does not appear!
My thoughts were directed specifically to using the standard batch (so that, in addition to relinking, when opening a file, other automation could be used), but it seems easier to add a batch processing module inside the script itself.
I'll definitely do this when I have some free time...
Copy link to clipboard
Copied
@jazz-y wrote:
Thanks for the example! I use this option so rarely that I completely forgot to check. Indeed, with idforceNotify = false, the dialog with the relink offer does not appear!
It must be beginner's luck! This is probably the first time that I have looked at this problem, so I had to attack it with no preconceptions.
My thoughts were directed specifically to using the standard batch (so that, in addition to relinking, when opening a file, other automation could be used), but it seems easier to add a batch processing module inside the script itself.
I'll definitely do this when I have some free time...
Please do so, I have been bashing away at this for a few hours now without any success.
I made a batch, but it only works with a single-layer file (or a file where the linked SO can be easily targeted). I have not had any success looping over all layers/sets and only processing a linked smart object, ignoring anything else.
/*
Batch relink missing SO layer to new folder.jsx
https://community.adobe.com/t5/photoshop-ecosystem-discussions/how-to-relink-files-in-multiple-photoshop-files/td-p/13011537
v1.0 - Stephen Marsh, 18th June 2022
*/
#target photoshop
// Select the broken links folder
var brokenLinkInput = Folder.selectDialog('Please select the broken links files directory:');
// Get the PSD & PSB files
var brokenLinkFiles = brokenLinkInput.getFiles(/\.(psd|psb)$/i);
// Select the folder to relink to
var theNewFolder = Folder.selectDialog('Please select the new directory to relink to:');
// Start the file processing counter at zero
var counter = 0;
for (var i = 0; i < brokenLinkFiles.length; i++) {
openSuppress(brokenLinkFiles[i]);
// TO DO: ADD CODE TO LOOP OVER ALL LAYERS & PROCESS ONLY LINKED SO LAYERS!
relinkPath();
activeDocument.close(SaveOptions.SAVECHANGES);
// Increment the file processing counter for each loop
counter++;
}
// End of script notification
app.beep();
alert('Script completed!' + '\r' + counter + ' files saved to:' + '\r' + brokenLinkInput.fsName);
/* Functions */
function openSuppress(theFiles) {
// Open the file suppressing the missing link dialog
var idopen = stringIDToTypeID("open");
var desc567 = new ActionDescriptor();
var iddontRecord = stringIDToTypeID("dontRecord");
desc567.putBoolean(iddontRecord, false);
var idforceNotify = stringIDToTypeID("forceNotify");
desc567.putBoolean(idforceNotify, false);
var idnull = stringIDToTypeID("null");
desc567.putPath(idnull, new File( theFiles ));
var iddocumentID = stringIDToTypeID("documentID");
desc567.putInteger(iddocumentID, 1081);
executeAction(idopen, desc567, DialogModes.NO);
}
function relinkPath() {
// Get the link path
/*
Based on:
https://stackoverflow.com/questions/63010107/get-a-smart-objects-layers-files-directory-source-in-jsx
*/
ref = new ActionReference();
ref.putProperty(charIDToTypeID("Prpr"), stringIDToTypeID("smartObject"));
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
var so = executeActionGet(ref).getObjectValue(stringIDToTypeID("smartObject"));
so.getBoolean(stringIDToTypeID("linked"));
so.getString(stringIDToTypeID("fileReference"));
var missingLinkPath = so.getPath(stringIDToTypeID("link"));
var missingFile = missingLinkPath.toString().replace(/(^~.+\/)(.+)/, '$2');
// Relink
var idplacedLayerRelinkToFile = stringIDToTypeID("placedLayerRelinkToFile");
var desc579 = new ActionDescriptor();
var idnull = stringIDToTypeID("null");
desc579.putPath(idnull, new File(theNewFolder + "/" + missingFile));
executeAction(idplacedLayerRelinkToFile, desc579, DialogModes.NO);
}
Copy link to clipboard
Copied
// TO DO: ADD CODE TO LOOP OVER ALL LAYERS & PROCESS ONLY LINKED SO LAYERS!
#target photoshop
s2t = stringIDToTypeID;
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('numberOfLayers'));
r.putEnumerated(s2t('document'), s2t('ordinal'), s2t('targetEnum'));
var len = executeActionGet(r).getInteger(p),
linkedLayers = [];
for (var i = 1; i <= len; i++) {
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('layerSection'));
r.putIndex(s2t('layer'), i);
if (typeIDToStringID(executeActionGet(r).getEnumerationValue(p)) == 'layerSectionEnd') continue;
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('layerKind'));
r.putIndex(s2t('layer'), i);
if (executeActionGet(r).getInteger(p) == 5) {
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('smartObject'));
r.putIndex(s2t('layer'), i);
if (executeActionGet(r).getObjectValue(p).getBoolean(s2t('linked'))) {
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('layerID'));
r.putIndex(s2t('layer'), i);
linkedLayers.push(executeActionGet(r).getInteger(p))
}
};
}
alert('linkedLayersIDs:\n' + linkedLayers.toSource())
do {
(r = new ActionReference()).putIdentifier(s2t("layer"), linkedLayers.shift());
(d = new ActionDescriptor()).putReference(s2t("target"), r);
d.putBoolean(s2t('makeVisible'), false);
executeAction(s2t("select"), d, DialogModes.NO);
alert ('linked layer with id ' + d.getReference(s2t('target')).getIdentifier(s2t('layer')) +' selected!')
} while (linkedLayers.length)