// Photoshop Action Runner_V01 #targetengine "session" var SCRIPT_NAME="Photoshop Action Runner_V01"; function main() { if (app.documents.length === 0) { alert("No document open."); return; } var doc = app.activeDocument; // ---------- Helpers ---------- function extLower(nm){ var m=/\.[^\.]+$/.exec(nm||""); return m?m[0].toLowerCase():""; } function ensureFile(link){ var fpath=link.filePath||link.filePath; if(!fpath) return null; var f=new File(fpath); return f.exists?f:null; } function getColorSpaceOfLink(lnk){ try{ if(lnk && lnk.parent && lnk.parent.parent && lnk.parent.parent.graphics && lnk.parent.parent.graphics.length>0){ var gr=lnk.parent.parent.graphics[0]; if(gr && gr.space) return gr.space.toString(); } }catch(e){} try{ if(lnk && lnk.linkXmp && lnk.linkXmp.properties && lnk.linkXmp.properties.colorSpace){ return lnk.linkXmp.properties.colorSpace; } }catch(e2){} return ""; } function getColorProfileOfLink(lnk){ try{ if(lnk && lnk.linkXmp && lnk.linkXmp.properties){ var p=lnk.linkXmp.properties; if(p.iccProfileName) return p.iccProfileName; if(p.colorProfile) return p.colorProfile; if(p.profileName) return p.profileName; if(p.profileDescription) return p.profileDescription; } }catch(e){} return ""; } // ---------- Collect supported links (RGB/CMYK/etc.) ---------- var allowedExts={".psd":true,".tif":true,".tiff":true,".jpg":true,".jpeg":true}; var links=doc.links, rgbLinks=[]; for(var i=0;ib?1:0); }); for(var k=0;k0){ var selIndex=-1; if(prefs.psTarget){ for(var i2=0;i2=0 && t1.indexOf("2026")>=0){ selIndex=b1; break; } } } if(selIndex<0){ for(var b2=0;b2=0){ selIndex=b2; break; } } } if(selIndex<0){ for(var b3=0;b30?parts[0]:"Photoshop"; var ver=parts.length>1?parts[1]:"unknown"; psDetail.text="Connected target: "+name+" v"+ver+" ("+tgt+")"; }; bt.onError=function(){ psDetail.text="Target details unavailable for "+tgt; }; bt.send(); }catch(e){ psDetail.text="Target details unavailable for "+tgt; } } ddPsTargets.onChange=function(){ $.global.__RGB_BIN_PREFS__.psTarget=getChosenPsTarget(); updatePsCallout(); updatePsDetail(); loadActionsFromPS(); }; updatePsCallout(); updatePsDetail(); // ---------- Load actions from Photoshop ---------- function setActionUIDisabled(disabled, showHint){ ddSet.enabled=!disabled; ddAction.enabled=!disabled; emptyHint.visible=!!showHint; } function populateActionsForSet(setName){ ddAction.removeAll(); var arr=actionsBySet[setName]||[]; for(var i=0;i0){ for(var i=0;i0){ for(var g=0; g0){ for(var ag=0; ag=items.length){ status.text="Profiles refreshed. Success: "+ok+", Failed: "+fail+"."; if(showDoneAlert) alert("Profile refresh finished. Success: "+ok+", Failed: "+fail+"."); progressBar.value=0; progressBar.visible=false; return; } var rec=items[idx++]; status.text="["+idx+"/"+items.length+"] Reading profile for "+rec.name+"..."; var currentFile=getCurrentFileForRecord(rec); if(!currentFile){ rec.profile=getColorProfileOfLink(rec.link)||getColorSpaceOfLink(rec.link)||rec.profile||""; updateRowDisplay(rec); fail++; progressBar.value=Math.round((ok+fail)*100/items.length); nextProfile(); return; } fetchProfileFromPS(currentFile, function(payload){ var prof="", mode="", icc=""; try{ var parts=payload.split("##"); prof=parts.length>0?parts[0]:""; icc=parts.length>1?parts[1]:""; mode=parts.length>2?parts[2]:""; }catch(e){} rec.profile=prof||icc||mode||getColorProfileOfLink(rec.link)||getColorSpaceOfLink(rec.link)||"Unknown"; updateRowDisplay(rec); ok++; progressBar.value=Math.round((ok+fail)*100/items.length); nextProfile(); }, function(){ rec.profile=getColorProfileOfLink(rec.link)||getColorSpaceOfLink(rec.link)||rec.profile||"Unknown"; updateRowDisplay(rec); fail++; progressBar.value=Math.round((ok+fail)*100/items.length); nextProfile(); }); } nextProfile(); } function psdPathFromFileName(name){ if(/\.[^\.]+$/.test(name)) return name.replace(/\.[^\.]+$/, ".psd"); return name+".psd"; } function getSourceAndPsdDest(rec){ var srcPath=""; try{ srcPath=rec && rec.link && rec.link.filePath ? rec.link.filePath : ""; }catch(e){} if(!srcPath && rec && rec.file) srcPath=rec.file.fsName; var srcFile=new File(srcPath); var psdName=psdPathFromFileName(srcFile.name); var dstFile=new File(srcFile.path+"/"+psdName); return {src:srcFile,dst:dstFile,psdName:psdName}; } function relinkGracefully(rec, targetFile){ try{ rec.link.relink(targetFile); rec.link.update(); return true; }catch(e){ // If already linked to the exact same file, treat as success. try{ var fp=rec && rec.link ? rec.link.filePath : ""; if(fp){ var cur=new File(fp); if(cur.exists && targetFile.exists){ var curPath=cur.fsName; var tgtPath=targetFile.fsName; if(curPath===tgtPath) return true; } } }catch(e2){} return false; } } function computeFinalRelinkSummary(items){ var ok=0, fail=0, failedNames=[]; for(var i=0;i=items.length){ var finalSummary=computeFinalRelinkSummary(items); ok=finalSummary.ok; fail=finalSummary.fail; status.text="PSD relink complete. Success: "+ok+", Failed: "+fail+"."; updateLinksForItems(items); refreshProfilesForItems(items, false); if(fail===0) alert("PSD relink has successfully finished. Success: "+ok+", Failed: 0."); else if(ok>0) alert("PSD relink finished with warnings. Success: "+ok+", Failed: "+fail+".\nFailed: "+finalSummary.failedNames.join(", ")); else alert("PSD relink failed. Success: 0, Failed: "+fail+"."); progressBar.value=0; progressBar.visible=false; return; } var rec=items[idx++]; var io=getSourceAndPsdDest(rec); var src=io.src; var dst=io.dst; status.text="["+(idx)+"/"+items.length+"] Converting "+src.name+" to PSD..."; var srcExt=extLower(src.name); // If the source is already PSD, just relink/update and continue. if(srcExt===".psd"){ try{ if(relinkGracefully(rec, src)){ rec.file=src; rec.fsName=src.fsName; rec.name=src.name; rec.profile=getColorProfileOfLink(rec.link)||getColorSpaceOfLink(rec.link)||rec.profile||""; updateRowDisplay(rec); ok++; } else { fail++; status.text="Relink failed for existing PSD: "+src.name; alert("Relink failed for "+src.name); } }catch(e0){ fail++; status.text="Relink failed for existing PSD: "+src.name+" ("+e0+")"; alert("Relink failed for "+src.name+"\n"+e0); } progressBar.value=Math.round((ok+fail)*100/items.length); nextRelink(); return; } var psCode=[ "(function(){ try{", "app.displayDialogs=DialogModes.NO; var src=File("+uneval(src.fullName)+"); var dst=File("+uneval(dst.fullName)+");", "if(!src.exists) return 'ERR:File not found: '+src.fsName;", "var d=app.open(src); if(!d) return 'ERR:Open failed for '+src.fsName;", "var ps=new PhotoshopSaveOptions(); ps.embedColorProfile=true; ps.layers=true; ps.alphaChannels=true;", "try{ d.saveAs(dst, ps, true, Extension.LOWERCASE); }catch(se){ try{ if(d&&d.isValid) d.close(SaveOptions.DONOTSAVECHANGES); }catch(e1){} return 'ERR:SaveAs failed: '+se; }", "try{ if(d&&d.isValid) d.close(SaveOptions.DONOTSAVECHANGES); }catch(e2){}", "return 'OK:'+dst.fsName;", "}catch(e){ return 'ERR:'+e; } })()" ].join("\n"); try{ var bt=new BridgeTalk(); bt.target=tgt; bt.body=psCode; bt.onResult=function(res){ try{ var body=res&&res.body?res.body:""; var relinked=false; function relinkAndMark(psdFile){ if(!relinkGracefully(rec, psdFile)) throw new Error("Relink could not be confirmed."); rec.file=psdFile; rec.fsName=psdFile.fsName; rec.name=psdFile.name; rec.profile=getColorProfileOfLink(rec.link)||getColorSpaceOfLink(rec.link)||rec.profile||""; updateRowDisplay(rec); ok++; relinked=true; } if(body.indexOf("ERR:")===0){ // Some Photoshop builds can return an error string even if the file was still written. if(dst.exists){ try{ relinkAndMark(dst); status.text="Converted with warning for "+src.name+": "+body.substring(4); }catch(eWarn){ fail++; status.text="PSD conversion warning + relink failed for "+src.name+": "+eWarn; alert("PSD conversion warning and relink failed for "+src.name+"\n"+body+"\n"+eWarn); } } else { fail++; status.text="PSD conversion failed for "+src.name+": "+body.substring(4); alert("PSD conversion failed for "+src.name+"\n"+body); } progressBar.value=Math.round((ok+fail)*100/items.length); nextRelink(); return; } var outPath=body.indexOf("OK:")===0?body.substring(3):""; if(outPath && outPath.replace) outPath=outPath.replace(/^\s+|\s+$/g, ""); var psdFile=outPath?new File(outPath):dst; if(psdFile.exists){ relinkAndMark(psdFile); } else { fail++; status.text="PSD not found after save: "+dst.fsName; alert("PSD was not created for "+src.name+"\nExpected: "+dst.fsName); } }catch(e){ fail++; status.text="Error relinking: "+e; } progressBar.value=Math.round((ok+fail)*100/items.length); nextRelink(); }; bt.onError=function(err){ // Some BridgeTalk errors still produce the destination file. if(dst.exists){ try{ if(relinkGracefully(rec, dst)){ rec.file=dst; rec.fsName=dst.fsName; rec.name=dst.name; rec.profile=getColorProfileOfLink(rec.link)||getColorSpaceOfLink(rec.link)||rec.profile||""; updateRowDisplay(rec); ok++; status.text="BridgeTalk warning but PSD created for "+src.name; } else { fail++; status.text="BridgeTalk warning and relink could not be confirmed for "+src.name; } }catch(eBt){ fail++; status.text="BridgeTalk error + relink failed: "+(err&&err.body?err.body:err); alert("Error on "+src.name+": "+(err&&err.body?err.body:err)+"\n"+eBt); } } else { fail++; status.text="BridgeTalk error: "+(err&&err.body?err.body:err); alert("Error on "+src.name+": "+(err&&err.body?err.body:err)); } progressBar.value=Math.round((ok+fail)*100/items.length); nextRelink(); }; bt.send(); }catch(e){ fail++; status.text="Exception on "+src.name+": "+e; alert("Error: "+e); nextRelink(); } } nextRelink(); } // ---------- Processing (run chosen action only) ---------- function processItems(items){ if(!items||items.length===0){ alert("No items selected."); return; } items=normalizeSelectedItems(items); var choice=getChosenActionAndSet(), actionName=choice.action, actionSet=choice.set, tgt=getChosenPsTarget(); progressBar.visible=true; progressBar.value=0; status.text="Running action on "+items.length+" file(s)..."; var idx=0, ok=0, fail=0; function nextStep(){ if(idx>=items.length){ status.text="Done. Success: "+ok+", Failed: "+fail+". Updating links..."; updateLinksForItems(items); refreshProfilesForItems(items, false); status.text="Completed. Success: "+ok+", Failed: "+fail+"."; alert("Action has successfully finished. Success: "+ok+", Failed: "+fail+"."); progressBar.value=0; progressBar.visible=false; return; } var rec=items[idx++]; try{ rec.link.update(); }catch(_u){} var currentFile=getExactLinkedFile(rec); if(!currentFile || !currentFile.exists){ fail++; status.text="File missing for "+(rec.name||("Item "+idx)); progressBar.value=Math.round((ok+fail)*100/items.length); nextStep(); return; } // Keep the record synchronized to the exact linked file being processed. rec.file=currentFile; rec.fsName=currentFile.fsName; rec.name=currentFile.name; updateRowDisplay(rec); status.text="["+idx+"/"+items.length+"] Running action on "+currentFile.name+"..."; var shouldRetryRgbAction=(/rgb/i.test(actionName) && /(cmyk|gracol|convert)/i.test(actionName)); var psCodeAction=[ "(function(){ try{", "app.displayDialogs=DialogModes.NO; var f=File("+uneval(currentFile.fullName)+"); if(!f.exists) return 'ERR:File not found';", "var doc=app.open(f); if(!doc) return 'ERR:Could not open file';", "try{ app.doAction("+uneval(actionName)+", "+uneval(actionSet)+"); } catch(ae){", " var retried=false;", " if("+String(shouldRetryRgbAction)+"){", " try{", " if(doc.bitsPerChannel && doc.bitsPerChannel!=BitsPerChannelType.EIGHT) doc.bitsPerChannel=BitsPerChannelType.EIGHT;", " if(doc.mode && doc.mode!=DocumentMode.RGB) doc.changeMode(ChangeMode.RGB);", " app.doAction("+uneval(actionName)+", "+uneval(actionSet)+");", " retried=true;", " }catch(ae2){}", " }", " if(!retried){ if(doc && doc.isValid) doc.close(SaveOptions.DONOTSAVECHANGES); return 'ERR:Action failed: '+ae; }", "}", "if(!doc || !doc.isValid) return 'OK:Document already closed by action';", "var nm=f.name; var ext=(nm.match(/\\.[^\\.]+$/)||[''])[0].toLowerCase();", "try{", " if(ext==='.psd'){ var ps=new PhotoshopSaveOptions(); ps.embedColorProfile=true; doc.saveAs(f, ps, true, Extension.LOWERCASE); }", " else if(ext==='.tif'||ext==='.tiff'){ var to=new TiffSaveOptions(); to.imageCompression=TIFFEncoding.TIFFLZW; to.embedColorProfile=true; doc.saveAs(f, to, true, Extension.LOWERCASE); }", " else if(ext==='.jpg'||ext==='.jpeg'){ var jo=new JPEGSaveOptions(); jo.quality=12; jo.embedColorProfile=true; doc.saveAs(f, jo, true, Extension.LOWERCASE); }", " else { doc.save(); }", "} catch(se){", " try{ if(doc && doc.isValid) doc.save(); }catch(se2){ if(doc && doc.isValid) doc.close(SaveOptions.DONOTSAVECHANGES); return 'ERR:Save failed: '+se; }", "}", "if(doc && doc.isValid) doc.close(SaveOptions.DONOTSAVECHANGES); return 'OK'; } catch(e){ return 'ERR:'+e; } })()" ].join("\n"); try{ var bt=new BridgeTalk(); bt.target=tgt; bt.body=psCodeAction; bt.onResult=function(res){ var body=res&&res.body?res.body:""; if(body.indexOf("ERR:")===0){ fail++; status.text="Error running action on "+currentFile.name+": "+body.substring(4); alert("Failed to run action on "+currentFile.name+"\n"+body); } else { ok++; status.text="Successfully processed "+currentFile.name; } progressBar.value=Math.round((ok+fail)*100/items.length); nextStep(); }; bt.onError=function(err){ fail++; status.text="BridgeTalk error on action"; alert("Error running action on "+currentFile.name+"\n"+(err&&err.body?err.body:err)); progressBar.value=Math.round((ok+fail)*100/items.length); nextStep(); }; bt.send(); }catch(e){ fail++; status.text="Exception"; alert("Exception: "+e); progressBar.value=Math.round((ok+fail)*100/items.length); nextStep(); } } nextStep(); } runAllBtn.onClick=function(){ progressBar.value=0; processItems(rgbLinks); }; runSelBtn.onClick=function(){ var items=getSelectedItems(); if(items.length===0){ alert("Please select one or more items in the list, or select linked frames on the page."); return; } progressBar.value=0; status.text="Running selected action on "+items.length+" selected image(s)..."; processItems(items); }; relinkPsdBtn.onClick=function(){ var items=getSelectedItems(); if(items.length===0){ alert("Please select one or more items in the list, or select linked frames on the page."); return; } relinkItemsToPsd(items); }; refreshProfilesBtn.onClick=function(){ var items=getSelectedItems(); if(items.length===0) items=rgbLinks; refreshProfilesForItems(items, true); }; gotoBtn.onClick=function(){ var sels=list.selection; if(!sels||sels.length===0){ alert("Please select an item to locate on the page."); return; } var row=sels.length&&sels.length!==undefined?sels[0]:sels; var rec=rgbLinks[row._dataIndex]; try{ var parentItem=rec.link.parent&&rec.link.parent.parent?rec.link.parent.parent:null; if(parentItem&&parentItem.isValid){ app.select(parentItem); if(parentItem.parentPage) app.activeWindow.activePage=parentItem.parentPage; } else { alert("Could not locate the page item."); } }catch(e){ alert("Could not select the page item."); } }; closeBtn.onClick=function(){ try{ w.close(); }catch(e){} }; // Keep startup lightweight. Profile loading is manual via Refresh Profiles. updateLinksForItems(rgbLinks); status.text="Ready. Click Refresh Profiles to read profiles from Photoshop."; w.center(); w.show(); } app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, SCRIPT_NAME);