kt-forpro-tools 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "kt-forpro-tools",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "author": "Miguel de Mendoza",
6
+ "main": "src/index.ts",
7
+ "license": "MIT",
8
+ "scripts": {
9
+ "build": "kt-build",
10
+ "build-tests": "kt-build build-tests",
11
+ "debug-build-tests": "kt-build debug-build-tests"
12
+ },
13
+ "files": [
14
+ "src",
15
+ "tsconfig.json"
16
+ ],
17
+ "dependencies": {
18
+ "kt-ae-is-checkers": "^1.0.7",
19
+ "kt-ae-tools-layers": "^1.0.0",
20
+ "kt-ae-tools-project": "^1.0.5",
21
+ "kt-core": "*",
22
+ "kt-io": "^1.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "kt-extendscript-builder": "*",
26
+ "kt-testing-suite-ae": "*",
27
+ "kt-testing-suite-core": "^1.7.7",
28
+ "prettier": "^3.6.2"
29
+ }
30
+ }
@@ -0,0 +1,10 @@
1
+ import { KT_Slides, type KT_SlidesOptions, KT_SlidesAPI } from "./slides";
2
+
3
+ export class KT_ForproTools {
4
+ private name = "KtForproTools";
5
+ private version = "1.0.0";
6
+ Slides = KT_SlidesAPI;
7
+ constructor() {
8
+ $.writeln("KtForproTools constructor");
9
+ }
10
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { KT_Core } from "kt-core";
2
+ import { KT_ForproTools } from "./KT_ForproTools";
3
+ import { KT_Slides } from "./slides";
4
+
5
+ export { KT_ForproTools, KT_Slides };
package/src/slides.ts ADDED
@@ -0,0 +1,377 @@
1
+ import { KT_Project } from "kt-ae-tools-project";
2
+ import { KT_Layers } from "kt-ae-tools-layers";
3
+ import { KT_AeIs as is } from "kt-ae-is-checkers";
4
+ import { IO } from "kt-io";
5
+
6
+ const colorSelector = () => {
7
+ let lastColorIndex = -1;
8
+ const numColors = 16;
9
+ const colorPool: number[] = [];
10
+
11
+ // ✅ Pre-generar pool de colores disponibles
12
+ for (let i = 0; i < numColors; i++) {
13
+ colorPool.push(i);
14
+ }
15
+
16
+ return () => {
17
+ // ✅ Filtrar colores que no sean el último usado
18
+ const availableColors = colorPool.filter((color) => color !== lastColorIndex);
19
+
20
+ // ✅ Seleccionar uno aleatorio del pool disponible
21
+ const randomIndex = Math.floor(Math.random() * availableColors.length);
22
+ lastColorIndex = availableColors[randomIndex];
23
+ return lastColorIndex;
24
+ };
25
+ };
26
+ export type KT_SlidesOptions = {
27
+ name?: string;
28
+ duration?: number;
29
+ width?: number;
30
+ height?: number;
31
+ segments?: number;
32
+ inDuration?: number;
33
+ comp?: CompItem;
34
+ slideMultiplier?: number;
35
+ };
36
+
37
+ class KT_Slides {
38
+ footageFolder!: FolderItem;
39
+ compsFolder!: FolderItem;
40
+ rootFolder!: FolderItem;
41
+ backgroundFolder!: FolderItem;
42
+ backgroundComp!: CompItem;
43
+ compedSlidesFolder!: FolderItem;
44
+ duration: number = 30;
45
+ masterComp: CompItem;
46
+ slidesFootage: FootageItem[] = [];
47
+ width: number = 1920;
48
+ height: number = 1080;
49
+ segments: number = 6;
50
+ slides: Layer[] = [];
51
+ inDuration: number = 1;
52
+ name: string = "Slides";
53
+ slideMultiplier: number = 1;
54
+ backgroundColor: [number, number, number] = [1, 1, 1];
55
+ private getNextColor = colorSelector();
56
+
57
+ constructor(options: KT_SlidesOptions = {}) {
58
+ const { name, duration, width, height, segments, inDuration, comp, slideMultiplier } = options;
59
+ this.name = name || this.name;
60
+ this.duration = duration || this.duration;
61
+ this.width = width || this.width;
62
+ this.height = height || this.height;
63
+ this.segments = segments || this.segments;
64
+ this.inDuration = inDuration || this.inDuration;
65
+ this.slideMultiplier = slideMultiplier || this.slideMultiplier;
66
+ this.initFolders();
67
+ const compExists = KT_Project.find.comps({ name: this.name + "Master" });
68
+ if (compExists && compExists.length > 0) {
69
+ this.masterComp = compExists[0] as CompItem;
70
+ return;
71
+ }
72
+ this.masterComp =
73
+ comp ||
74
+ (KT_Project.add.comp({
75
+ name: this.name + "Master",
76
+ width: this.width,
77
+ height: this.height,
78
+ duration: this.duration,
79
+ parentFolder: this.rootFolder,
80
+ }) as CompItem);
81
+ }
82
+
83
+ initFolders = (options: KT_SlidesOptions = {}): void => {
84
+ const rootFolder = KT_Project.find.folders({ name: this.name + " Project" });
85
+ if (!rootFolder || rootFolder.length === 0) {
86
+ this.rootFolder = KT_Project.add.folder({ name: this.name + " Project" });
87
+ } else {
88
+ this.rootFolder = rootFolder[0];
89
+ }
90
+
91
+ const footageFolder = KT_Project.find.folders({ name: "Footage", root: this.rootFolder });
92
+ this.footageFolder =
93
+ footageFolder && footageFolder.length > 0
94
+ ? footageFolder[0]
95
+ : KT_Project.add.folder({ name: "Footage", parentFolder: this.rootFolder });
96
+ const compsFolder = KT_Project.find.folders({ name: "Premiere Comps", root: this.rootFolder });
97
+ this.compsFolder =
98
+ compsFolder && compsFolder.length > 0
99
+ ? compsFolder[0]
100
+ : KT_Project.add.folder({ name: "Premiere Comps", parentFolder: this.rootFolder });
101
+ let backgroundFolder: FolderItem | FolderItem[] = KT_Project.find.folders({
102
+ name: "Backgrounds",
103
+ root: this.rootFolder,
104
+ });
105
+
106
+ if (backgroundFolder && backgroundFolder.length > 0) {
107
+ this.backgroundFolder = backgroundFolder[0];
108
+ this.backgroundComp = KT_Project.find.comps({
109
+ name: "Background",
110
+ root: this.backgroundFolder,
111
+ })[0] as CompItem;
112
+ } else {
113
+ this.backgroundFolder = KT_Project.add.folder({ name: "Backgrounds", parentFolder: this.rootFolder });
114
+ }
115
+ const backgroundComp = KT_Project.find.comps({ name: "Background", root: this.backgroundFolder });
116
+ if (backgroundComp && backgroundComp.length > 0) {
117
+ this.backgroundComp = backgroundComp[0] as CompItem;
118
+ } else {
119
+ this.backgroundComp = KT_Project.add.comp({
120
+ name: "Background",
121
+ width: this.width,
122
+ height: this.height,
123
+ duration: this.duration,
124
+ parentFolder: this.backgroundFolder,
125
+ }) as CompItem;
126
+
127
+ this.backgroundComp.layers.addSolid(
128
+ this.backgroundColor,
129
+ "Background Solid",
130
+ this.width,
131
+ this.height,
132
+ 1,
133
+ this.duration
134
+ );
135
+ }
136
+
137
+ const compedSlidesFolder = KT_Project.find.folders({ name: "Comped Slides", root: this.rootFolder });
138
+ this.compedSlidesFolder =
139
+ compedSlidesFolder && compedSlidesFolder.length > 0
140
+ ? compedSlidesFolder[0]
141
+ : KT_Project.add.folder({ name: "Comped Slides", parentFolder: this.rootFolder });
142
+ };
143
+
144
+ import = (slideMultiplier?: number) => {
145
+ if (!this.masterComp) {
146
+ $.writeln("Master comp not found");
147
+ return;
148
+ }
149
+ this.slideMultiplier = slideMultiplier || this.slideMultiplier;
150
+ const path = IO.fs.openFolderDialog("Select Slides Folder");
151
+ if (!path) {
152
+ $.writeln("No path selected");
153
+ return;
154
+ }
155
+
156
+ let footage = KT_Project.import.images({
157
+ path: path.fsName,
158
+ footageFolder: this.footageFolder,
159
+ importAs: "footage",
160
+ recursive: false,
161
+ flat: true,
162
+ toComp: true,
163
+ compFolder: this.compedSlidesFolder,
164
+ });
165
+
166
+ footage = KT_Project.find.comps({ root: this.compedSlidesFolder });
167
+ for (const f of footage) {
168
+ $.writeln("Imported comp: " + f.name);
169
+ }
170
+ this.sortSlides(footage);
171
+ if (footage.length === 0) {
172
+ $.writeln("No footage imported");
173
+ return;
174
+ }
175
+ this.slidesFootage = footage as FootageItem[];
176
+
177
+ this.masterComp.duration = this.inDuration * 2 * (footage.length * this.slideMultiplier) + this.inDuration;
178
+
179
+ footage.forEach((item, index) => {
180
+ const randomColorIndex = this.getNextColor();
181
+ for (let i = 0; i < this.slideMultiplier; i++) {
182
+ if (item.name === this.name) $.bp();
183
+ const subCompName = ((item as FootageItem).name.split(".")[0] + "-" + (i + 1)).replace(
184
+ "Artboard ",
185
+ "slide"
186
+ );
187
+ const subComp = KT_Project.add.compFromFootage(item as FootageItem, {
188
+ name: (item as FootageItem).name.split(".")[0] + "-" + (i + 1),
189
+ parentFolder: this.compedSlidesFolder,
190
+ });
191
+
192
+ const layer = this.addSlide(
193
+ subComp as AVItem,
194
+ this.inDuration * 2,
195
+ this.masterComp,
196
+ (index * this.slideMultiplier + i) * this.inDuration * 2
197
+ );
198
+ this.enableTimeRemap(layer as AVLayer);
199
+ layer.outPoint = this.masterComp.duration;
200
+ layer.label = randomColorIndex;
201
+ this.slides.push(layer);
202
+ }
203
+ });
204
+
205
+ return this;
206
+ };
207
+ batchSplitSlides = () => {
208
+ const slides = KT_Layers.find.layers({ comps: this.masterComp });
209
+ const totalSlides = slides.length;
210
+ let batchSize = this.slideMultiplier;
211
+ for (let i = 0; i < totalSlides; i) {
212
+ const batchLayers = slides.slice(i, i + batchSize);
213
+ //reduce batchlayers if next layer inPoint is more than inDuration * 3 away
214
+ for (let j = 0; j < batchLayers.length - 2; j++) {
215
+ const layer = batchLayers[j] as AVLayer;
216
+ const nextLayer = batchLayers[j + 1] as AVLayer;
217
+ if (layer.inPoint - nextLayer.inPoint > this.inDuration * 3) {
218
+ batchLayers.splice(j + 1, batchLayers.length - (j + 1));
219
+ break;
220
+ }
221
+ }
222
+ i += batchLayers.length;
223
+ this.splitSlides(batchLayers);
224
+ }
225
+ };
226
+ splitSlides = (layers?: Layer[]) => {
227
+ if (!Array.isArray(layers) || layers.length <= 1) {
228
+ layers = this.masterComp.selectedLayers;
229
+ }
230
+
231
+ if (!layers || layers.length === 0) {
232
+ $.writeln("No layers selected for precomp");
233
+ return;
234
+ }
235
+
236
+ const foundComps: { [key: string]: CompItem } = {};
237
+ const layerNames: string[] = [];
238
+
239
+ layers.forEach((pivotLayer, index, collection) => {
240
+ const invertedIndex = collection.length - index;
241
+ const slideName = pivotLayer.name.split("-")[0] + "_Slide_" + invertedIndex;
242
+ layerNames.push(slideName);
243
+ });
244
+
245
+ KT_Project.find.comps({
246
+ root: this.compsFolder,
247
+ name: layerNames,
248
+ callback: (item) => {
249
+ const comp = item as CompItem;
250
+ foundComps[comp.name] = comp;
251
+ },
252
+ });
253
+
254
+ layers.sort((a, b) => a.index - b.index); // Ordenar de menor a mayor
255
+ let pivotIndex = 0;
256
+ layers.forEach((pivotLayer, index, collection) => {
257
+ pivotLayer = pivotLayer as AVLayer;
258
+ const invertedIndex = collection.length - index;
259
+ const slideName = pivotLayer.name.split("-")[0] + "_Slide_" + invertedIndex;
260
+ let precomp = foundComps[slideName];
261
+ if (precomp instanceof CompItem) {
262
+ const layersToRemove = KT_Layers.find.layers({ comps: precomp });
263
+ layersToRemove.forEach((layer) => layer.remove());
264
+ } else {
265
+ precomp = KT_Project.add.comp({
266
+ name: slideName,
267
+ width: this.width,
268
+ height: this.height,
269
+ duration: this.duration,
270
+ parentFolder: this.compsFolder,
271
+ }) as CompItem;
272
+ }
273
+
274
+ // Obtener todas las capas
275
+
276
+ // Eliminar todas las capas posteriores a la capa pivote
277
+
278
+ for (let i = collection.length - 1; i >= 0; i--) {
279
+ const layer = collection[i];
280
+
281
+ if (layer.index < pivotLayer.index) {
282
+ continue;
283
+ }
284
+ layer.copyToComp(precomp);
285
+ const newLayer = precomp.layers[1];
286
+ if (i === pivotIndex) {
287
+ newLayer.startTime = newLayer.startTime - newLayer.inPoint;
288
+ } else {
289
+ newLayer.startTime = newLayer.startTime - newLayer.inPoint - this.inDuration;
290
+ }
291
+
292
+ newLayer.outPoint = this.duration;
293
+ newLayer.label = layer.label;
294
+ }
295
+
296
+ pivotIndex += 1;
297
+ // Ajustar la duración de la precomp
298
+ const solid = precomp.layers.add(this.backgroundComp);
299
+ solid.moveToEnd();
300
+ precomp.duration = this.duration;
301
+ });
302
+ };
303
+
304
+ private addSlide(
305
+ source: AVItem,
306
+ duration: number,
307
+ comp: CompItem = app.project.activeItem as CompItem,
308
+ startTime?: number
309
+ ) {
310
+ const layer = comp.layers.add(source);
311
+ layer.startTime = 0;
312
+ layer.outPoint = duration;
313
+ this.slideUp(layer);
314
+ this.disolveIn(layer);
315
+ layer.startTime = startTime || 0;
316
+ return layer;
317
+ }
318
+
319
+ private disolveIn = (layer: Layer) => {
320
+ layer.opacity.setValueAtTime(layer.startTime, 0);
321
+ layer.opacity.setValueAtTime(layer.startTime + this.inDuration, 100);
322
+ const easeIn = new KeyframeEase(0, 20);
323
+ const easeOut = new KeyframeEase(0, 50);
324
+ layer.opacity.setTemporalEaseAtKey(1, [easeOut], [easeIn]);
325
+ layer.opacity.setTemporalEaseAtKey(2, [easeOut], [easeIn]);
326
+ };
327
+
328
+ private slideUp = (layer: Layer) => {
329
+ const finalPosition = layer.position.value;
330
+ layer.position.setValueAtTime(layer.startTime, [finalPosition[0], finalPosition[1] + 100]);
331
+ layer.position.setValueAtTime(layer.startTime + this.inDuration, finalPosition);
332
+ let easeIn = new KeyframeEase(0, 20);
333
+ let easeOut = new KeyframeEase(0, 50);
334
+ layer.position.setTemporalEaseAtKey(1, [easeOut], [easeIn]);
335
+ layer.position.setTemporalEaseAtKey(2, [easeOut], [easeIn]);
336
+ };
337
+
338
+ private sortSlides = (layers: Layer[] | _ItemClasses[]) => {
339
+ layers.sort((a, b) => {
340
+ const nameA = a.name.split("-")[0];
341
+ const nameB = b.name.split("-")[0];
342
+ return parseInt(nameA) - parseInt(nameB);
343
+ });
344
+ };
345
+
346
+ private enableTimeRemap = (layer: AVLayer) => {
347
+ layer.timeRemapEnabled = true;
348
+ const property = layer.property("ADBE Time Remapping") as Property;
349
+ if (!is.layer || layer.canSetTimeRemapEnabled === false) return layer;
350
+ if (property) {
351
+ property.addKey(property.keyTime(property.numKeys) - layer.containingComp.frameDuration);
352
+ property.removeKey(property.numKeys);
353
+ }
354
+ return layer;
355
+ };
356
+ }
357
+
358
+ export { KT_Slides };
359
+
360
+ class __KT_SlidesAPI {
361
+ private slidesInstance: KT_Slides | null = null;
362
+ static import = (options: KT_SlidesOptions = {}) => {
363
+ const slides = new KT_Slides(options);
364
+ slides.import(options.slideMultiplier);
365
+ return slides;
366
+ };
367
+
368
+ static refreshAll = (slidesInstance: KT_Slides) => {
369
+ slidesInstance.batchSplitSlides();
370
+ };
371
+
372
+ static refreshSelected = (slidesInstance: KT_Slides) => {
373
+ slidesInstance.splitSlides();
374
+ };
375
+ }
376
+
377
+ export const KT_SlidesAPI = __KT_SlidesAPI;
@@ -0,0 +1,36 @@
1
+ import { describe, it, expect, runTests, beforeAll, afterAll, beforeEach, afterEach } from "kt-testing-suite-core";
2
+ import { KT_Slides } from "../index";
3
+
4
+ describe("KtForproTools Tests", () => {
5
+ it("should import the slides module", () => {
6
+ const duration = 30;
7
+ const slideMultiplier = 16;
8
+ const width = 3840;
9
+ const height = 2160;
10
+
11
+ const slides = new KT_Slides({
12
+ name: "Test Slides",
13
+ duration: duration,
14
+ width: width,
15
+ height: height,
16
+ comp: app.project.activeItem as CompItem,
17
+ slideMultiplier: slideMultiplier,
18
+ });
19
+ // slides.import();
20
+ // slides.splitSlides();
21
+ const startTime = new Date().getTime();
22
+ // slides.batchSplitSlides();
23
+ slides.splitSlides();
24
+
25
+ const endTime = new Date().getTime();
26
+ const timeTaken = endTime - startTime;
27
+ //display time taken in minutes and seconds format mm:ss
28
+ $.writeln(
29
+ "Time taken: "
30
+ .concat(Math.floor(timeTaken / 60000).toFixed(0), ":")
31
+ .concat(((timeTaken % 60000) / 1000).toFixed(0), "s")
32
+ );
33
+ });
34
+ });
35
+
36
+ runTests();
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES3",
4
+ "module": "CommonJS",
5
+ "outDir": "./dist",
6
+ "rootDir": "./src",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "lib": [],
12
+ "sourceMap": true,
13
+ "types": [
14
+ "./node_modules/kt-core/src/lib/kt-types",
15
+ "./node_modules/types-for-adobe/shared/global",
16
+ "./node_modules/types-for-adobe/shared/JavaScript",
17
+ "./node_modules/types-for-adobe/AfterEffects/23.0"
18
+ ]
19
+ },
20
+ "include": [
21
+ "src"
22
+ ]
23
+ }