How to relink files in multiple Photoshop files

Explorer ,
Jun 16, 2022 Jun 16, 2022

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.

Is there a better way to relink them all at once? (with/without Script)

TOPICS
Actions and scripting , Cross-app workflows

Views

275

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Adobe Community Professional , Jun 17, 2022 Jun 17, 2022
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 = ...

Likes

Translate

Translate
Adobe Community Professional ,
Jun 17, 2022 Jun 17, 2022

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 😞

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jun 17, 2022 Jun 17, 2022

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)

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Jun 17, 2022 Jun 17, 2022

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, …? 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jun 17, 2022 Jun 17, 2022

Copy link to clipboard

Copied

I had my old structure file order: oldData.png

1- Photoshop File

2- Photoshop linked-Files

3- My Old Files-Structure / Order

 

I moved just the photosohp linked files in my (newData) folder: newData.png

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.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Jun 17, 2022 Jun 17, 2022

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.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Jun 17, 2022 Jun 17, 2022

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?). 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jun 17, 2022 Jun 17, 2022

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?

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Jun 17, 2022 Jun 17, 2022

Copy link to clipboard

Copied

Nothing is impossible.

I see four options:

  • suppress broken link window by operating system automation (with a certain delay, send the 'enter' press after opening the file), but this method has its limitations (depends on the operating system and the rights of the current user)
  • parse the source file looking for links and change them there (I have taken some steps in this direction, but not so advanced in working with binary files as @r-bin )
  • allow the user to close this window and continue the script
  • configurably ask Adobe to remove the window with a notification of broken links

 

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 ¯\_(ツ)_/¯

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jun 17, 2022 Jun 17, 2022

Copy link to clipboard

Copied

I tried your first option. It works like a charm 🙂

but with a (pretty much) long delay 😕

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jun 17, 2022 Jun 17, 2022

Copy link to clipboard

Copied

Thanks a lot. I dont have to search for the one single file in a bunch of files 🙂

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Jun 17, 2022 Jun 17, 2022

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.

2022-06-17_12-19-47.png

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jun 17, 2022 Jun 17, 2022

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?

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Jun 17, 2022 Jun 17, 2022

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
}

 

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Jun 17, 2022 Jun 17, 2022

Copy link to clipboard

Copied

to jazz-y

 

Привет. Прикольный скрипт. Даже страшно разбираться как он работает.

 

Подскажу идею, может как-то поможет или ускорит. Судя по коду ты её не используешь (а может это я слепой). Не знаю (в код не смотрел) как ты получаешь линки, в т.ч. и из вложенных смарт-объектов, но если в дескриптор для получения "json" документа вставить параметр d.putBoolean(stringIDToTypeID("expandSmartObjects"), true), то вся информация сразу по всем объектам, и вложенным тоже, будет в этом джсоне.  Это происходит без явного открытия всех вложенных смарт-обектов.

 

ЗЫ. насчет парсинга psd тоже идея хорошая, но мне, честно, неохота этим заниматься. )

ЗЫ2. мне неинтересно, потому что у меня CS6. Там линков нет по-определению. Хотя я сделал аналог ещё когда СС и не пахло. Идея: в метаданных слоя хранится линк на файл. Заменить содержимое такого слоя даже с учетом трансформации, типа масштаб и поворот, на содержимое файла дело техники. Там же (в метаданных) хранится таймштапм для файла, чтоб контролировать актуальность того, что показывается в слое.

 

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Jun 17, 2022 Jun 17, 2022

Copy link to clipboard

Copied

@r-bin оно работает через convert to layers, который появился в CC 2020 (то есть рекурсией раскрываю все объекты и собираю пути). С JSON есть проблема - прекрасно показывает путь и расширение исходного файла когда он на месте, но если речь идет о битой ссылке то подменяет относительный путь на папку temp, а расширение на psb. Для простого восстановления ссылок (по имени файла) это сойдет, но я слегка поздаморочился и писал, чтобы учитывался относительный путь файла и (опционально) его расширение. 

 

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jun 24, 2022 Jun 24, 2022

Copy link to clipboard

Copied

LATEST

THANK YOU, It works better now. (Whenever you got time, it would be awesome if you could reduce waiting time after clicking on relink)

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Jun 17, 2022 Jun 17, 2022

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);

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Jun 17, 2022 Jun 17, 2022

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...

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Jun 17, 2022 Jun 17, 2022

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);
}

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Jun 18, 2022 Jun 18, 2022

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)

 

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines