using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using JD.Adobe.Service.API;
using Plume.Core.Logging;
using Plume.Interfaces.Services;
using Shuttle.Api.AfterEffectsDTO;
using Shuttle.Api.AfterEffectsDTO.Items;
using Shuttle.Api.AfterEffectsDTO.Render;
using Shuttle.Api.AfterEffectsDTO.UpdatedData;
using Shuttle.Api.CommunicationDTO;
using Shuttle.Api.ServerAPI;
using Shuttle.Servers;
using Shuttle.Servers.Managers.Items;
using Shuttle.Servers.Managers.Notifications;
namespace JD.Adobe.Service.Implementation
{
///
/// Service to interact with After Effects.
///
public class AfterEffectsService : AdobeService, IAfterEffectsService
{
private IAfterEffectsOperationExecutor _operationExecutor;
private IAfterEffectsItemsProvider _itemProvider;
private const int RootItemId = 0;
public AfterEffectsService(ILogger logger)
: base(logger, ServerConstants.ShuttleServerUrl)
{
}
protected override void InitializeImpl(IServiceContext context)
{
if (_isInitialized)
{
throw new InvalidOperationException($"{nameof(AdobeService)} is already initialized.");
}
_itemProvider = new AfterEffectsItemProvider(Logger, _clientManager, _asyncProvider);
_operationExecutor = new AfterEffectsOperationExecutor(Logger, _clientManager, _asyncProvider);
_isInitialized = true;
}
public override async Task StartNewInstance(AdobeApplicationType appType)
{
string requestToken = _asyncProvider.CreateResourceRequest("start-new-instance");
// define a local function to capture the token and avoid keeping a state in the service
void EnsureEmptyProject(TargetAppInfo targetAppInfo)
{
if (targetAppInfo.AppType != appType)
{
return;
}
string currentFilePath = this.GetCurrentProjectFilePath(targetAppInfo).GetAwaiter().GetResult(); // make the call synchronously because the event itself will be raised in a Task
if (currentFilePath == string.Empty)
{
// the new instance just connected :)
_asyncProvider.DepositResource(requestToken, targetAppInfo);
}
else
{
Logger.Debug($"{targetAppInfo} connected but didn't have an empty project file: {currentFilePath}");
}
}
_clientManager.OnTargetConnection += EnsureEmptyProject;
try
{
if (!_adobeExecutableManager.TryStartNewInstance(appType))
{
throw new ArgumentException($"Couldn't start a new instance of {appType}. Are you sure this Adobe application is installed on your machine?");
}
int waitingTime = 60_000;
bool success = await _asyncProvider.Wait(requestToken, waitingTime);
if (!success)
{
throw new TimeoutException($"{appType} took more than {waitingTime}ms to launch and establish a connection. " +
$"You may need to disable the Home startup menu from Edit > Preferences > General.");
}
}
finally
{
_clientManager.OnTargetConnection -= EnsureEmptyProject;
}
TargetAppInfo startedAppInfo = _asyncProvider.GetResource(requestToken);
return startedAppInfo;
}
// TASKS
public async Task PurgeCache(TargetAppInfo target)
{
ThrowIfUnknown(target);
await _operationExecutor.PurgeMemoryAsync(target.ConnectionId);
}
public async Task Save(TargetAppInfo target)
{
ThrowIfUnknown(target);
await _operationExecutor.SaveProjectAsync(target.ConnectionId);
}
public async Task SaveAs(TargetAppInfo target, string filePath)
{
ThrowIfUnknown(target);
if (string.IsNullOrEmpty(filePath))
{
throw new ArgumentException($"{nameof(filePath)} cannot be null or empty.", nameof(filePath));
}
await _operationExecutor.SaveProjectAsync(target.ConnectionId, filePath);
}
public async Task OpenProject(TargetAppInfo target, string projectFilePath)
{
ThrowIfUnknown(target);
if (!File.Exists(projectFilePath))
{
throw new ArgumentException($"{nameof(projectFilePath)} doesn't exist at '{projectFilePath}'.");
}
await _operationExecutor.OpenProject(target.ConnectionId, projectFilePath);
}
public async Task ShowRenderWindow(TargetAppInfo target, bool show)
{
ThrowIfUnknown(target);
await _operationExecutor.ShowRenderWindow(target.ConnectionId, show);
}
public async Task BeginUndoGroup(TargetAppInfo target, string undoGroupName)
{
ThrowIfUnknown(target);
if (string.IsNullOrEmpty(undoGroupName))
{
throw new ArgumentException($"{nameof(undoGroupName)} cannot be null or empty.", nameof(undoGroupName));
}
await _operationExecutor.BeginUndoGroup(target.ConnectionId, undoGroupName);
}
public async Task EndUndoGroup(TargetAppInfo target)
{
ThrowIfUnknown(target);
await _operationExecutor.EndUndoGroup(target.ConnectionId);
}
public async Task GetCurrentProjectFilePath(TargetAppInfo target)
{
ThrowIfUnknown(target);
string filename = await _itemProvider.GetCurrentFileAsync(target.ConnectionId);
return filename;
}
// GET ITEMS
public async Task> GetItems(TargetAppInfo target)
{
ThrowIfUnknown(target);
ICollection- items = await _itemProvider.GetAllItemsAsync(target.ConnectionId);
return items;
}
public async Task> GetSelectedItems(TargetAppInfo target)
{
ThrowIfUnknown(target);
ICollection
- items = await _itemProvider.GetSelectedProjectItemsAsync(target.ConnectionId);
return items;
}
public async Task GetRenderQueue(TargetAppInfo target)
{
ThrowIfUnknown(target);
RenderQueue renderQueue = await _itemProvider.GetRenderQueueAsync(target.ConnectionId);
return renderQueue;
}
public async Task GetRenderQueueItem(TargetAppInfo target, int index)
{
ThrowIfUnknown(target);
RenderQueueItem renderQueueItem = await _itemProvider.GetRenderQueueItemAsync(target.ConnectionId, index);
return renderQueueItem;
}
public async Task GetFolderItem(TargetAppInfo target, int folderId)
{
ThrowIfUnknown(target);
FolderItem folderItem = await _itemProvider.GetFolderItemAsync(target.ConnectionId, folderId);
return folderItem;
}
public async Task GetCompItem(TargetAppInfo target, int compositionId)
{
ThrowIfUnknown(target);
CompItem compItem = await _itemProvider.GetCompItemAsync(target.ConnectionId, compositionId);
return compItem;
}
public async Task GetFootageItem(TargetAppInfo target, int footageId)
{
ThrowIfUnknown(target);
FootageItem footageItem = await _itemProvider.GetFootageItemAsync(target.ConnectionId, footageId);
return footageItem;
}
// CREATE ITEMS
public async Task AddFolder(TargetAppInfo target, string name)
{
return await AddFolder(target, name, RootItemId);
}
public async Task AddFolder(TargetAppInfo target, string name, int parentFolderId)
{
ThrowIfUnknown(target);
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (parentFolderId < 0)
{
throw new ArgumentOutOfRangeException(nameof(parentFolderId), $"{nameof(parentFolderId)} must be non-negative.");
}
FolderItem folderItem = await _itemProvider.CreateFolderAsync(target.ConnectionId, name, parentFolderId);
return folderItem;
}
public async Task AddComp(TargetAppInfo target, string name, int width, int height, float pixelAspect, float duration, float frameRate)
{
return await AddComp(target, name, width, height, pixelAspect, duration, frameRate, RootItemId);
}
public async Task AddComp(TargetAppInfo target, string name, int width, int height, float pixelAspect, float duration, float frameRate, int parentFolderId)
{
ThrowIfUnknown(target);
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (width < 0) throw new ArgumentOutOfRangeException(nameof(width), $"{nameof(width)} must be non-negative.");
if (height < 0) throw new ArgumentOutOfRangeException(nameof(height), $"{nameof(height)} must be non-negative.");
if (pixelAspect < 0) throw new ArgumentOutOfRangeException(nameof(pixelAspect), $"{nameof(pixelAspect)} must be non-negative.");
if (duration < 0) throw new ArgumentOutOfRangeException(nameof(duration), $"{nameof(duration)} must be non-negative.");
if (frameRate < 0) throw new ArgumentOutOfRangeException(nameof(frameRate), $"{nameof(frameRate)} must be non-negative.");
if (parentFolderId < 0)
{
throw new ArgumentOutOfRangeException(nameof(parentFolderId), $"{nameof(parentFolderId)} must be non-negative.");
}
CompSettings settings = new CompSettings()
{
Width = width,
Height = height,
PixelAspect = pixelAspect,
Duration = duration,
FrameRate = frameRate
};
CompItem compItem = await _itemProvider.CreateCompAsync(target.ConnectionId, name, settings, parentFolderId);
return compItem;
}
public async Task
- ImportFile(TargetAppInfo target, string footageFilePath)
{
return await ImportFile(target, footageFilePath, RootItemId);
}
public async Task
- ImportFile(TargetAppInfo target, string footageFilePath, int parentFolderId)
{
ThrowIfUnknown(target);
if (!File.Exists(footageFilePath))
{
throw new ArgumentException($"{nameof(footageFilePath)} doesn't exist at '{footageFilePath}'.");
}
if (parentFolderId < 0)
{
throw new ArgumentOutOfRangeException(nameof(parentFolderId), $"{nameof(parentFolderId)} must be non-negative.");
}
Item footageItem = await _itemProvider.CreateFootageAsync(target.ConnectionId, footageFilePath, parentFolderId);
return footageItem;
}
public async Task AddToRenderQueue(TargetAppInfo target, int compId)
{
ThrowIfUnknown(target);
if (compId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(compId), $"{nameof(compId)} must be strictly positive.");
}
RenderQueueItem renderQueueItem = await _itemProvider.AddToRenderQueue(target.ConnectionId, compId);
return renderQueueItem;
}
public async Task AddNullLayer(TargetAppInfo target, int compId, string layerName)
{
ThrowIfUnknown(target);
if (compId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(compId), $"{nameof(compId)} must be strictly positive.");
}
if (layerName == null)
{
throw new ArgumentNullException(nameof(layerName));
}
Layer newLayer = await _itemProvider.AddNullLayerAsync(target.ConnectionId, compId, layerName);
return newLayer;
}
public async Task AddLayer(TargetAppInfo target, int compId, int sourceItemId)
{
ThrowIfUnknown(target);
if (compId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(compId), $"{nameof(compId)} must be strictly positive.");
}
if (sourceItemId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(sourceItemId), $"{nameof(sourceItemId)} must be strictly positive.");
}
Layer newLayer = await _itemProvider.AddLayerAsync(target.ConnectionId, compId, sourceItemId);
return newLayer;
}
// UPDATE ITEMS
public async Task UpdateFolderItem(TargetAppInfo target, int folderItemId, FolderItemUpdatableData folderItemData)
{
ThrowIfUnknown(target);
if (folderItemData == null)
{
throw new ArgumentNullException(nameof(folderItemData));
}
if (folderItemId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(folderItemId), $"{nameof(folderItemId)} must be strictly positive.");
}
FolderItem updatedFolderItem = await _itemProvider.UpdateFolderAsync(target.ConnectionId, folderItemId, folderItemData);
return updatedFolderItem;
}
public async Task UpdateCompItem(TargetAppInfo target, int compItemId, CompItemUpdatableData compItemData)
{
ThrowIfUnknown(target);
if (compItemData == null)
{
throw new ArgumentNullException(nameof(compItemData));
}
if (compItemId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(compItemId), $"{nameof(compItemId)} must be strictly positive.");
}
CompItem updatedCompItem = await _itemProvider.UpdateCompAsync(target.ConnectionId, compItemId, compItemData);
return updatedCompItem;
}
public async Task CopyLayers(TargetAppInfo target, int sourceCompId, int targetCompId)
{
ThrowIfUnknown(target);
if (sourceCompId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(sourceCompId), $"{nameof(sourceCompId)} must be strictly positive.");
}
if (targetCompId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(targetCompId), $"{nameof(targetCompId)} must be strictly positive.");
}
CompItem updatedCompItem = await _itemProvider.CopyLayersAsync(target.ConnectionId, sourceCompId, targetCompId);
return updatedCompItem;
}
public async Task RemoveLayers(TargetAppInfo target, int compId, ICollection layerNames)
{
ThrowIfUnknown(target);
if (compId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(compId), $"{nameof(compId)} must be strictly positive.");
}
if (layerNames == null)
{
throw new ArgumentNullException(nameof(layerNames));
}
if (layerNames.Count == 0)
{
return await _itemProvider.GetCompItemAsync(target.ConnectionId, compId);
}
CompItem updatedCompItem = await _itemProvider.RemoveLayersAsync(target.ConnectionId, compId, layerNames);
return updatedCompItem;
}
public async Task UpdateFootageItem(TargetAppInfo target, int footageItemId, FootageItemUpdatableData footageItemData)
{
ThrowIfUnknown(target);
if (footageItemData == null)
{
throw new ArgumentNullException(nameof(footageItemData));
}
if (footageItemId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(footageItemId), $"{nameof(footageItemId)} must be strictly positive.");
}
FootageItem updateFootageItem = await _itemProvider.UpdateFootageAsync(target.ConnectionId, footageItemId, footageItemData);
return updateFootageItem;
}
public async Task ReplaceFootageSource(TargetAppInfo target, int footageId, string sourceFilePath)
{
ThrowIfUnknown(target);
if (footageId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(footageId), $"{nameof(footageId)} must be strictly positive.");
}
if (!File.Exists(sourceFilePath))
{
throw new ArgumentException($"{nameof(sourceFilePath)} doesn't exist at '{sourceFilePath}'.");
}
FootageItem updateFootageItem = await _itemProvider.ReplaceFootageSourceAsync(target.ConnectionId, footageId, sourceFilePath);
return updateFootageItem;
}
public async Task ReplaceFootageSourceWithSequence(TargetAppInfo target, int footageId, string firstSourceFilePath, bool forceAlphabetical)
{
ThrowIfUnknown(target);
if (footageId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(footageId), $"{nameof(footageId)} must be strictly positive.");
}
if (!File.Exists(firstSourceFilePath))
{
throw new ArgumentException($"{nameof(firstSourceFilePath)} doesn't exist at '{firstSourceFilePath}'.");
}
FootageItem updateFootageItem = await _itemProvider.ReplaceFootageSourceWithSequenceAsync(target.ConnectionId, footageId, firstSourceFilePath, forceAlphabetical);
return updateFootageItem;
}
public async Task UpdateRenderQueueItem(TargetAppInfo target, int renderQueueItemId, RenderQueueItem renderQueueItem)
{
ThrowIfUnknown(target);
if (renderQueueItem == null)
{
throw new ArgumentNullException(nameof(renderQueueItem));
}
if (renderQueueItemId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(renderQueueItemId), $"{nameof(renderQueueItemId)} must be strictly positive.");
}
RenderQueueItem updateRenderQueueItem = await _itemProvider.UpdateRenderQueueItem(target.ConnectionId, renderQueueItemId, renderQueueItem);
return updateRenderQueueItem;
}
public async Task ConfigureRenderQueueItem(TargetAppInfo target, int renderQueueItemIndex, string settingsTemplateName, string outputModuleTemplateName, string outputFilePath)
{
ThrowIfUnknown(target);
if (renderQueueItemIndex <= 0)
{
throw new ArgumentOutOfRangeException(nameof(renderQueueItemIndex), $"{nameof(renderQueueItemIndex)} must be strictly positive (indexes start at 1).");
}
if (string.IsNullOrEmpty(settingsTemplateName))
{
throw new ArgumentException($"{nameof(settingsTemplateName)} cannot be null or empty.", nameof(settingsTemplateName));
}
if (string.IsNullOrEmpty(outputModuleTemplateName))
{
throw new ArgumentException($"{nameof(outputModuleTemplateName)} cannot be null or empty.", nameof(outputModuleTemplateName));
}
if (string.IsNullOrEmpty(outputFilePath))
{
throw new ArgumentException($"{nameof(outputFilePath)} cannot be null or empty.", nameof(outputFilePath));
}
RenderQueueItem configuredRenderQueueItem = await _itemProvider.ConfigureRenderQueueItem(target.ConnectionId, renderQueueItemIndex, settingsTemplateName, outputModuleTemplateName, outputFilePath);
return configuredRenderQueueItem;
}
public async Task UpdateLayer(TargetAppInfo target, int compId, int layerIndex, LayerUpdatableData layerData)
{
ThrowIfUnknown(target);
if (compId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(compId), $"{nameof(compId)} must be strictly positive.");
}
if (layerIndex <= 0)
{
throw new ArgumentOutOfRangeException(nameof(layerIndex), $"{nameof(layerIndex)} must be strictly positive.");
}
if (layerData == null)
{
throw new ArgumentNullException(nameof(layerData));
}
Layer updatedLayer = await _itemProvider.UpdateLayerAsync(target.ConnectionId, compId, layerIndex, layerData);
return updatedLayer;
}
public async Task
- SetParentFolderToRoot(TargetAppInfo target, int itemId)
{
return await SetParentFolder(target, itemId, RootItemId);
}
public async Task
- SetParentFolder(TargetAppInfo target, int itemId, int parentFolderId)
{
ThrowIfUnknown(target);
if (itemId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(itemId), $"{nameof(itemId)} must be strictly positive.");
}
if (parentFolderId < 0)
{
throw new ArgumentOutOfRangeException(nameof(parentFolderId), $"{nameof(parentFolderId)} must be non-negative.");
}
Item updateItem = await _itemProvider.SetParentAsync(target.ConnectionId, itemId, parentFolderId);
return updateItem;
}
public async Task
- RenameItem(TargetAppInfo target, int itemId, string newName)
{
ThrowIfUnknown(target);
if (itemId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(itemId), $"{nameof(itemId)} must be strictly positive.");
}
if (string.IsNullOrEmpty(newName))
{
throw new ArgumentException($"{nameof(newName)} cannot be null or empty.", nameof(newName));
}
Item updateItem = await _itemProvider.RenameItem(target.ConnectionId, itemId, newName);
return updateItem;
}
// DELETE ITEMS
public async Task RemoveItem(TargetAppInfo target, int itemId)
{
ThrowIfUnknown(target);
if (itemId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(itemId), $"{nameof(itemId)} must be strictly positive.");
}
await _itemProvider.RemoveItemFromProject(target.ConnectionId, itemId);
}
}
}