Comparing version 2.0.0 to 2.1.0
const canvideo = require("../index"); | ||
const path = require('path'); | ||
var animation = new canvideo.Animation(); | ||
canvideo.setTempPath("C://Users/username/Desktop/Trash") | ||
canvideo.setTempPath(path.join(__dirname, "./res")); | ||
var animation = new canvideo.Animation({ width: 200, height: 200 }, 4); | ||
animation.add(new canvideo.Rectangle(50, 50, 100, 100, "cyan")); | ||
animation.add(new canvideo.Rectangle(100, 100, 100, 100, "magenta")); | ||
animation | ||
.addKeyframe(new canvideo.Keyframe(0.25) | ||
.addShape(new canvideo.Rectangle(0, 0, 100, 100, "blue")) | ||
) | ||
.addKeyframe(new canvideo.Keyframe(0.5) | ||
.addShape(new canvideo.Rectangle(50, 50, 100, 100, "cyan")) | ||
) | ||
.addKeyframe(new canvideo.Keyframe(0.75) | ||
.addShape(new canvideo.Rectangle(100, 100, 100, 100, "white")) | ||
) | ||
animation.on("end", () => { | ||
animation.on("done", () => { | ||
console.log("done") | ||
}); | ||
animation.on("error", () => { | ||
console.log("error!"); | ||
}); | ||
animation.export("C://Users/username/Desktop/Trash/output.mp4"); | ||
animation.export(path.join(__dirname, "./res/output.mp4")); |
100
index.d.ts
import fs = require('fs'); | ||
import ffmpeg = require('fluent-ffmpeg'); | ||
import events = require('events'); | ||
import canvas = require('canvas'); | ||
declare namespace canvideo { | ||
interface CodecData { | ||
format: string, | ||
audio: string, | ||
video: string, | ||
duration: string, | ||
video_details: Array<string> | ||
type colorRepresentor = string | [number, number, number]; | ||
type color = canvideo.Color | colorRepresentor; | ||
type animationSize = AnimationSize | AnimationSizeShort; | ||
type evenNumber = number; | ||
interface AnimationSize { | ||
width: evenNumber; | ||
height: evenNumber; | ||
} | ||
interface Progress { | ||
frames: number, | ||
currentFps: number, | ||
currentKbps: number, | ||
targetSize: 2, | ||
timemark: string, | ||
percent: number | ||
interface AnimationSizeShort { | ||
w: evenNumber; | ||
h: evenNumber; | ||
} | ||
interface AnimationOptions { | ||
size: animationSize; | ||
fps: number; | ||
} | ||
interface AnimationOptionsAll implements animationSize { | ||
fps: number; | ||
} | ||
type color = canvideo.Color | string | [number, number, number]; | ||
export function setTempPath(path: fs.PathLike): void; | ||
export class Color { | ||
constructor(color: color): this; | ||
constructor(color: colorRepresentor): this; | ||
color: number; | ||
value: number; | ||
@@ -33,8 +37,10 @@ static isValid(color: any): boolean; | ||
export class Shape extends Color { | ||
export abstract class Shape { | ||
constructor(color: color): this; | ||
color: Color; | ||
} | ||
export class Rectangle extends Shape { | ||
constructor(x: number, y: number, width: number, height: number, color?: color): Rectangle; | ||
constructor(x: number, y: number, width: number, height: number, color?: color): this; | ||
@@ -45,17 +51,51 @@ x: number; | ||
height: number; | ||
draw(ctx: canvas.CanvasRenderingContext2D): void; | ||
} | ||
export class Keyframe { | ||
constructor(startTime: number): this; | ||
shapes: Array<Shape>; | ||
animation: Animation; | ||
frameNumber: number; | ||
addShape(shape: Shape): this; | ||
render(shapes: Array<Shape>): void; | ||
} | ||
export interface AnimationAfterExport { | ||
on(event: "done", handler: () => void): this; | ||
on(event: "error", handler: () => void): this; | ||
} | ||
export abstract class AnimationAfterExport { | ||
keyframes: Array<Keyframe>; | ||
tempPath: fs.PathLike; | ||
width: evenNumber; | ||
height: evenNumber; | ||
fps: number; | ||
get spf(): number; | ||
} | ||
export interface Animation { | ||
on(event: 'start', handler: (commandLine?: string) => void): this; | ||
on(event: 'codecData', handler: (data?: CodecData) => void): this; | ||
on(event: 'progress', handler: (progress?: Progress) => void): this; | ||
on(event: 'stderr', handler: (stderr?: string) => void): this; | ||
on(event: 'error', handler: (err?: Error, stdout?: string, stderr?: string) => void): this; | ||
on(event: 'end', handler: (stdout?: string, stderr?: string) => void): this; | ||
on(event: "done", handler: () => void): this; | ||
on(event: "error", handler: () => void): this; | ||
} | ||
export class Animation extends ffmpeg { | ||
constructor(): Animation; | ||
export class Animation extends events.EventEmitter { | ||
constructor(width: evenNumber, height: evenNumber, fps: number): this; | ||
constructor(size: animationSize, fps: number): this; | ||
constructor(options: AnimationOptions): this; | ||
constructor(options: AnimationOptionsAll): this; | ||
add(shape: Shape): this; | ||
export(filePath: fs.PathLike, tempPath?: fs.PathLike): this; | ||
keyframes: Array<Keyframe>; | ||
tempPath: fs.PathLike; | ||
width: evenNumber; | ||
height: evenNumber; | ||
fps: number; | ||
get spf(): number; | ||
addKeyFrame(keyframe: Keyframe): this; | ||
export(filePath: fs.PathLike): AnimationAfterExport; | ||
} | ||
@@ -62,0 +102,0 @@ } |
318
index.js
@@ -8,2 +8,3 @@ //Main file | ||
const path = require('path'); | ||
const EventEmitter = require('events').EventEmitter; | ||
@@ -15,2 +16,6 @@ //Npm Modules | ||
//My Modules | ||
const AsyncLoop = require("./lib/asyncLoop"); | ||
const myMath = require("./lib/myMath"); | ||
//Set ffmpeg path | ||
@@ -45,3 +50,3 @@ ffmpeg.setFfmpegPath(ffmpegPath); | ||
if (canvideo.Color.isValid(color)) { | ||
this.color = color; | ||
this.value = color; | ||
} | ||
@@ -92,8 +97,47 @@ else { | ||
//Shape | ||
canvideo.Shape = class extends canvideo.Color { | ||
canvideo.Shape = class { | ||
constructor(color) { | ||
super(color); | ||
if (color instanceof canvideo.Color) { | ||
this.color = color; | ||
} | ||
else { | ||
this.color = new canvideo.Color(color); | ||
} | ||
}; | ||
} | ||
//Control Point | ||
canvideo.ControlPoint = class { | ||
static defaultSetterX(value) { | ||
this._x = value; | ||
} | ||
static defaultSetterY(value) { | ||
this._y = value; | ||
} | ||
constructor(shape, setterX = defaultSetterX, setterY = defaultSetterY) { | ||
if (shape instanceof canvideo.Shape) { | ||
this.shape = shape; | ||
this.setterX = setterX; | ||
this.setterY = setterY; | ||
this._x = 0; | ||
this._y = 0; | ||
} | ||
else { | ||
throw new TypeError(`Shape: ${shape} is not of type Shape`); | ||
} | ||
} | ||
set x(value) { | ||
return this.setterX(value); | ||
} | ||
get x() { | ||
return this._x; | ||
} | ||
set y(value) { | ||
return this.setterY(value); | ||
} | ||
get y() { | ||
return this._y; | ||
} | ||
} | ||
//Rectangle | ||
@@ -128,58 +172,268 @@ canvideo.Rectangle = class extends canvideo.Shape { | ||
} | ||
draw(ctx) { | ||
ctx.fillStyle = this.color.value; | ||
ctx.fillRect(this.x, this.y, this.width, this.height); | ||
} | ||
} | ||
//Animation class | ||
canvideo.Animation = class extends ffmpeg { | ||
constructor() { | ||
super(); | ||
//Frame Class | ||
canvideo.Keyframe = class { | ||
constructor(startTime) { | ||
if (typeof startTime == 'number') { | ||
this.startTime = startTime; | ||
} | ||
else { | ||
throw new TypeError("startTime must be a number."); | ||
} | ||
this.shapes = []; | ||
} | ||
set animation(value) { | ||
if (value instanceof canvideo.Animation) { | ||
this._animation = value; | ||
} | ||
else { | ||
throw new TypeError("Animation is not of type Animation."); | ||
} | ||
} | ||
get animation() { | ||
return this._animation; | ||
} | ||
set frameNumber(value) { | ||
if (typeof value == 'number') { | ||
this._frameNumber = value; | ||
} | ||
else { | ||
throw new TypeError("frameNumber must be a number."); | ||
} | ||
} | ||
get frameNumber() { | ||
return this._frameNumber; | ||
} | ||
add(shape) { | ||
if (shape instanceof canvideo.Rectangle) { | ||
addShape(shape) { | ||
if (shape instanceof canvideo.Shape) { | ||
this.shapes.push(shape); | ||
} | ||
else { | ||
throw new TypeError("shape is not of type Rectangle"); | ||
throw new TypeError("shape is not of type Shape."); | ||
} | ||
return this; | ||
} | ||
render(shapes = []) { | ||
if (typeof this.frameNumber == 'number') { | ||
if (shapes instanceof Array) { | ||
for (var i = 0; i < shapes.length; i++) { | ||
if (!(shapes[i] instanceof canvideo.Shape)) { | ||
throw new TypeError(`shapes[${i}] is not a Shape.`); | ||
} | ||
} | ||
} | ||
else { | ||
throw new TypeError("Shapes must be an array of shapes."); | ||
} | ||
export(filePath, tempPath = config.tempPath) { | ||
if (typeof filePath == 'string' || filePath instanceof Buffer || filePath instanceof URL) { | ||
if (path.extname(filePath) !== ".mp4") { | ||
throw new URIError(`File path: ${filePath} must have the extension .mp4.`); | ||
var shapesToRender = shapes.concat(this.shapes); | ||
this.animation.loop.goal += 1; | ||
//Render frame 0 | ||
var canvas = createCanvas(this.animation.width, this.animation.height); | ||
var ctx = canvas.getContext('2d'); | ||
for (var i = 0; i < shapesToRender.length; i++) { | ||
shapesToRender[i].draw(ctx); | ||
} | ||
var framePath = this.animation.tempPath + "/frame" + this.frameNumber + ".jpg"; | ||
canvas.createJPEGStream() | ||
.on("end", () => { | ||
this.animation.loop.emit("result", false); | ||
}) | ||
.on("error", err => { | ||
this.animation.loop.emit("result", err, this.frameNumber); | ||
}) | ||
.pipe(fs.createWriteStream(framePath)); | ||
//Render next keyframe | ||
if (this.frameNumber + 1 < this.animation.keyframes.length) { | ||
this.animation.keyframes[this.frameNumber + 1].render(shapesToRender); | ||
} | ||
else { | ||
//This is the last frame | ||
} | ||
return this; | ||
} | ||
else { | ||
throw new TypeError(`File path: ${filePath} is not a valid path type.`); | ||
throw new TypeError("this.frameNumber is not a number"); | ||
} | ||
if (typeof tempPath == 'string' || tempPath instanceof Buffer || tempPath instanceof URL) { | ||
} | ||
} | ||
//Animation class | ||
canvideo.Animation = class extends EventEmitter { | ||
constructor(arg1 = { width: 200, height: 200 }, arg2, arg3) { | ||
//Constructor: width: number, height: number, fps | ||
//Constructor: { width: number, height: number }, fps | ||
//Constructor: { w: number, h: number }, fps | ||
//Constructor: { width: number, height: number, fps: number} | ||
//Constructor: { w: number, h: number, fps: number} | ||
//Constructor: { size: { width: number, height: number }, fps: number } | ||
//Constructor: { size: { w: number, h: number }, fps: number } | ||
var width, height, fps; | ||
function typeCheck(arg1, arg2, arg3) { | ||
if (typeof arg1 == 'number' && typeof arg2 == 'number' && typeof arg3 == 'number') { | ||
width = arg1, height = arg2, fps = arg3; | ||
} | ||
else if (typeof arg1 == 'object' && typeof arg2 == 'number' && typeof arg3 == 'undefined') { | ||
fps = arg2; | ||
if (typeof arg1.width == 'number' && typeof arg1.height == 'number') { | ||
width = arg1.width, height = arg1.height; | ||
} | ||
else if (typeof arg1.w == 'number' && typeof arg1.h == 'number') { | ||
width = arg1.w, height = arg1.h; | ||
} | ||
else { | ||
return false; | ||
} | ||
} | ||
else if (typeof arg1 == 'object' && typeof arg1.fps == 'number' && typeof arg2 == 'undefined' && typeof arg3 == 'undefined') { | ||
fps = arg1.fps; | ||
if (typeof arg1.size == 'object') { | ||
if (typeof arg1.size.width == 'number' && typeof arg1.size.height == 'number') { | ||
width = arg1.size.width, height = arg1.size.height; | ||
} | ||
else if (typeof arg1.size.w == 'number' && typeof arg1.size.h == 'number') { | ||
width = arg1.size.w, height = arg1.size.h; | ||
} | ||
else { | ||
return false; | ||
} | ||
} | ||
else { | ||
if (typeof arg1.width == 'number' && typeof arg1.height == 'number') { | ||
width = arg1.width, height = arg1.height; | ||
} | ||
else if (typeof arg1.w == 'number' && typeof arg1.h == 'number') { | ||
width = arg1.w, height = arg1.h; | ||
} | ||
else { | ||
return false; | ||
} | ||
} | ||
} | ||
else { | ||
console.log("none") | ||
return false; | ||
} | ||
} | ||
if (typeCheck(arg1, arg2, arg3) === false) { | ||
throw new TypeError("Invalid constructor."); | ||
} | ||
if (myMath.isOdd(width)) { | ||
throw new TypeError("width must be an even number."); | ||
} | ||
if (myMath.isOdd(height)) { | ||
throw new TypeError("height must be an even number."); | ||
} | ||
super(); | ||
this.keyframes = []; | ||
this.tempPath = config.tempPath; | ||
this.loop = new AsyncLoop(); | ||
this.width = width; | ||
this.height = height; | ||
this.fps = fps; | ||
this.exported = false; | ||
this.command = ffmpeg(); | ||
this.command.on('end', () => { | ||
this.emit("done"); | ||
}); | ||
this.command.on('error', () => { | ||
this.emit("error"); | ||
}); | ||
} | ||
set tempPath(value) { | ||
if (this.exported) { | ||
throw new SyntaxError("Cannot change tempPath after exporting."); | ||
} | ||
if (typeof value == 'string' || value instanceof Buffer || value instanceof URL) { | ||
//Make sure it exists | ||
if (!fs.existsSync(tempPath)) { | ||
throw new URIError(`Path: ${tempPath} does not exist.`); | ||
if (fs.existsSync(value)) { | ||
this._tempPath = value; | ||
} | ||
else { | ||
throw new URIError(`Path: ${value} does not exist.`); | ||
} | ||
} | ||
else { | ||
throw new TypeError(`Path: ${tempPath} is not a valid path type.`); | ||
throw new TypeError(`Path: ${value} is not a valid path type.`); | ||
} | ||
} | ||
get tempPath() { | ||
return this._tempPath; | ||
} | ||
get spf() { | ||
return 1 / this.fps; | ||
} | ||
//Create the canvas | ||
var canvas = createCanvas(200, 200); | ||
var ctx = canvas.getContext('2d'); | ||
addKeyframe(keyframe) { | ||
if (this.exported) { | ||
throw new SyntaxError("Cannot add keyframe after exporting."); | ||
} | ||
if (keyframe instanceof canvideo.Keyframe) { | ||
var frameNumber = Math.round(keyframe.startTime * this.fps); | ||
keyframe.animation = this; | ||
keyframe.frameNumber = frameNumber; | ||
this.keyframes[frameNumber] = keyframe; | ||
} | ||
else { | ||
throw new TypeError("keyframe is not of type Keyframe."); | ||
} | ||
for (var i = 0; i < this.shapes.length; i++) { | ||
let { x, y, width, height, color } = this.shapes[i]; | ||
ctx.fillStyle = color; | ||
ctx.fillRect(x, y, width, height); | ||
return this; | ||
} | ||
export(filePath) { | ||
if (this.exported) { | ||
throw new SyntaxError("Cannot export twice."); | ||
} | ||
if (typeof filePath == 'string' || filePath instanceof Buffer || filePath instanceof URL) { | ||
if (path.extname(filePath) !== ".mp4") { | ||
throw new URIError(`File path: ${filePath} must have the extension .mp4.`); | ||
} | ||
} | ||
else { | ||
throw new TypeError(`File path: ${filePath} is not a valid path type.`); | ||
} | ||
//Make sure there is a keyframe at 0 seconds | ||
if (!(this.keyframes[0] instanceof canvideo.Keyframe)) { | ||
this.addKeyframe(new canvideo.Keyframe(0)); | ||
} | ||
//Save the canvas | ||
canvas.createJPEGStream().pipe(fs.createWriteStream(config.tempPath + "/frame0.jpg")); | ||
//Fill in the blank frames | ||
for (var i = 0; i < this.keyframes.length; i++) { | ||
if (!(this.keyframes[i] instanceof canvideo.Keyframe)) { | ||
this.addKeyframe(new canvideo.Keyframe(i * this.spf)); | ||
} | ||
} | ||
this.input(config.tempPath + "/frame%1d.jpg") | ||
.inputFPS(1) | ||
.save(filePath) | ||
.outputFPS(1); | ||
//Render the first frame | ||
this.keyframes[0].render(); | ||
this.loop.on("done", errors => { | ||
if (!errors) { | ||
this.command | ||
.input(config.tempPath + "/frame%1d.jpg") | ||
.inputFPS(this.fps) | ||
.save(filePath) | ||
.outputFPS(this.fps); | ||
} | ||
else { | ||
this.emit("error"); | ||
} | ||
}); | ||
this.exported = true; | ||
return this; | ||
@@ -186,0 +440,0 @@ } |
{ | ||
"name": "canvideo", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"description": "\u0016\u0016An open-source tool for Node.js that can make animations and generate videos.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
33999
15
603
2