mcp-after-effects

by Dakkshin
// mcp-bridge-auto.jsx // Auto-running MCP Bridge panel for After Effects // Remove #include directives as we define functions below /* #include "createComposition.jsx" #include "createTextLayer.jsx" #include "createShapeLayer.jsx" #include "createSolidLayer.jsx" #include "setLayerProperties.jsx" */ // --- Function Definitions --- // --- createComposition (from createComposition.jsx) --- function createComposition(args) { try { var name = args.name || "New Composition"; var width = parseInt(args.width) || 1920; var height = parseInt(args.height) || 1080; var pixelAspect = parseFloat(args.pixelAspect) || 1.0; var duration = parseFloat(args.duration) || 10.0; var frameRate = parseFloat(args.frameRate) || 30.0; var bgColor = args.backgroundColor ? [args.backgroundColor.r/255, args.backgroundColor.g/255, args.backgroundColor.b/255] : [0, 0, 0]; var newComp = app.project.items.addComp(name, width, height, pixelAspect, duration, frameRate); if (args.backgroundColor) { newComp.bgColor = bgColor; } return JSON.stringify({ status: "success", message: "Composition created successfully", composition: { name: newComp.name, id: newComp.id, width: newComp.width, height: newComp.height, pixelAspect: newComp.pixelAspect, duration: newComp.duration, frameRate: newComp.frameRate, bgColor: newComp.bgColor } }, null, 2); } catch (error) { return JSON.stringify({ status: "error", message: error.toString() }, null, 2); } } // --- createTextLayer (from createTextLayer.jsx) --- function createTextLayer(args) { try { var compName = args.compName || ""; var text = args.text || "Text Layer"; var position = args.position || [960, 540]; var fontSize = args.fontSize || 72; var color = args.color || [1, 1, 1]; var startTime = args.startTime || 0; var duration = args.duration || 5; var fontFamily = args.fontFamily || "Arial"; var alignment = args.alignment || "center"; var comp = null; for (var i = 1; i <= app.project.numItems; i++) { var item = app.project.item(i); if (item instanceof CompItem && item.name === compName) { comp = item; break; } } if (!comp) { if (app.project.activeItem instanceof CompItem) { comp = app.project.activeItem; } else { throw new Error("No composition found with name '" + compName + "' and no active composition"); } } var textLayer = comp.layers.addText(text); var textProp = textLayer.property("ADBE Text Properties").property("ADBE Text Document"); var textDocument = textProp.value; textDocument.fontSize = fontSize; textDocument.fillColor = color; textDocument.font = fontFamily; if (alignment === "left") { textDocument.justification = ParagraphJustification.LEFT_JUSTIFY; } else if (alignment === "center") { textDocument.justification = ParagraphJustification.CENTER_JUSTIFY; } else if (alignment === "right") { textDocument.justification = ParagraphJustification.RIGHT_JUSTIFY; } textProp.setValue(textDocument); textLayer.property("Position").setValue(position); textLayer.startTime = startTime; if (duration > 0) { textLayer.outPoint = startTime + duration; } return JSON.stringify({ status: "success", message: "Text layer created successfully", layer: { name: textLayer.name, index: textLayer.index, type: "text", inPoint: textLayer.inPoint, outPoint: textLayer.outPoint, position: textLayer.property("Position").value } }, null, 2); } catch (error) { return JSON.stringify({ status: "error", message: error.toString() }, null, 2); } } // --- createShapeLayer (from createShapeLayer.jsx) --- function createShapeLayer(args) { try { var compName = args.compName || ""; var shapeType = args.shapeType || "rectangle"; var position = args.position || [960, 540]; var size = args.size || [200, 200]; var fillColor = args.fillColor || [1, 0, 0]; var strokeColor = args.strokeColor || [0, 0, 0]; var strokeWidth = args.strokeWidth || 0; var startTime = args.startTime || 0; var duration = args.duration || 5; var name = args.name || "Shape Layer"; var points = args.points || 5; var comp = null; for (var i = 1; i <= app.project.numItems; i++) { var item = app.project.item(i); if (item instanceof CompItem && item.name === compName) { comp = item; break; } } if (!comp) { if (app.project.activeItem instanceof CompItem) { comp = app.project.activeItem; } else { throw new Error("No composition found with name '" + compName + "' and no active composition"); } } var shapeLayer = comp.layers.addShape(); shapeLayer.name = name; var contents = shapeLayer.property("Contents"); var shapeGroup = contents.addProperty("ADBE Vector Group"); var groupContents = shapeGroup.property("Contents"); var shapePathProperty; if (shapeType === "rectangle") { shapePathProperty = groupContents.addProperty("ADBE Vector Shape - Rect"); shapePathProperty.property("Size").setValue(size); } else if (shapeType === "ellipse") { shapePathProperty = groupContents.addProperty("ADBE Vector Shape - Ellipse"); shapePathProperty.property("Size").setValue(size); } else if (shapeType === "polygon" || shapeType === "star") { shapePathProperty = groupContents.addProperty("ADBE Vector Shape - Star"); shapePathProperty.property("Type").setValue(shapeType === "polygon" ? 1 : 2); shapePathProperty.property("Points").setValue(points); shapePathProperty.property("Outer Radius").setValue(size[0] / 2); if (shapeType === "star") { shapePathProperty.property("Inner Radius").setValue(size[0] / 3); } } var fill = groupContents.addProperty("ADBE Vector Graphic - Fill"); fill.property("Color").setValue(fillColor); fill.property("Opacity").setValue(100); if (strokeWidth > 0) { var stroke = groupContents.addProperty("ADBE Vector Graphic - Stroke"); stroke.property("Color").setValue(strokeColor); stroke.property("Stroke Width").setValue(strokeWidth); stroke.property("Opacity").setValue(100); } shapeLayer.property("Position").setValue(position); shapeLayer.startTime = startTime; if (duration > 0) { shapeLayer.outPoint = startTime + duration; } return JSON.stringify({ status: "success", message: "Shape layer created successfully", layer: { name: shapeLayer.name, index: shapeLayer.index, type: "shape", shapeType: shapeType, inPoint: shapeLayer.inPoint, outPoint: shapeLayer.outPoint, position: shapeLayer.property("Position").value } }, null, 2); } catch (error) { return JSON.stringify({ status: "error", message: error.toString() }, null, 2); } } // --- createSolidLayer (from createSolidLayer.jsx) --- function createSolidLayer(args) { try { var compName = args.compName || ""; var color = args.color || [1, 1, 1]; var name = args.name || "Solid Layer"; var position = args.position || [960, 540]; var size = args.size; var startTime = args.startTime || 0; var duration = args.duration || 5; var isAdjustment = args.isAdjustment || false; var comp = null; for (var i = 1; i <= app.project.numItems; i++) { var item = app.project.item(i); if (item instanceof CompItem && item.name === compName) { comp = item; break; } } if (!comp) { if (app.project.activeItem instanceof CompItem) { comp = app.project.activeItem; } else { throw new Error("No composition found with name '" + compName + "' and no active composition"); } } if (!size) { size = [comp.width, comp.height]; } var solidLayer; if (isAdjustment) { solidLayer = comp.layers.addSolid([0, 0, 0], name, size[0], size[1], 1); solidLayer.adjustmentLayer = true; } else { solidLayer = comp.layers.addSolid(color, name, size[0], size[1], 1); } solidLayer.property("Position").setValue(position); solidLayer.startTime = startTime; if (duration > 0) { solidLayer.outPoint = startTime + duration; } return JSON.stringify({ status: "success", message: isAdjustment ? "Adjustment layer created successfully" : "Solid layer created successfully", layer: { name: solidLayer.name, index: solidLayer.index, type: isAdjustment ? "adjustment" : "solid", inPoint: solidLayer.inPoint, outPoint: solidLayer.outPoint, position: solidLayer.property("Position").value, isAdjustment: solidLayer.adjustmentLayer } }, null, 2); } catch (error) { return JSON.stringify({ status: "error", message: error.toString() }, null, 2); } } // --- setLayerProperties (modified to handle text properties) --- function setLayerProperties(args) { try { var compName = args.compName || ""; var layerName = args.layerName || ""; var layerIndex = args.layerIndex; // General Properties var position = args.position; var scale = args.scale; var rotation = args.rotation; var opacity = args.opacity; var startTime = args.startTime; var duration = args.duration; // Text Specific Properties var textContent = args.text; // New: text content var fontFamily = args.fontFamily; // New: font family var fontSize = args.fontSize; // New: font size var fillColor = args.fillColor; // New: font color // Find the composition (same logic as before) var comp = null; for (var i = 1; i <= app.project.numItems; i++) { var item = app.project.item(i); if (item instanceof CompItem && item.name === compName) { comp = item; break; } } if (!comp) { if (app.project.activeItem instanceof CompItem) { comp = app.project.activeItem; } else { throw new Error("No composition found with name '" + compName + "' and no active composition"); } } // Find the layer (same logic as before) var layer = null; if (layerIndex !== undefined && layerIndex !== null) { if (layerIndex > 0 && layerIndex <= comp.numLayers) { layer = comp.layer(layerIndex); } else { throw new Error("Layer index out of bounds: " + layerIndex); } } else if (layerName) { for (var j = 1; j <= comp.numLayers; j++) { if (comp.layer(j).name === layerName) { layer = comp.layer(j); break; } } } if (!layer) { throw new Error("Layer not found: " + (layerName || "index " + layerIndex)); } var changedProperties = []; var textDocumentChanged = false; var textProp = null; var textDocument = null; // --- Text Property Handling --- if (layer instanceof TextLayer && (textContent !== undefined || fontFamily !== undefined || fontSize !== undefined || fillColor !== undefined)) { var sourceTextProp = layer.property("Source Text"); if (sourceTextProp && sourceTextProp.value) { var currentTextDocument = sourceTextProp.value; // Get the current value var updated = false; if (textContent !== undefined && textContent !== null && currentTextDocument.text !== textContent) { currentTextDocument.text = textContent; changedProperties.push("text"); updated = true; } if (fontFamily !== undefined && fontFamily !== null && currentTextDocument.font !== fontFamily) { // Add basic validation/logging for font existence if needed // try { app.fonts.findFont(fontFamily); } catch (e) { logToPanel("Warning: Font '"+fontFamily+"' might not be installed."); } currentTextDocument.font = fontFamily; changedProperties.push("fontFamily"); updated = true; } if (fontSize !== undefined && fontSize !== null && currentTextDocument.fontSize !== fontSize) { currentTextDocument.fontSize = fontSize; changedProperties.push("fontSize"); updated = true; } // Comparing colors needs care due to potential floating point inaccuracies if set via UI // Simple comparison for now if (fillColor !== undefined && fillColor !== null && (currentTextDocument.fillColor[0] !== fillColor[0] || currentTextDocument.fillColor[1] !== fillColor[1] || currentTextDocument.fillColor[2] !== fillColor[2])) { currentTextDocument.fillColor = fillColor; changedProperties.push("fillColor"); updated = true; } // Only set the value if something actually changed if (updated) { try { sourceTextProp.setValue(currentTextDocument); logToPanel("Applied changes to Text Document for layer: " + layer.name); } catch (e) { logToPanel("ERROR applying Text Document changes: " + e.toString()); // Decide if we should throw or just log the error for text properties // For now, just log, other properties might still succeed } } // Store the potentially updated document for the return value textDocument = currentTextDocument; } else { logToPanel("Warning: Could not access Source Text property for layer: " + layer.name); } } // --- General Property Handling --- if (position !== undefined && position !== null) { layer.property("Position").setValue(position); changedProperties.push("position"); } if (scale !== undefined && scale !== null) { layer.property("Scale").setValue(scale); changedProperties.push("scale"); } if (rotation !== undefined && rotation !== null) { if (layer.threeDLayer) { // For 3D layers, Z rotation is often what's intended by a single value layer.property("Z Rotation").setValue(rotation); } else { layer.property("Rotation").setValue(rotation); } changedProperties.push("rotation"); } if (opacity !== undefined && opacity !== null) { layer.property("Opacity").setValue(opacity); changedProperties.push("opacity"); } if (startTime !== undefined && startTime !== null) { layer.startTime = startTime; changedProperties.push("startTime"); } if (duration !== undefined && duration !== null && duration > 0) { var actualStartTime = (startTime !== undefined && startTime !== null) ? startTime : layer.startTime; layer.outPoint = actualStartTime + duration; changedProperties.push("duration"); } // Return success with updated layer details (including text if changed) var returnLayerInfo = { name: layer.name, index: layer.index, position: layer.property("Position").value, scale: layer.property("Scale").value, rotation: layer.threeDLayer ? layer.property("Z Rotation").value : layer.property("Rotation").value, // Return appropriate rotation opacity: layer.property("Opacity").value, inPoint: layer.inPoint, outPoint: layer.outPoint, changedProperties: changedProperties }; // Add text properties to the return object if it was a text layer if (layer instanceof TextLayer && textDocument) { returnLayerInfo.text = textDocument.text; returnLayerInfo.fontFamily = textDocument.font; returnLayerInfo.fontSize = textDocument.fontSize; returnLayerInfo.fillColor = textDocument.fillColor; } // *** ADDED LOGGING HERE *** logToPanel("Final check before return:"); logToPanel(" Changed Properties: " + changedProperties.join(", ")); logToPanel(" Return Layer Info Font: " + (returnLayerInfo.fontFamily || "N/A")); logToPanel(" TextDocument Font: " + (textDocument ? textDocument.font : "N/A")); return JSON.stringify({ status: "success", message: "Layer properties updated successfully", layer: returnLayerInfo }, null, 2); } catch (error) { // Error handling remains similar, but add more specific checks if needed return JSON.stringify({ status: "error", message: error.toString() }, null, 2); } } /** * Sets a keyframe for a specific property on a layer. * @param {number} compIndex - The index of the composition (1-based). * @param {number} layerIndex - The index of the layer within the composition (1-based). * @param {string} propertyName - The name of the property (e.g., "Position", "Scale", "Rotation", "Opacity"). * @param {number} timeInSeconds - The time (in seconds) for the keyframe. * @param {any} value - The value for the keyframe (e.g., [x, y] for Position, [w, h] for Scale, angle for Rotation, percentage for Opacity). * @returns {string} JSON string indicating success or error. */ function setLayerKeyframe(compIndex, layerIndex, propertyName, timeInSeconds, value) { try { // Adjust indices to be 0-based for ExtendScript arrays var comp = app.project.items[compIndex]; if (!comp || !(comp instanceof CompItem)) { return JSON.stringify({ success: false, message: "Composition not found at index " + compIndex }); } var layer = comp.layers[layerIndex]; if (!layer) { return JSON.stringify({ success: false, message: "Layer not found at index " + layerIndex + " in composition '" + comp.name + "'"}); } var transformGroup = layer.property("Transform"); if (!transformGroup) { return JSON.stringify({ success: false, message: "Transform properties not found for layer '" + layer.name + "' (type: " + layer.matchName + ")." }); } var property = transformGroup.property(propertyName); if (!property) { // Check other common property groups if not in Transform if (layer.property("Effects") && layer.property("Effects").property(propertyName)) { property = layer.property("Effects").property(propertyName); } else if (layer.property("Text") && layer.property("Text").property(propertyName)) { property = layer.property("Text").property(propertyName); } // Add more groups if needed (e.g., Masks, Shapes) if (!property) { return JSON.stringify({ success: false, message: "Property '" + propertyName + "' not found on layer '" + layer.name + "'." }); } } // Ensure the property can be keyframed if (!property.canVaryOverTime) { return JSON.stringify({ success: false, message: "Property '" + propertyName + "' cannot be keyframed." }); } // Make sure the property is enabled for keyframing if (property.numKeys === 0 && !property.isTimeVarying) { property.setValueAtTime(comp.time, property.value); // Set initial keyframe if none exist } property.setValueAtTime(timeInSeconds, value); return JSON.stringify({ success: true, message: "Keyframe set for '" + propertyName + "' on layer '" + layer.name + "' at " + timeInSeconds + "s." }); } catch (e) { return JSON.stringify({ success: false, message: "Error setting keyframe: " + e.toString() + " (Line: " + e.line + ")" }); } } /** * Sets an expression for a specific property on a layer. * @param {number} compIndex - The index of the composition (1-based). * @param {number} layerIndex - The index of the layer within the composition (1-based). * @param {string} propertyName - The name of the property (e.g., "Position", "Scale", "Rotation", "Opacity"). * @param {string} expressionString - The JavaScript expression string. Use "" to remove expression. * @returns {string} JSON string indicating success or error. */ function setLayerExpression(compIndex, layerIndex, propertyName, expressionString) { try { // Adjust indices to be 0-based for ExtendScript arrays var comp = app.project.items[compIndex]; if (!comp || !(comp instanceof CompItem)) { return JSON.stringify({ success: false, message: "Composition not found at index " + compIndex }); } var layer = comp.layers[layerIndex]; if (!layer) { return JSON.stringify({ success: false, message: "Layer not found at index " + layerIndex + " in composition '" + comp.name + "'"}); } var transformGroup = layer.property("Transform"); if (!transformGroup) { // Allow expressions on non-transformable layers if property exists elsewhere // return JSON.stringify({ success: false, message: "Transform properties not found for layer '" + layer.name + "' (type: " + layer.matchName + ")." }); } var property = transformGroup ? transformGroup.property(propertyName) : null; if (!property) { // Check other common property groups if not in Transform if (layer.property("Effects") && layer.property("Effects").property(propertyName)) { property = layer.property("Effects").property(propertyName); } else if (layer.property("Text") && layer.property("Text").property(propertyName)) { property = layer.property("Text").property(propertyName); } // Add more groups if needed if (!property) { return JSON.stringify({ success: false, message: "Property '" + propertyName + "' not found on layer '" + layer.name + "'." }); } } if (!property.canSetExpression) { return JSON.stringify({ success: false, message: "Property '" + propertyName + "' does not support expressions." }); } property.expression = expressionString; var action = expressionString === "" ? "removed" : "set"; return JSON.stringify({ success: true, message: "Expression " + action + " for '" + propertyName + "' on layer '" + layer.name + "'." }); } catch (e) { return JSON.stringify({ success: false, message: "Error setting expression: " + e.toString() + " (Line: " + e.line + ")" }); } } // --- applyEffect (from applyEffect.jsx) --- function applyEffect(args) { try { // Extract parameters var compIndex = args.compIndex || 1; // Default to first comp var layerIndex = args.layerIndex || 1; // Default to first layer var effectName = args.effectName; // Name of the effect to apply var effectMatchName = args.effectMatchName; // After Effects internal name (more reliable) var effectCategory = args.effectCategory || ""; // Optional category for filtering var presetPath = args.presetPath; // Optional path to an effect preset var effectSettings = args.effectSettings || {}; // Optional effect parameters if (!effectName && !effectMatchName && !presetPath) { throw new Error("You must specify either effectName, effectMatchName, or presetPath"); } // Find the composition by index var comp = app.project.item(compIndex); if (!comp || !(comp instanceof CompItem)) { throw new Error("Composition not found at index " + compIndex); } // Find the layer by index var layer = comp.layer(layerIndex); if (!layer) { throw new Error("Layer not found at index " + layerIndex + " in composition '" + comp.name + "'"); } var effectResult; // Apply preset if a path is provided if (presetPath) { var presetFile = new File(presetPath); if (!presetFile.exists) { throw new Error("Effect preset file not found: " + presetPath); } // Apply the preset to the layer layer.applyPreset(presetFile); effectResult = { type: "preset", name: presetPath.split('/').pop().split('\\').pop(), applied: true }; } // Apply effect by match name (more reliable method) else if (effectMatchName) { var effect = layer.Effects.addProperty(effectMatchName); effectResult = { type: "effect", name: effect.name, matchName: effect.matchName, index: effect.propertyIndex }; // Apply settings if provided applyEffectSettings(effect, effectSettings); } // Apply effect by display name else { // Get the effect from the Effect menu var effect = layer.Effects.addProperty(effectName); effectResult = { type: "effect", name: effect.name, matchName: effect.matchName, index: effect.propertyIndex }; // Apply settings if provided applyEffectSettings(effect, effectSettings); } return JSON.stringify({ status: "success", message: "Effect applied successfully", effect: effectResult, layer: { name: layer.name, index: layerIndex }, composition: { name: comp.name, index: compIndex } }, null, 2); } catch (error) { return JSON.stringify({ status: "error", message: error.toString() }, null, 2); } } // Helper function to apply effect settings function applyEffectSettings(effect, settings) { // Skip if no settings are provided if (!settings || Object.keys(settings).length === 0) { return; } // Iterate through all provided settings for (var propName in settings) { if (settings.hasOwnProperty(propName)) { try { // Find the property in the effect var property = null; // Try direct property access first try { property = effect.property(propName); } catch (e) { // If direct access fails, search through all properties for (var i = 1; i <= effect.numProperties; i++) { var prop = effect.property(i); if (prop.name === propName) { property = prop; break; } } } // Set the property value if found if (property && property.setValue) { property.setValue(settings[propName]); } } catch (e) { // Log error but continue with other properties $.writeln("Error setting effect property '" + propName + "': " + e.toString()); } } } } // --- applyEffectTemplate (from applyEffectTemplate.jsx) --- function applyEffectTemplate(args) { try { // Extract parameters var compIndex = args.compIndex || 1; // Default to first comp var layerIndex = args.layerIndex || 1; // Default to first layer var templateName = args.templateName; // Name of the template to apply var customSettings = args.customSettings || {}; // Optional customizations if (!templateName) { throw new Error("You must specify a templateName"); } // Find the composition by index var comp = app.project.item(compIndex); if (!comp || !(comp instanceof CompItem)) { throw new Error("Composition not found at index " + compIndex); } // Find the layer by index var layer = comp.layer(layerIndex); if (!layer) { throw new Error("Layer not found at index " + layerIndex + " in composition '" + comp.name + "'"); } // Template definitions var templates = { // Blur effects "gaussian-blur": { effectMatchName: "ADBE Gaussian Blur 2", settings: { "Blurriness": customSettings.blurriness || 20 } }, "directional-blur": { effectMatchName: "ADBE Motion Blur", settings: { "Direction": customSettings.direction || 0, "Blur Length": customSettings.length || 10 } }, // Color correction effects "color-balance": { effectMatchName: "ADBE Color Balance (HLS)", settings: { "Hue": customSettings.hue || 0, "Lightness": customSettings.lightness || 0, "Saturation": customSettings.saturation || 0 } }, "brightness-contrast": { effectMatchName: "ADBE Brightness & Contrast 2", settings: { "Brightness": customSettings.brightness || 0, "Contrast": customSettings.contrast || 0, "Use Legacy": false } }, "curves": { effectMatchName: "ADBE CurvesCustom", // Curves are complex and would need special handling }, // Stylistic effects "glow": { effectMatchName: "ADBE Glow", settings: { "Glow Threshold": customSettings.threshold || 50, "Glow Radius": customSettings.radius || 15, "Glow Intensity": customSettings.intensity || 1 } }, "drop-shadow": { effectMatchName: "ADBE Drop Shadow", settings: { "Shadow Color": customSettings.color || [0, 0, 0, 1], "Opacity": customSettings.opacity || 50, "Direction": customSettings.direction || 135, "Distance": customSettings.distance || 10, "Softness": customSettings.softness || 10 } }, // Common effect chains "cinematic-look": { effects: [ { effectMatchName: "ADBE Curves", settings: {} // Would need special handling }, { effectMatchName: "ADBE Vibrance", settings: { "Vibrance": 15, "Saturation": -5 } }, { effectMatchName: "ADBE Vignette", settings: { "Amount": 15, "Roundness": 50, "Feather": 40 } } ] }, "text-pop": { effects: [ { effectMatchName: "ADBE Drop Shadow", settings: { "Shadow Color": [0, 0, 0, 1], "Opacity": 75, "Distance": 5, "Softness": 10 } }, { effectMatchName: "ADBE Glow", settings: { "Glow Threshold": 50, "Glow Radius": 10, "Glow Intensity": 1.5 } } ] } }; // Check if the requested template exists var template = templates[templateName]; if (!template) { var availableTemplates = Object.keys(templates).join(", "); throw new Error("Template '" + templateName + "' not found. Available templates: " + availableTemplates); } var appliedEffects = []; // Apply single effect or multiple effects based on template structure if (template.effectMatchName) { // Single effect template var effect = layer.Effects.addProperty(template.effectMatchName); // Apply settings for (var propName in template.settings) { try { var property = effect.property(propName); if (property) { property.setValue(template.settings[propName]); } } catch (e) { $.writeln("Warning: Could not set " + propName + " on effect " + effect.name + ": " + e); } } appliedEffects.push({ name: effect.name, matchName: effect.matchName }); } else if (template.effects) { // Multiple effects template for (var i = 0; i < template.effects.length; i++) { var effectData = template.effects[i]; var effect = layer.Effects.addProperty(effectData.effectMatchName); // Apply settings for (var propName in effectData.settings) { try { var property = effect.property(propName); if (property) { property.setValue(effectData.settings[propName]); } } catch (e) { $.writeln("Warning: Could not set " + propName + " on effect " + effect.name + ": " + e); } } appliedEffects.push({ name: effect.name, matchName: effect.matchName }); } } return JSON.stringify({ status: "success", message: "Effect template '" + templateName + "' applied successfully", appliedEffects: appliedEffects, layer: { name: layer.name, index: layerIndex }, composition: { name: comp.name, index: compIndex } }, null, 2); } catch (error) { return JSON.stringify({ status: "error", message: error.toString() }, null, 2); } } // --- End of Function Definitions --- // Create panel interface var panel = (this instanceof Panel) ? this : new Window("palette", "MCP Bridge Auto", undefined); panel.orientation = "column"; panel.alignChildren = ["fill", "top"]; panel.spacing = 10; panel.margins = 16; // Status display var statusText = panel.add("statictext", undefined, "Waiting for commands..."); statusText.alignment = ["fill", "top"]; // Add log area var logPanel = panel.add("panel", undefined, "Command Log"); logPanel.orientation = "column"; logPanel.alignChildren = ["fill", "fill"]; var logText = logPanel.add("edittext", undefined, "", {multiline: true, readonly: true}); logText.preferredSize.height = 200; // Auto-run checkbox var autoRunCheckbox = panel.add("checkbox", undefined, "Auto-run commands"); autoRunCheckbox.value = true; // Check interval (ms) var checkInterval = 2000; var isChecking = false; // Command file path function getCommandFilePath() { var tempFolder = Folder.temp; return tempFolder.fsName + "/ae_command.json"; } // Result file path function getResultFilePath() { var tempFolder = Folder.temp; return tempFolder.fsName + "/ae_mcp_result.json"; } // Functions for each script type function getProjectInfo() { var project = app.project; var result = { projectName: project.file ? project.file.name : "Untitled Project", path: project.file ? project.file.fsName : "", numItems: project.numItems, bitsPerChannel: project.bitsPerChannel, frameRate: project.frameRate, dimensions: [project.width, project.height], duration: project.duration, items: [] }; // Count item types var countByType = { compositions: 0, footage: 0, folders: 0, solids: 0 }; // Get item information (limited for performance) for (var i = 1; i <= Math.min(project.numItems, 50); i++) { var item = project.item(i); var itemType = ""; if (item instanceof CompItem) { itemType = "Composition"; countByType.compositions++; } else if (item instanceof FolderItem) { itemType = "Folder"; countByType.folders++; } else if (item instanceof FootageItem) { if (item.mainSource instanceof SolidSource) { itemType = "Solid"; countByType.solids++; } else { itemType = "Footage"; countByType.footage++; } } result.items.push({ id: item.id, name: item.name, type: itemType }); } result.itemCounts = countByType; return JSON.stringify(result, null, 2); } function listCompositions() { var project = app.project; var result = { compositions: [] }; // Loop through items in the project for (var i = 1; i <= project.numItems; i++) { var item = project.item(i); // Check if the item is a composition if (item instanceof CompItem) { result.compositions.push({ id: item.id, name: item.name, duration: item.duration, frameRate: item.frameRate, width: item.width, height: item.height, numLayers: item.numLayers }); } } return JSON.stringify(result, null, 2); } function getLayerInfo() { var project = app.project; var result = { layers: [] }; // Get the active composition var activeComp = null; if (app.project.activeItem instanceof CompItem) { activeComp = app.project.activeItem; } else { return JSON.stringify({ error: "No active composition" }, null, 2); } // Loop through layers in the active composition for (var i = 1; i <= activeComp.numLayers; i++) { var layer = activeComp.layer(i); var layerInfo = { index: layer.index, name: layer.name, enabled: layer.enabled, locked: layer.locked, inPoint: layer.inPoint, outPoint: layer.outPoint }; result.layers.push(layerInfo); } return JSON.stringify(result, null, 2); } // Execute command function executeCommand(command, args) { var result = ""; logToPanel("Executing command: " + command); statusText.text = "Running: " + command; panel.update(); try { logToPanel("Attempting to execute: " + command); // Log before switch // Use a switch statement for clarity switch (command) { case "getProjectInfo": result = getProjectInfo(); break; case "listCompositions": result = listCompositions(); break; case "getLayerInfo": result = getLayerInfo(); break; case "createComposition": logToPanel("Calling createComposition function..."); result = createComposition(args); logToPanel("Returned from createComposition."); break; case "createTextLayer": logToPanel("Calling createTextLayer function..."); result = createTextLayer(args); logToPanel("Returned from createTextLayer."); break; case "createShapeLayer": logToPanel("Calling createShapeLayer function..."); result = createShapeLayer(args); logToPanel("Returned from createShapeLayer. Result type: " + typeof result); break; case "createSolidLayer": logToPanel("Calling createSolidLayer function..."); result = createSolidLayer(args); logToPanel("Returned from createSolidLayer."); break; case "setLayerProperties": logToPanel("Calling setLayerProperties function..."); result = setLayerProperties(args); logToPanel("Returned from setLayerProperties."); break; case "setLayerKeyframe": logToPanel("Calling setLayerKeyframe function..."); result = setLayerKeyframe(args.compIndex, args.layerIndex, args.propertyName, args.timeInSeconds, args.value); logToPanel("Returned from setLayerKeyframe."); break; case "setLayerExpression": logToPanel("Calling setLayerExpression function..."); result = setLayerExpression(args.compIndex, args.layerIndex, args.propertyName, args.expressionString); logToPanel("Returned from setLayerExpression."); break; case "applyEffect": logToPanel("Calling applyEffect function..."); result = applyEffect(args); logToPanel("Returned from applyEffect."); break; case "applyEffectTemplate": logToPanel("Calling applyEffectTemplate function..."); result = applyEffectTemplate(args); logToPanel("Returned from applyEffectTemplate."); break; default: result = JSON.stringify({ error: "Unknown command: " + command }); } logToPanel("Execution finished for: " + command); // Log after switch // Save the result (ensure result is always a string) logToPanel("Preparing to write result file..."); var resultString = (typeof result === 'string') ? result : JSON.stringify(result); // Try to parse the result as JSON to add a timestamp try { var resultObj = JSON.parse(resultString); // Add a timestamp to help identify if we're getting fresh results resultObj._responseTimestamp = new Date().toISOString(); resultObj._commandExecuted = command; resultString = JSON.stringify(resultObj, null, 2); logToPanel("Added timestamp to result JSON for tracking freshness."); } catch (parseError) { // If it's not valid JSON, append the timestamp as a comment logToPanel("Could not parse result as JSON to add timestamp: " + parseError.toString()); // We'll still continue with the original string } var resultFile = new File(getResultFilePath()); resultFile.encoding = "UTF-8"; // Ensure UTF-8 encoding logToPanel("Opening result file for writing..."); var opened = resultFile.open("w"); if (!opened) { logToPanel("ERROR: Failed to open result file for writing: " + resultFile.fsName); throw new Error("Failed to open result file for writing."); } logToPanel("Writing to result file..."); var written = resultFile.write(resultString); if (!written) { logToPanel("ERROR: Failed to write to result file (write returned false): " + resultFile.fsName); // Still try to close, but log the error } logToPanel("Closing result file..."); var closed = resultFile.close(); if (!closed) { logToPanel("ERROR: Failed to close result file: " + resultFile.fsName); // Continue, but log the error } logToPanel("Result file write process complete."); logToPanel("Command completed successfully: " + command); // Changed log message statusText.text = "Command completed: " + command; // Update command file status logToPanel("Updating command status to completed..."); updateCommandStatus("completed"); logToPanel("Command status updated."); } catch (error) { var errorMsg = "ERROR in executeCommand for '" + command + "': " + error.toString() + (error.line ? " (line: " + error.line + ")" : ""); logToPanel(errorMsg); // Log detailed error statusText.text = "Error: " + error.toString(); // Write detailed error to result file try { logToPanel("Attempting to write ERROR to result file..."); var errorResult = JSON.stringify({ status: "error", command: command, message: error.toString(), line: error.line, fileName: error.fileName }); var errorFile = new File(getResultFilePath()); errorFile.encoding = "UTF-8"; if (errorFile.open("w")) { errorFile.write(errorResult); errorFile.close(); logToPanel("Successfully wrote ERROR to result file."); } else { logToPanel("CRITICAL ERROR: Failed to open result file to write error!"); } } catch (writeError) { logToPanel("CRITICAL ERROR: Failed to write error to result file: " + writeError.toString()); } // Update command file status even after error logToPanel("Updating command status to error..."); updateCommandStatus("error"); logToPanel("Command status updated to error."); } } // Update command file status function updateCommandStatus(status) { try { var commandFile = new File(getCommandFilePath()); if (commandFile.exists) { commandFile.open("r"); var content = commandFile.read(); commandFile.close(); if (content) { var commandData = JSON.parse(content); commandData.status = status; commandFile.open("w"); commandFile.write(JSON.stringify(commandData, null, 2)); commandFile.close(); } } } catch (e) { logToPanel("Error updating command status: " + e.toString()); } } // Log message to panel function logToPanel(message) { var timestamp = new Date().toLocaleTimeString(); logText.text = timestamp + ": " + message + "\n" + logText.text; } // Check for new commands function checkForCommands() { if (!autoRunCheckbox.value || isChecking) return; isChecking = true; try { var commandFile = new File(getCommandFilePath()); if (commandFile.exists) { commandFile.open("r"); var content = commandFile.read(); commandFile.close(); if (content) { try { var commandData = JSON.parse(content); // Only execute pending commands if (commandData.status === "pending") { // Update status to running updateCommandStatus("running"); // Execute the command executeCommand(commandData.command, commandData.args || {}); } } catch (parseError) { logToPanel("Error parsing command file: " + parseError.toString()); } } } } catch (e) { logToPanel("Error checking for commands: " + e.toString()); } isChecking = false; } // Set up timer to check for commands function startCommandChecker() { app.scheduleTask("checkForCommands()", checkInterval, true); } // Add manual check button var checkButton = panel.add("button", undefined, "Check for Commands Now"); checkButton.onClick = function() { logToPanel("Manually checking for commands"); checkForCommands(); }; // Log startup logToPanel("MCP Bridge Auto started"); statusText.text = "Ready - Auto-run is " + (autoRunCheckbox.value ? "ON" : "OFF"); // Start the command checker startCommandChecker(); // Show the panel if (panel instanceof Window) { panel.center(); panel.show(); }
ID: bs9g9p44kv