• Global community
    • Language:
      • Deutsch
      • English
      • EspaƱol
      • FranƧais
      • PortuguĆŖs
  • ę—„ęœ¬čŖžć‚³ćƒŸćƒ„ćƒ‹ćƒ†ć‚£
    Dedicated community for Japanese speakers
  • ķ•œźµ­ ģ»¤ė®¤ė‹ˆķ‹°
    Dedicated community for Korean speakers
Exit Search
0

How to relink files in multiple Photoshop files

Participant ,
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

1.2K

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

Community Expert , 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 = 
...

Votes

Translate

Translate
Community Expert ,
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 šŸ˜ž

Votes

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
Participant ,
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)

Votes

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

Votes

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

Votes

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
Community Expert ,
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.

Votes

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
Community Expert ,
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?). 

Votes

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

Votes

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
Community Expert ,
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 ĀÆ\_(惄)_/ĀÆ

Votes

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
Participant ,
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 šŸ˜•

Votes

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
Participant ,
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 šŸ™‚

Votes

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
Community Expert ,
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

Votes

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

Votes

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
Community Expert ,
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
}

 

 

Votes

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
Valorous Hero ,
Jun 17, 2022 Jun 17, 2022

Copy link to clipboard

Copied

to jazz-y

 

ŠŸŃ€ŠøŠ²ŠµŃ‚. ŠŸŃ€ŠøŠŗŠ¾Š»ŃŒŠ½Ń‹Š¹ скрипт. Даже ŃŃ‚Ń€Š°ŃˆŠ½Š¾ Ń€Š°Š·Š±ŠøŃ€Š°Ń‚ŃŒŃŃ как он работает.

 

ПоГскажу ŠøŠ“ŠµŃŽ, может как-то поможет или ŃƒŃŠŗŠ¾Ń€ŠøŃ‚. Š”ŃƒŠ“Ń по коГу ты её не используешь (а может ŃŃ‚Š¾ я слепой). ŠŠµ Š·Š½Š°ŃŽ (в коГ не смотрел) как ты ŠæŠ¾Š»ŃƒŃ‡Š°ŠµŃˆŃŒ линки, в т.ч. Šø ŠøŠ· вложенных смарт-Š¾Š±ŃŠŠµŠŗŃ‚Š¾Š², но если в Гескриптор Š“Š»Ń ŠæŠ¾Š»ŃƒŃ‡ŠµŠ½ŠøŃ "json" Š“Š¾ŠŗŃƒŠ¼ŠµŠ½Ń‚Š° Š²ŃŃ‚Š°Š²ŠøŃ‚ŃŒ параметр d.putBoolean(stringIDToTypeID("expandSmartObjects"), true), то Š²ŃŃ ŠøŠ½Ń„Š¾Ń€Š¼Š°Ń†ŠøŃ ŃŃ€Š°Š·Ńƒ по всем Š¾Š±ŃŠŠµŠŗŃ‚ам, Šø вложенным тоже, Š±ŃƒŠ“ŠµŃ‚ в ŃŃ‚Š¾Š¼ Гжсоне.  Это происхоГит без ŃŠ²Š½Š¾Š³Š¾ Š¾Ń‚ŠŗŃ€Ń‹Ń‚ŠøŃ всех вложенных смарт-обектов.

 

Š—Š«. насчет парсинга psd тоже ŠøŠ“ŠµŃ Ń…Š¾Ń€Š¾ŃˆŠ°Ń, но мне, честно, неохота ŃŃ‚ŠøŠ¼ Š·Š°Š½ŠøŠ¼Š°Ń‚ŃŒŃŃ. )

Š—Š«2. мне неинтересно, ŠæŠ¾Ń‚Š¾Š¼Ńƒ что у Š¼ŠµŠ½Ń CS6. Там линков нет по-Š¾ŠæŃ€ŠµŠ“ŠµŠ»ŠµŠ½ŠøŃŽ. Š„Š¾Ń‚Ń я сГелал аналог ещё когГа ДД Šø не пахло. Š˜Š“ŠµŃ: в метаГанных ŃŠ»Š¾Ń Ń…Ń€Š°Š½ŠøŃ‚ŃŃ линк на файл. Š—Š°Š¼ŠµŠ½ŠøŃ‚ŃŒ соГержимое такого ŃŠ»Š¾Ń Гаже с ŃƒŃ‡ŠµŃ‚Š¾Š¼ трансформации, типа Š¼Š°ŃŃˆŃ‚аб Šø поворот, на соГержимое файла Гело техники. Там же (в метаГанных) Ń…Ń€Š°Š½ŠøŃ‚ŃŃ Ń‚Š°Š¹Š¼ŃˆŃ‚Š°ŠæŠ¼ Š“Š»Ń файла, чтоб ŠŗŠ¾Š½Ń‚Ń€Š¾Š»ŠøŃ€Š¾Š²Š°Ń‚ŃŒ Š°ŠŗŃ‚ŃƒŠ°Š»ŃŒŠ½Š¾ŃŃ‚ŃŒ того, что ŠæŠ¾ŠŗŠ°Š·Ń‹Š²Š°ŠµŃ‚ŃŃ в слое.

 

 

Votes

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
Community Expert ,
Jun 17, 2022 Jun 17, 2022

Copy link to clipboard

Copied

@r-bin Š¾Š½Š¾ работает через convert to layers, который ŠæŠ¾ŃŠ²ŠøŠ»ŃŃ в CC 2020 (то ŠµŃŃ‚ŃŒ Ń€ŠµŠŗŃƒŃ€ŃŠøŠµŠ¹ Ń€Š°ŃŠŗŃ€Ń‹Š²Š°ŃŽ все Š¾Š±ŃŠŠµŠŗŃ‚Ń‹ Šø ŃŠ¾Š±ŠøŃ€Š°ŃŽ ŠæŃƒŃ‚Šø). Š” JSON ŠµŃŃ‚ŃŒ проблема - прекрасно показывает ŠæŃƒŃ‚ŃŒ Šø Ń€Š°ŃŃˆŠøŃ€ŠµŠ½ŠøŠµ исхоГного файла когГа он на месте, но если Ń€ŠµŃ‡ŃŒ иГет о битой ссылке то ŠæŠ¾Š“Š¼ŠµŠ½ŃŠµŃ‚ Š¾Ń‚Š½Š¾ŃŠøŃ‚ŠµŠ»ŃŒŠ½Ń‹Š¹ ŠæŃƒŃ‚ŃŒ на папку temp, а Ń€Š°ŃŃˆŠøŃ€ŠµŠ½ŠøŠµ на psb. Š”Š»Ń простого Š²Š¾ŃŃŃ‚Š°Š½Š¾Š²Š»ŠµŠ½ŠøŃ ссылок (по имени файла) ŃŃ‚Š¾ сойГет, но я слегка ŠæŠ¾Š·Š“Š°Š¼Š¾Ń€Š¾Ń‡ŠøŠ»ŃŃ Šø писал, чтобы ŃƒŃ‡ŠøŃ‚Ń‹Š²Š°Š»ŃŃ Š¾Ń‚Š½Š¾ŃŠøŃ‚ŠµŠ»ŃŒŠ½Ń‹Š¹ ŠæŃƒŃ‚ŃŒ файла Šø (Š¾ŠæŃ†ŠøŠ¾Š½Š°Š»ŃŒŠ½Š¾) его Ń€Š°ŃŃˆŠøŃ€ŠµŠ½ŠøŠµ. 

 

 

Votes

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
Participant ,
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)

Votes

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
Community Expert ,
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);

 

Votes

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
Community Expert ,
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...

Votes

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
Community Expert ,
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);
}

 

Votes

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
Community Expert ,
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)

 

 

Votes

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