framo
Advanced tools
Comparing version 0.0.6 to 0.0.7
@@ -1,323 +0,1 @@ | ||
var __defProp = Object.defineProperty; | ||
var __markAsModule = (target) => __defProp(target, "__esModule", {value: true}); | ||
var __export = (target, all) => { | ||
for (var name in all) | ||
__defProp(target, name, {get: all[name], enumerable: true}); | ||
}; | ||
var __async = (__this, __arguments, generator) => { | ||
return new Promise((resolve, reject) => { | ||
var fulfilled = (value) => { | ||
try { | ||
step(generator.next(value)); | ||
} catch (e) { | ||
reject(e); | ||
} | ||
}; | ||
var rejected = (value) => { | ||
try { | ||
step(generator.throw(value)); | ||
} catch (e) { | ||
reject(e); | ||
} | ||
}; | ||
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); | ||
step((generator = generator.apply(__this, __arguments)).next()); | ||
}); | ||
}; | ||
// src/browser.ts | ||
__markAsModule(exports); | ||
__export(exports, { | ||
Framo: () => Framo, | ||
FramoImageExtension: () => FramoImageExtension | ||
}); | ||
// src/constants/constants.ts | ||
var FRAMO_NOT_INITIALIZED = "Framo has not been initialized. Please call initializeFramo before using any of the features"; | ||
// src/stores/scripts.ts | ||
var ScriptNames; | ||
(function(ScriptNames2) { | ||
ScriptNames2["FFMPEG"] = "ffmpeg"; | ||
})(ScriptNames || (ScriptNames = {})); | ||
var ScriptStore = [ | ||
{ | ||
name: ScriptNames.FFMPEG, | ||
src: "https://unpkg.com/@ffmpeg/ffmpeg@0.10.0/dist/ffmpeg.min.js" | ||
} | ||
]; | ||
// src/services/script-loader.service.ts | ||
var LoadStatus; | ||
(function(LoadStatus2) { | ||
LoadStatus2["AlreadyLoaded"] = "Already Loaded"; | ||
LoadStatus2["Loaded"] = "Loaded"; | ||
})(LoadStatus || (LoadStatus = {})); | ||
var ScriptService = class { | ||
constructor() { | ||
this.scripts = {}; | ||
ScriptStore.forEach((script) => { | ||
this.scripts[script.name] = { | ||
loaded: false, | ||
src: script.src | ||
}; | ||
}); | ||
} | ||
load(...scripts) { | ||
const promises = []; | ||
scripts.forEach((script) => promises.push(this.loadScript(script))); | ||
return Promise.all(promises); | ||
} | ||
loadScript(name) { | ||
return new Promise((resolve) => { | ||
if (this.scripts[name].loaded) { | ||
const response = { | ||
script: name, | ||
loaded: true, | ||
status: LoadStatus.AlreadyLoaded | ||
}; | ||
resolve(response); | ||
} else { | ||
const script = document.createElement("script"); | ||
script.type = "text/javascript"; | ||
script.src = this.scripts[name].src; | ||
script.onload = () => { | ||
this.scripts[name].loaded = true; | ||
const response = { | ||
script: name, | ||
loaded: true, | ||
status: LoadStatus.Loaded | ||
}; | ||
resolve(response); | ||
}; | ||
script.onerror = (error) => { | ||
console.error(error); | ||
const response = { | ||
script: name, | ||
loaded: false, | ||
status: LoadStatus.Loaded | ||
}; | ||
resolve(response); | ||
}; | ||
document.getElementsByTagName("head")[0].appendChild(script); | ||
} | ||
}); | ||
} | ||
}; | ||
// src/services/ffmpeg.service.ts | ||
var scripService = new ScriptService(); | ||
var FfmpegService = class extends EventTarget { | ||
constructor() { | ||
super(...arguments); | ||
this.isReady = false; | ||
this.progress = new EventTarget(); | ||
this.initializeFramo = () => __async(this, null, function* () { | ||
yield scripService.loadScript(ScriptNames.FFMPEG); | ||
this.ffmpeg = FFmpeg.createFFmpeg({ | ||
log: true | ||
}); | ||
yield this.ffmpeg.load(); | ||
this.initializeProgressForwarding(); | ||
this.isReady = true; | ||
}); | ||
this.fetchFile = (filename, file) => __async(this, null, function* () { | ||
this.ffmpeg.FS("writeFile", filename, yield FFmpeg.fetchFile(file)); | ||
}); | ||
} | ||
static getInstance() { | ||
if (!FfmpegService.instance) { | ||
FfmpegService.instance = new FfmpegService(); | ||
} | ||
return FfmpegService.instance; | ||
} | ||
initializeProgressForwarding() { | ||
this.ffmpeg.setProgress((event) => { | ||
this.progress.dispatchEvent(new CustomEvent("progress", {detail: event})); | ||
}); | ||
} | ||
}; | ||
// src/utility/utility.ts | ||
var Utility = class { | ||
static getScale(dimensions) { | ||
var _a, _b; | ||
if (!dimensions.height && !dimensions.width) { | ||
return ""; | ||
} | ||
return `scale=${(_a = dimensions.width) != null ? _a : -2}:${(_b = dimensions.height) != null ? _b : -2}`; | ||
} | ||
}; | ||
// src/services/frame-extractor.service.ts | ||
var outputFilePrefix = "out"; | ||
var outputFileDigits = 6; | ||
var FrameExtractorService = class { | ||
constructor() { | ||
this.ffmpegService = FfmpegService.getInstance(); | ||
this.extractFrames = (config) => __async(this, null, function* () { | ||
try { | ||
yield this.ffmpegService.fetchFile(config.filename, config.file); | ||
const {parameters, outputFilenames} = yield this.getParameters(config); | ||
yield this.ffmpegService.ffmpeg.run(...parameters); | ||
const blobs = outputFilenames.map((filename) => { | ||
const data = this.ffmpegService.ffmpeg.FS("readFile", filename); | ||
return new Blob([data.buffer]); | ||
}); | ||
return blobs; | ||
} catch (error) { | ||
throw new Error(error); | ||
} | ||
}); | ||
} | ||
static getInstance() { | ||
if (!FrameExtractorService.instance) { | ||
FrameExtractorService.instance = new FrameExtractorService(); | ||
} | ||
return FrameExtractorService.instance; | ||
} | ||
getParameters(config) { | ||
return __async(this, null, function* () { | ||
const auxillaryParameters = ["-y"]; | ||
let parametersResponse = { | ||
parameters: [], | ||
outputFilenames: [] | ||
}; | ||
if (config.timePoints) { | ||
parametersResponse = this.getTimeBasedParameters(config); | ||
} else if (config.interval) { | ||
parametersResponse = yield this.getIntervalBasedParameters(config); | ||
} | ||
const response = { | ||
parameters: [...parametersResponse.parameters, ...auxillaryParameters], | ||
outputFilenames: parametersResponse.outputFilenames | ||
}; | ||
return response; | ||
}); | ||
} | ||
getTimeBasedParameters(config) { | ||
var _a; | ||
const inParameters = []; | ||
const outParameters = []; | ||
const outputFilenames = []; | ||
const scale = config.resolution ? Utility.getScale(config.resolution) : ""; | ||
(_a = config.timePoints) == null ? void 0 : _a.forEach((timePoint, index) => { | ||
inParameters.push(...this.getSingleTimeBasedParameter(timePoint, config.filename)); | ||
const outputFilename = this.getOutputFilename(index, config.outputExtension); | ||
outputFilenames.push(outputFilename); | ||
outParameters.push(...this.getOutputMappingParameter(index, outputFilename, scale)); | ||
}); | ||
const response = { | ||
parameters: [...inParameters, ...outParameters], | ||
outputFilenames | ||
}; | ||
return response; | ||
} | ||
getSingleTimeBasedParameter(time, filename) { | ||
return ["-ss", `${time}`, "-i", filename]; | ||
} | ||
getOutputMappingParameter(index, outputFilename, scale) { | ||
const outputMapping = ["-map", `${index}:v`, "-frames:v", "1"]; | ||
if (scale) { | ||
outputMapping.push(...["-vf", scale]); | ||
} | ||
outputMapping.push(outputFilename); | ||
return outputMapping; | ||
} | ||
getOutputFilename(index, extension) { | ||
return `out_${index}_${Date.now()}.${extension}`; | ||
} | ||
getOutputFilenameForIntervalBasedParameters(index, extension) { | ||
const filenumber = index.toString().padStart(outputFileDigits, "0"); | ||
return `out_${filenumber}.${extension}`; | ||
} | ||
getIntervalBasedParameters(config) { | ||
return __async(this, null, function* () { | ||
const videoDuration = yield this.getVideoDuration(config.file); | ||
const interval = config.interval; | ||
const rate = (1 / interval).toFixed(2); | ||
const scale = config.resolution ? Utility.getScale(config.resolution) : ""; | ||
const inParameters = this.getInParametersForIntervalBasedExtraction(config.filename, rate, scale); | ||
const outParameters = [`out_%0${outputFileDigits}d.${config.outputExtension}`]; | ||
const outputFilenames = []; | ||
for (let i = interval, j = 1; i <= videoDuration; i += interval, j++) { | ||
const outputFilename = this.getOutputFilenameForIntervalBasedParameters(j, config.outputExtension); | ||
outputFilenames.push(outputFilename); | ||
} | ||
const response = { | ||
parameters: [...inParameters, ...outParameters], | ||
outputFilenames | ||
}; | ||
return response; | ||
}); | ||
} | ||
getVideoDuration(file) { | ||
return new Promise((resolve, reject) => { | ||
const video = document.createElement("video"); | ||
video.preload = "metadata"; | ||
video.onloadedmetadata = () => { | ||
window.URL.revokeObjectURL(video.src); | ||
resolve(video.duration); | ||
}; | ||
video.onerror = (error) => { | ||
reject(error); | ||
}; | ||
video.src = typeof file === "string" ? file : URL.createObjectURL(file); | ||
}); | ||
} | ||
getOutputFilenamesForIntervalBasedParameters(interval, videoDuration, extension) { | ||
const filenames = []; | ||
const numberOfFrames = videoDuration / interval; | ||
for (let i = 1; i <= numberOfFrames; i++) { | ||
const filenumber = i.toString().padStart(outputFileDigits, "0"); | ||
filenames.push(`${outputFilePrefix}_${filenumber}.${extension}`); | ||
} | ||
return filenames; | ||
} | ||
getInParametersForIntervalBasedExtraction(filename, rate, scale) { | ||
const inParameters = ["-i", filename, "-vf"]; | ||
if (scale) { | ||
inParameters.push(`fps=${rate},${scale}`); | ||
} else { | ||
inParameters.push(`fps=${rate}`); | ||
} | ||
return inParameters; | ||
} | ||
}; | ||
// src/main.ts | ||
var Main = class { | ||
constructor() { | ||
this.ffmpegService = FfmpegService.getInstance(); | ||
this.frameExtractorService = FrameExtractorService.getInstance(); | ||
this.initializeFramo = () => this.ffmpegService.initializeFramo(); | ||
this.extractFrames = (config) => this.frameExtractorService.extractFrames(config); | ||
} | ||
initializationGuard() { | ||
if (!this.ffmpegService.isReady) { | ||
throw new Error(FRAMO_NOT_INITIALIZED); | ||
} | ||
return this; | ||
} | ||
}; | ||
// src/models/generic-ffmpeg.model.ts | ||
var FramoImageExtension; | ||
(function(FramoImageExtension2) { | ||
FramoImageExtension2["JPEG"] = "jpeg"; | ||
FramoImageExtension2["JPG"] = "jpg"; | ||
FramoImageExtension2["PNG"] = "png"; | ||
FramoImageExtension2["BMP"] = "bmp"; | ||
})(FramoImageExtension || (FramoImageExtension = {})); | ||
// src/browser.ts | ||
var Framo = class { | ||
constructor() { | ||
this.ffmpegService = FfmpegService.getInstance(); | ||
this.frameExtractorService = FrameExtractorService.getInstance(); | ||
this.main = new Main(); | ||
this.initializeFramo = () => this.main.initializeFramo(); | ||
this.extractFrames = (config) => this.main.initializationGuard().extractFrames(config); | ||
this.progress = this.ffmpegService.progress; | ||
} | ||
}; | ||
var R=Object.defineProperty;var B=p=>R(p,"__esModule",{value:!0});var I=(p,e)=>{for(var t in e)R(p,t,{get:e[t],enumerable:!0})};var u=(p,e,t)=>new Promise((s,r)=>{var i=a=>{try{m(t.next(a))}catch(l){r(l)}},n=a=>{try{m(t.throw(a))}catch(l){r(l)}},m=a=>a.done?s(a.value):Promise.resolve(a.value).then(i,n);m((t=t.apply(p,e)).next())});B(exports);I(exports,{Framo:()=>w,FramoImageExtension:()=>h});var b="Framo has not been initialized. Please call initializeFramo before using any of the features";var f;(function(e){e.FFMPEG="ffmpeg"})(f||(f={}));var E=[{name:f.FFMPEG,src:"https://unpkg.com/@ffmpeg/ffmpeg@0.10.0/dist/ffmpeg.min.js"}];var d;(function(t){t.AlreadyLoaded="Already Loaded",t.Loaded="Loaded"})(d||(d={}));var P=class{constructor(){this.scripts={};E.forEach(e=>{this.scripts[e.name]={loaded:!1,src:e.src}})}load(...e){let t=[];return e.forEach(s=>t.push(this.loadScript(s))),Promise.all(t)}loadScript(e){return new Promise(t=>{if(this.scripts[e].loaded){let s={script:e,loaded:!0,status:d.AlreadyLoaded};t(s)}else{let s=document.createElement("script");s.type="text/javascript",s.src=this.scripts[e].src,s.onload=()=>{this.scripts[e].loaded=!0;let r={script:e,loaded:!0,status:d.Loaded};t(r)},s.onerror=r=>{console.error(r);let i={script:e,loaded:!1,status:d.Loaded};t(i)},document.getElementsByTagName("head")[0].appendChild(s)}})}};var L=new P,o=class extends EventTarget{constructor(){super(...arguments);this.isReady=!1;this.progress=new EventTarget;this.initializeFramo=()=>u(this,null,function*(){yield L.loadScript(f.FFMPEG),this.ffmpeg=FFmpeg.createFFmpeg({log:!0}),yield this.ffmpeg.load(),this.initializeProgressForwarding(),this.isReady=!0});this.fetchFile=(e,t)=>u(this,null,function*(){this.ffmpeg.FS("writeFile",e,yield FFmpeg.fetchFile(t))})}static getInstance(){return o.instance||(o.instance=new o),o.instance}initializeProgressForwarding(){this.ffmpeg.setProgress(e=>{this.progress.dispatchEvent(new CustomEvent("progress",{detail:e}))})}};var F=class{static getScale(e){var t,s;return!e.height&&!e.width?"":`scale=${(t=e.width)!=null?t:-2}:${(s=e.height)!=null?s:-2}`}};var $="out",v=6,c=class{constructor(){this.ffmpegService=o.getInstance();this.extractFrames=e=>u(this,null,function*(){try{yield this.ffmpegService.fetchFile(e.filename,e.file);let{parameters:t,outputFilenames:s}=yield this.getParameters(e);return yield this.ffmpegService.ffmpeg.run(...t),s.map(i=>{let n=this.ffmpegService.ffmpeg.FS("readFile",i);return new Blob([n.buffer])})}catch(t){throw new Error(t)}})}static getInstance(){return c.instance||(c.instance=new c),c.instance}getParameters(e){return u(this,null,function*(){let t=["-y"],s={parameters:[],outputFilenames:[]};return e.timePoints?s=this.getTimeBasedParameters(e):e.interval&&(s=yield this.getIntervalBasedParameters(e)),{parameters:[...s.parameters,...t],outputFilenames:s.outputFilenames}})}getTimeBasedParameters(e){var m;let t=[],s=[],r=[],i=e.resolution?F.getScale(e.resolution):"";return(m=e.timePoints)==null||m.forEach((a,l)=>{t.push(...this.getSingleTimeBasedParameter(a,e.filename));let g=this.getOutputFilename(l,e.outputExtension);r.push(g),s.push(...this.getOutputMappingParameter(l,g,i))}),{parameters:[...t,...s],outputFilenames:r}}getSingleTimeBasedParameter(e,t){return["-ss",`${e}`,"-i",t]}getOutputMappingParameter(e,t,s){let r=["-map",`${e}:v`,"-frames:v","1"];return s&&r.push("-vf",s),r.push(t),r}getOutputFilename(e,t){return`out_${e}_${Date.now()}.${t}`}getOutputFilenameForIntervalBasedParameters(e,t){return`out_${e.toString().padStart(v,"0")}.${t}`}getIntervalBasedParameters(e){return u(this,null,function*(){let t=yield this.getVideoDuration(e.file),s=e.interval,r=(1/s).toFixed(2),i=e.resolution?F.getScale(e.resolution):"",n=this.getInParametersForIntervalBasedExtraction(e.filename,r,i),m=[`out_%0${v}d.${e.outputExtension}`],a=[];for(let g=s,S=1;g<=t;g+=s,S++){let y=this.getOutputFilenameForIntervalBasedParameters(S,e.outputExtension);a.push(y)}return{parameters:[...n,...m],outputFilenames:a}})}getVideoDuration(e){return new Promise((t,s)=>{let r=document.createElement("video");r.preload="metadata",r.onloadedmetadata=()=>{window.URL.revokeObjectURL(r.src),t(r.duration)},r.onerror=i=>{s(i)},r.src=typeof e=="string"?e:URL.createObjectURL(e)})}getOutputFilenamesForIntervalBasedParameters(e,t,s){let r=[],i=t/e;for(let n=1;n<=i;n++){let m=n.toString().padStart(v,"0");r.push(`${$}_${m}.${s}`)}return r}getInParametersForIntervalBasedExtraction(e,t,s){let r=["-i",e,"-vf"];return s?r.push(`fps=${t},${s}`):r.push(`fps=${t}`),r}};var x=class{constructor(){this.ffmpegService=o.getInstance();this.frameExtractorService=c.getInstance();this.initializeFramo=()=>this.ffmpegService.initializeFramo();this.extractFrames=e=>this.frameExtractorService.extractFrames(e)}initializationGuard(){if(!this.ffmpegService.isReady)throw new Error(b);return this}};var h;(function(r){r.JPEG="jpeg",r.JPG="jpg",r.PNG="png",r.BMP="bmp"})(h||(h={}));var w=class{constructor(){this._ffmpegService=o.getInstance();this._main=new x;this.initializeFramo=()=>this._main.initializeFramo();this.extractFrames=e=>this._main.initializationGuard().extractFrames(e);this.progress=this._ffmpegService.progress}}; |
@@ -1,11 +0,7 @@ | ||
import { Main } from "./main"; | ||
import { FrameRequestConfig } from "./models/frame-extractor.model"; | ||
import { FramoImageExtension } from "./models/generic-ffmpeg.model"; | ||
import { FfmpegService } from "./services/ffmpeg.service"; | ||
import { FrameExtractorService } from "./services/frame-extractor.service"; | ||
export { FrameRequestConfig, FramoImageExtension }; | ||
export declare class Framo { | ||
ffmpegService: FfmpegService; | ||
frameExtractorService: FrameExtractorService; | ||
main: Main; | ||
private _ffmpegService; | ||
private _main; | ||
/** | ||
@@ -12,0 +8,0 @@ * @description Initialize Framo and its dependencies |
@@ -8,16 +8,14 @@ "use strict"; | ||
const ffmpeg_service_1 = require("./services/ffmpeg.service"); | ||
const frame_extractor_service_1 = require("./services/frame-extractor.service"); | ||
class Framo { | ||
constructor() { | ||
this.ffmpegService = ffmpeg_service_1.FfmpegService.getInstance(); | ||
this.frameExtractorService = frame_extractor_service_1.FrameExtractorService.getInstance(); | ||
this.main = new main_1.Main(); | ||
this._ffmpegService = ffmpeg_service_1.FfmpegService.getInstance(); | ||
this._main = new main_1.Main(); | ||
/** | ||
* @description Initialize Framo and its dependencies | ||
*/ | ||
this.initializeFramo = () => this.main.initializeFramo(); | ||
this.initializeFramo = () => this._main.initializeFramo(); | ||
/** | ||
* @description Extract frames from a video at specific times or regular intervals | ||
*/ | ||
this.extractFrames = (config) => this.main.initializationGuard().extractFrames(config); | ||
this.extractFrames = (config) => this._main.initializationGuard().extractFrames(config); | ||
/** | ||
@@ -27,3 +25,3 @@ * @description Emits the progress of the operation | ||
*/ | ||
this.progress = this.ffmpegService.progress; | ||
this.progress = this._ffmpegService.progress; | ||
} | ||
@@ -30,0 +28,0 @@ } |
{ | ||
"name": "framo", | ||
"version": "0.0.6", | ||
"version": "0.0.7", | ||
"description": "Awesome on-the-fly features using FFmpeg WASM", | ||
@@ -5,0 +5,0 @@ "author": "Aditya Krishnan <adityakrshnn@gmail.com>", |
@@ -16,3 +16,3 @@ <!-- PROJECT LOGO --> | ||
<img alt="Framo bundle size" src="https://img.shields.io/bundlephobia/min/framo"/> | ||
<img alt="Framo npm version" src="https://img.shields.io/npm/v/framo"/> | ||
<a href="https://www.npmjs.com/package/framo"><img alt="Framo npm version" src="https://img.shields.io/npm/v/framo"/></a> | ||
<img alt="Framo Maintained yes" src="https://img.shields.io/badge/Maintained-Yes-brightgreen"/> | ||
@@ -72,2 +72,4 @@ </p> | ||
### Installation | ||
To use Framo in your project | ||
@@ -79,2 +81,14 @@ | ||
### Usage | ||
Initialize framo with [initializeFramo()](https://adityakrshnn.github.io/framo/classes/framo.html#initializeframo) before using any of its features | ||
```sh | ||
const framo = new Framo(); | ||
await framo.initializeFramo(); | ||
... | ||
OR | ||
framo.initializeFramo().then(() => { | ||
... | ||
}) | ||
``` | ||
<!-- USAGE EXAMPLES --> | ||
@@ -81,0 +95,0 @@ |
Sorry, the diff of this file is not supported yet
60211
38
147
484