diff --git a/dist/editor/index.html b/dist/editor/index.html
new file mode 100644
index 0000000..86a8f19
--- /dev/null
+++ b/dist/editor/index.html
@@ -0,0 +1,20 @@
+
+
+
+ PupCaps! - Subtitles Editor
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dist/editor/index.js b/dist/editor/index.js
new file mode 100644
index 0000000..4697b9f
--- /dev/null
+++ b/dist/editor/index.js
@@ -0,0 +1,632 @@
+(function (vue) {
+ 'use strict';
+
+ var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
+
+ var FileSaver_min$1 = {exports: {}};
+
+ var FileSaver_min = FileSaver_min$1.exports;
+
+ var hasRequiredFileSaver_min;
+
+ function requireFileSaver_min () {
+ if (hasRequiredFileSaver_min) return FileSaver_min$1.exports;
+ hasRequiredFileSaver_min = 1;
+ (function (module, exports) {
+ (function(a,b){b();})(FileSaver_min,function(){function b(a,b){return "undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c);},d.onerror=function(){console.error("could not download file");},d.send();}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send();}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"));}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b);}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof commonjsGlobal&&commonjsGlobal.global===commonjsGlobal?commonjsGlobal:void 0,a=f.navigator&&/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href);},4E4),setTimeout(function(){e(j);},0));}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else {var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i);});}}:function(b,d,e,g){if(g=g||open("","_blank"),g&&(g.document.title=g.document.body.innerText="downloading..."),"string"==typeof b)return c(b,d,e);var h="application/octet-stream"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\/[\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&"undefined"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),g?g.location.href=a:location=a,g=null;},k.readAsDataURL(b);}else {var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m);},4E4);}});f.saveAs=g.saveAs=g,(module.exports=g);});
+
+
+ } (FileSaver_min$1));
+ return FileSaver_min$1.exports;
+ }
+
+ var FileSaver_minExports = requireFileSaver_min();
+
+ const _hoisted_1$4 = { class: "file has-name" };
+ const _hoisted_2$2 = { class: "file-label" };
+ const _hoisted_3$1 = { class: "file-name is-fullwidth" };
+ var script$6 = /*@__PURE__*/ vue.defineComponent({
+ __name: 'srt-file-picker.component',
+ emits: ["file-selected"],
+ setup(__props, { emit: __emit }) {
+ const emit = __emit;
+ const selectedFile = vue.ref();
+ function onFileSelected(event) {
+ const file = event.target.files?.[0];
+ if (file) {
+ selectedFile.value = file;
+ emit('file-selected', file);
+ }
+ }
+ return (_ctx, _cache) => {
+ return (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$4, [
+ vue.createElementVNode("label", _hoisted_2$2, [
+ vue.createElementVNode("input", {
+ name: "srt_file",
+ class: "file-input",
+ type: "file",
+ accept: ".srt",
+ onChange: onFileSelected
+ }, null, 32 /* NEED_HYDRATION */),
+ _cache[0] || (_cache[0] = vue.createElementVNode("span", { class: "file-cta" }, [
+ vue.createElementVNode("span", { class: "file-icon" }, [
+ vue.createElementVNode("i", { class: "fa fa-upload" })
+ ]),
+ vue.createElementVNode("span", { class: "file-label" }, "Pick subs file")
+ ], -1 /* HOISTED */)),
+ vue.createElementVNode("span", _hoisted_3$1, vue.toDisplayString(selectedFile.value?.name), 1 /* TEXT */)
+ ])
+ ]));
+ };
+ }
+ });
+
+ script$6.__file = "src/editor/components/srt-file-picker.component.vue";
+
+ async function loadFile(file) {
+ const reader = new FileReader();
+ return new Promise((resolve, reject) => {
+ reader.onload = (loadEvent) => {
+ resolve(loadEvent.target?.result);
+ };
+ reader.onerror = (err) => {
+ reject(err);
+ };
+ reader.readAsText(file);
+ });
+ }
+
+ class Timecode {
+ hours;
+ minutes;
+ seconds;
+ millis;
+ constructor(millis) {
+ this.millis = millis % 1000;
+ this.hours = Math.floor(millis / 3_600_000);
+ const remainingMillisAfterHours = millis % 3_600_000;
+ this.minutes = Math.floor(remainingMillisAfterHours / 60_000);
+ const remainingMillisAfterMinutes = remainingMillisAfterHours % 60_000;
+ this.seconds = Math.floor(remainingMillisAfterMinutes / 1000);
+ }
+ get hh() {
+ return String(this.hours).padStart(2, '0');
+ }
+ get mm() {
+ return String(this.minutes).padStart(2, '0');
+ }
+ get ss() {
+ return String(this.seconds).padStart(2, '0');
+ }
+ get SSS() {
+ return String(this.millis).padStart(3, '0');
+ }
+ get asString() {
+ return `${this.hh}:${this.mm}:${this.ss},${this.SSS}`;
+ }
+ }
+ function toMillis(timecodes) {
+ const parts = timecodes.split(/[:,]/).map(Number);
+ const hours = parts[0];
+ const minutes = parts[1];
+ const seconds = parts[2];
+ const milliseconds = parts[3];
+ return hours * 3_600_000 // hours to millis
+ + minutes * 60_000 // minutes to millis
+ + seconds * 1000 // second to millis
+ + milliseconds;
+ }
+
+ const indexLinePattern = /^\d+$/;
+ const timecodesLinePattern = /^(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})$/;
+ const highlightedWordPattern = /^\[(.+)](?:\((\w+)\))?$/;
+ function haveSameWords(caption1, caption2) {
+ if (caption1.words.length != caption2.words.length) {
+ return false;
+ }
+ for (let i = 0; i < caption1.words.length; i++) {
+ if (caption1.words[i].rawWord != caption2.words[i].rawWord) {
+ return false;
+ }
+ }
+ return true;
+ }
+ function readCaptions(srtContent) {
+ const lines = srtContent.split('\n');
+ const captions = [];
+ let index = 0;
+ let timecodesStart = null;
+ let timecodesEnd = null;
+ for (const line of lines) {
+ let match;
+ if ((match = line.match(indexLinePattern))) {
+ index = Number(line);
+ }
+ else if ((match = line.match(timecodesLinePattern))) {
+ timecodesStart = match[1];
+ timecodesEnd = match[2];
+ }
+ else if (line.length) {
+ const start = toMillis(timecodesStart);
+ const end = toMillis(timecodesEnd);
+ const words = readWords(line);
+ captions.push({
+ index,
+ words,
+ startTimeMs: start,
+ endTimeMs: end,
+ });
+ }
+ }
+ return captions;
+ }
+ function readWords(text) {
+ const words = splitText(text);
+ const highlightedIndex = words.findIndex(word => word.match(highlightedWordPattern));
+ const res = [];
+ for (let i = 0; i < words.length; i++) {
+ const word = words[i];
+ const match = word.match(highlightedWordPattern);
+ const rawWord = match ? match[1] : word;
+ const highlightClass = match && match[2] ? match[2] : null;
+ const isHighlighted = Boolean(match);
+ const isBeforeHighlighted = Boolean(~highlightedIndex && !isHighlighted && i < highlightedIndex);
+ const isAfterHighlighted = Boolean(~highlightedIndex && !isHighlighted && i > highlightedIndex);
+ const wordObject = {
+ rawWord,
+ isHighlighted,
+ isBeforeHighlighted,
+ isAfterHighlighted,
+ };
+ if (highlightClass) {
+ wordObject.highlightClass = highlightClass;
+ }
+ res.push(wordObject);
+ }
+ return res;
+ }
+ function splitText(text) {
+ const words = [];
+ let currentWord = '';
+ let isCurrentHighlighted = false;
+ for (let i = 0; i < text.length; i++) {
+ const char = text[i];
+ const isWhitespace = /^\s$/.test(char);
+ const isPunctuation = /[,.!?]/.test(char);
+ if (!isWhitespace) {
+ if (!isPunctuation) {
+ currentWord += char;
+ switch (char) {
+ case '[':
+ case '(':
+ isCurrentHighlighted = true;
+ break;
+ case ']':
+ case ')':
+ isCurrentHighlighted = false;
+ break;
+ }
+ }
+ else {
+ if (currentWord) {
+ currentWord += char;
+ }
+ else {
+ // Attach punctuation mark to the previous word
+ words[words.length - 1] += ' ' + char;
+ }
+ }
+ }
+ else {
+ // Is a whitespace
+ if (isCurrentHighlighted) {
+ currentWord += char;
+ }
+ else if (currentWord) {
+ words.push(currentWord);
+ currentWord = '';
+ }
+ }
+ }
+ if (currentWord) {
+ words.push(currentWord);
+ }
+ return words;
+ }
+
+ class KaraokeGroup {
+ indexStart;
+ indexEnd;
+ words;
+ constructor(indexStart, indexEnd, words) {
+ this.indexStart = indexStart;
+ this.indexEnd = indexEnd;
+ this.words = words;
+ }
+ static fromCaptions(captions) {
+ const indexStart = captions[0].index;
+ const indexEnd = captions[captions.length - 1].index;
+ const words = captions
+ .map(caption => {
+ const highlightedWord = caption.words.filter(word => word.isHighlighted)[0].rawWord;
+ return {
+ rawWord: highlightedWord,
+ startTimeMs: caption.startTimeMs,
+ endTimeMs: caption.endTimeMs,
+ };
+ });
+ return new KaraokeGroup(indexStart, indexEnd, words);
+ }
+ addAtBeginning(word) {
+ this.indexStart--;
+ this.words.unshift(word);
+ }
+ removeFromBeginning() {
+ this.indexStart++;
+ return this.words.splice(0, 1)[0];
+ }
+ addAtEnd(word) {
+ this.indexEnd++;
+ this.words.push(word);
+ }
+ removeFromEnd() {
+ this.indexEnd--;
+ return this.words.pop();
+ }
+ get id() {
+ return `${this.indexStart}-${this.indexEnd}`;
+ }
+ get startTimeMs() {
+ return this.words[0].startTimeMs;
+ }
+ get endTimeMs() {
+ return this.words[this.words.length - 1].endTimeMs;
+ }
+ get isEmpty() {
+ return this.words.length === 0;
+ }
+ }
+ class CaptionsService {
+ groups = [];
+ readCaptions(captions) {
+ this.groups = [];
+ let lastCaption = null;
+ let lastGroup = [];
+ for (const caption of captions) {
+ if (lastCaption && !haveSameWords(caption, lastCaption)) {
+ const karaokeGroup = KaraokeGroup.fromCaptions(lastGroup);
+ this.groups.push(karaokeGroup);
+ lastGroup = [];
+ }
+ lastGroup.push(caption);
+ lastCaption = caption;
+ }
+ if (lastGroup.length) {
+ const karaokeGroup = KaraokeGroup.fromCaptions(lastGroup);
+ this.groups.push(karaokeGroup);
+ }
+ console.dir(this.groups, { depth: null });
+ }
+ moveFirstWordToPrecedentGroup(groupId) {
+ const karaokeGroup = this.groups[groupId];
+ const firstWord = karaokeGroup.removeFromBeginning();
+ if (groupId > 0) {
+ this.groups[groupId - 1].addAtEnd(firstWord);
+ }
+ else {
+ const index = karaokeGroup.indexStart - 1;
+ const newKaraokeGroup = new KaraokeGroup(index, index, [firstWord]);
+ this.groups.unshift(newKaraokeGroup);
+ }
+ if (karaokeGroup.isEmpty) {
+ this.groups.splice(groupId, 1);
+ }
+ }
+ moveLastWordToNextGroup(groupId) {
+ const karaokeGroup = this.groups[groupId];
+ const lastWord = karaokeGroup.removeFromEnd();
+ if (groupId < this.groups.length - 1) {
+ this.groups[groupId + 1].addAtBeginning(lastWord);
+ }
+ else {
+ const index = karaokeGroup.indexEnd + 1;
+ const newKaraokeGroup = new KaraokeGroup(index, index, [lastWord]);
+ this.groups.push(newKaraokeGroup);
+ }
+ if (karaokeGroup.isEmpty) {
+ this.groups.splice(groupId, 1);
+ }
+ }
+ get karaokeGroups() {
+ return [...this.groups];
+ }
+ get asSrt() {
+ let srtText = '';
+ for (const group of this.groups) {
+ for (let i = 0; i < group.words.length; i++) {
+ const captionIndex = group.indexStart + i;
+ const highlightedWord = group.words[i];
+ const startTimecode = new Timecode(highlightedWord.startTimeMs).asString;
+ const endTimecode = new Timecode(highlightedWord.endTimeMs).asString;
+ let captionWords = '';
+ for (let j = 0; j < group.words.length; j++) {
+ const word = group.words[j];
+ captionWords += j === i
+ ? `[${word.rawWord}] `
+ : `${word.rawWord} `;
+ }
+ srtText += `${captionIndex}\n${startTimecode} --> ${endTimecode}\n${captionWords}\n\n`;
+ }
+ }
+ return srtText;
+ }
+ }
+
+ var script$5 = /*@__PURE__*/ vue.defineComponent({
+ __name: 'indexes.component',
+ props: {
+ karaokeGroup: { type: null, required: true }
+ },
+ setup(__props) {
+ const props = __props;
+ const karaokeGroup = props.karaokeGroup;
+ const start = karaokeGroup.indexStart;
+ const end = karaokeGroup.indexEnd;
+ return (_ctx, _cache) => {
+ return (vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [
+ vue.createElementVNode("strong", null, vue.toDisplayString(vue.unref(start)), 1 /* TEXT */),
+ (vue.unref(start) !== vue.unref(end))
+ ? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 0 }, [
+ _cache[0] || (_cache[0] = vue.createElementVNode("i", { class: "fas fa-arrow-right" }, null, -1 /* HOISTED */)),
+ vue.createElementVNode("strong", null, vue.toDisplayString(vue.unref(end)), 1 /* TEXT */)
+ ], 64 /* STABLE_FRAGMENT */))
+ : vue.createCommentVNode("v-if", true)
+ ], 64 /* STABLE_FRAGMENT */));
+ };
+ }
+ });
+
+ script$5.__file = "src/editor/components/indexes.component.vue";
+
+ const _hoisted_1$3 = { class: "tags has-addons" };
+ var script$4 = /*@__PURE__*/ vue.defineComponent({
+ __name: 'timecode.component',
+ props: {
+ timecode: { type: null, required: true },
+ hoursChanged: { type: Boolean, required: false },
+ minutesChanged: { type: Boolean, required: false },
+ secondsChanged: { type: Boolean, required: false },
+ millisChanged: { type: Boolean, required: false }
+ },
+ setup(__props) {
+ return (_ctx, _cache) => {
+ return (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$3, [
+ vue.createElementVNode("span", {
+ class: vue.normalizeClass(["tag", { 'is-warning': _ctx.hoursChanged }])
+ }, vue.toDisplayString(_ctx.timecode.hh), 3 /* TEXT, CLASS */),
+ _cache[0] || (_cache[0] = vue.createElementVNode("span", null, ":", -1 /* HOISTED */)),
+ vue.createElementVNode("span", {
+ class: vue.normalizeClass(["tag", { 'is-warning': _ctx.minutesChanged }])
+ }, vue.toDisplayString(_ctx.timecode.mm), 3 /* TEXT, CLASS */),
+ _cache[1] || (_cache[1] = vue.createElementVNode("span", null, ":", -1 /* HOISTED */)),
+ vue.createElementVNode("span", {
+ class: vue.normalizeClass(["tag", { 'is-warning': _ctx.secondsChanged }])
+ }, vue.toDisplayString(_ctx.timecode.ss), 3 /* TEXT, CLASS */),
+ _cache[2] || (_cache[2] = vue.createElementVNode("span", null, ",", -1 /* HOISTED */)),
+ vue.createElementVNode("span", {
+ class: vue.normalizeClass(["tag", { 'is-warning': _ctx.millisChanged }])
+ }, vue.toDisplayString(_ctx.timecode.SSS), 3 /* TEXT, CLASS */)
+ ]));
+ };
+ }
+ });
+
+ script$4.__file = "src/editor/components/timecode.component.vue";
+
+ const _hoisted_1$2 = { class: "buttons has-addons is-inline-block" };
+ const _hoisted_2$1 = ["onClick"];
+ const _hoisted_3 = { class: "button is-small is-rounded" };
+ const _hoisted_4 = ["onClick"];
+ var script$3 = /*@__PURE__*/ vue.defineComponent({
+ __name: 'words.component',
+ props: {
+ karaokeGroup: { type: null, required: true }
+ },
+ emits: ["move-word-to-prec", "move-word-to-next"],
+ setup(__props) {
+ const props = __props;
+ const karaokeGroup = props.karaokeGroup;
+ return (_ctx, _cache) => {
+ return (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(vue.unref(karaokeGroup).words, (word, index) => {
+ return (vue.openBlock(), vue.createElementBlock("span", _hoisted_1$2, [
+ (vue.unref(karaokeGroup).indexStart > 1 && index === 0)
+ ? (vue.openBlock(), vue.createElementBlock("button", {
+ key: 0,
+ onClick: ($event) => (_ctx.$emit('move-word-to-prec', word)),
+ class: "button is-small is-rounded is-info"
+ }, [...(_cache[0] || (_cache[0] = [
+ vue.createElementVNode("span", { class: "icon is-small" }, [
+ vue.createElementVNode("i", { class: "fas fa-chevron-left" })
+ ], -1 /* HOISTED */)
+ ]))], 8 /* PROPS */, _hoisted_2$1))
+ : vue.createCommentVNode("v-if", true),
+ vue.createElementVNode("button", _hoisted_3, vue.toDisplayString(word.rawWord), 1 /* TEXT */),
+ (index === vue.unref(karaokeGroup).words.length - 1)
+ ? (vue.openBlock(), vue.createElementBlock("button", {
+ key: 1,
+ onClick: ($event) => (_ctx.$emit('move-word-to-next', word)),
+ class: "button is-small is-rounded is-info"
+ }, [...(_cache[1] || (_cache[1] = [
+ vue.createElementVNode("span", { class: "icon is-small" }, [
+ vue.createElementVNode("i", { class: "fas fa-chevron-right" })
+ ], -1 /* HOISTED */)
+ ]))], 8 /* PROPS */, _hoisted_4))
+ : vue.createCommentVNode("v-if", true)
+ ]));
+ }), 256 /* UNKEYED_FRAGMENT */));
+ };
+ }
+ });
+
+ script$3.__file = "src/editor/components/words.component.vue";
+
+ var script$2 = /*@__PURE__*/ vue.defineComponent({
+ __name: 'cue.component',
+ props: {
+ karaokeGroup: { type: null, required: true }
+ },
+ emits: ["move-word-to-prec", "move-word-to-next"],
+ setup(__props) {
+ const props = __props;
+ const timecodeStart = new Timecode(props.karaokeGroup.startTimeMs);
+ const timecodeEnd = new Timecode(props.karaokeGroup.endTimeMs);
+ const hoursChanged = timecodeStart.hours !== timecodeEnd.hours;
+ const minutesChanged = timecodeStart.minutes !== timecodeEnd.minutes;
+ const secondsChanged = timecodeStart.seconds !== timecodeEnd.seconds;
+ const millisChanged = timecodeStart.millis !== timecodeEnd.millis;
+ return (_ctx, _cache) => {
+ return (vue.openBlock(), vue.createElementBlock("tr", null, [
+ vue.createElementVNode("td", null, [
+ vue.createVNode(script$5, vue.normalizeProps(vue.guardReactiveProps({ karaokeGroup: _ctx.karaokeGroup })), null, 16 /* FULL_PROPS */)
+ ]),
+ vue.createElementVNode("td", null, [
+ vue.createVNode(script$4, {
+ timecode: vue.unref(timecodeStart),
+ "hours-changed": hoursChanged,
+ "minutes-changed": minutesChanged,
+ "seconds-changed": secondsChanged,
+ "millis-changed": millisChanged
+ }, null, 8 /* PROPS */, ["timecode"])
+ ]),
+ vue.createElementVNode("td", null, [
+ vue.createVNode(script$4, {
+ timecode: vue.unref(timecodeEnd),
+ "hours-changed": hoursChanged,
+ "minutes-changed": minutesChanged,
+ "seconds-changed": secondsChanged,
+ "millis-changed": millisChanged
+ }, null, 8 /* PROPS */, ["timecode"])
+ ]),
+ vue.createElementVNode("td", null, [
+ vue.createVNode(script$3, {
+ "karaoke-group": props.karaokeGroup,
+ onMoveWordToPrec: _cache[0] || (_cache[0] = ($event) => (_ctx.$emit('move-word-to-prec', $event))),
+ onMoveWordToNext: _cache[1] || (_cache[1] = ($event) => (_ctx.$emit('move-word-to-next', $event)))
+ }, null, 8 /* PROPS */, ["karaoke-group"])
+ ])
+ ]));
+ };
+ }
+ });
+
+ script$2.__file = "src/editor/components/cue.component.vue";
+
+ const _hoisted_1$1 = { class: "table is-fullwidth is-hoverable" };
+ var script$1 = /*@__PURE__*/ vue.defineComponent({
+ __name: 'subtitles-table.component',
+ props: {
+ karaokeGroups: { type: Array, required: true }
+ },
+ setup(__props) {
+ const props = __props;
+ const captionService = vue.inject('captionService');
+ const groups = vue.ref(props.karaokeGroups);
+ vue.watch(() => props.karaokeGroups, (newValue) => {
+ groups.value = newValue;
+ });
+ function moveFirstWordToPrecedentGroup(groupId) {
+ captionService.moveFirstWordToPrecedentGroup(groupId);
+ groups.value = captionService.karaokeGroups;
+ }
+ function moveLastWordToNextGroup(groupId) {
+ captionService.moveLastWordToNextGroup(groupId);
+ groups.value = captionService.karaokeGroups;
+ }
+ return (_ctx, _cache) => {
+ return (vue.openBlock(), vue.createElementBlock("table", _hoisted_1$1, [
+ _cache[0] || (_cache[0] = vue.createElementVNode("thead", null, [
+ vue.createElementVNode("tr", { class: "is-link" }, [
+ vue.createElementVNode("th", {
+ class: "has-text-white",
+ style: { "width": "5%" }
+ }, "Indexes"),
+ vue.createElementVNode("th", {
+ class: "has-text-white",
+ style: { "width": "15%" }
+ }, "Start"),
+ vue.createElementVNode("th", {
+ class: "has-text-white",
+ style: { "width": "15%" }
+ }, "End"),
+ vue.createElementVNode("th", { class: "has-text-white" }, " Caption")
+ ])
+ ], -1 /* HOISTED */)),
+ vue.createElementVNode("tbody", null, [
+ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(groups.value, (karaokeGroup, index) => {
+ return (vue.openBlock(), vue.createBlock(script$2, {
+ key: karaokeGroup.id,
+ "karaoke-group": karaokeGroup,
+ onMoveWordToPrec: ($event) => (moveFirstWordToPrecedentGroup(index)),
+ onMoveWordToNext: ($event) => (moveLastWordToNextGroup(index))
+ }, null, 8 /* PROPS */, ["karaoke-group", "onMoveWordToPrec", "onMoveWordToNext"]));
+ }), 128 /* KEYED_FRAGMENT */))
+ ])
+ ]));
+ };
+ }
+ });
+
+ script$1.__file = "src/editor/components/subtitles-table.component.vue";
+
+ const _hoisted_1 = { class: "box" };
+ const _hoisted_2 = ["disabled"];
+ var script = /*@__PURE__*/ vue.defineComponent({
+ __name: 'application.component',
+ setup(__props) {
+ const captionService = new CaptionsService();
+ vue.provide('captionService', captionService);
+ const karaokeGroups = vue.ref([]);
+ const downloadEnabled = vue.ref(false);
+ async function onFileSelected(file) {
+ const content = await loadFile(file);
+ const captions = readCaptions(content);
+ captionService.readCaptions(captions);
+ karaokeGroups.value = captionService.karaokeGroups;
+ downloadEnabled.value = true;
+ }
+ function downloadSrt() {
+ const blob = new Blob([captionService.asSrt], { type: 'text/plain;charset=utf-8' });
+ FileSaver_minExports.saveAs(blob, 'edited.srt');
+ }
+ return (_ctx, _cache) => {
+ return (vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [
+ vue.createElementVNode("div", _hoisted_1, [
+ vue.createElementVNode("form", null, [
+ vue.createVNode(script$6, { onFileSelected: onFileSelected }),
+ vue.createElementVNode("button", {
+ type: "button",
+ class: "button is-primary",
+ disabled: !downloadEnabled.value,
+ onClick: downloadSrt
+ }, _cache[0] || (_cache[0] = [
+ vue.createElementVNode("span", { class: "icon" }, [
+ vue.createElementVNode("i", { class: "fa-solid fa-download" })
+ ], -1 /* HOISTED */),
+ vue.createElementVNode("span", null, "Download", -1 /* HOISTED */)
+ ]), 8 /* PROPS */, _hoisted_2)
+ ])
+ ]),
+ vue.createVNode(script$1, { "karaoke-groups": karaokeGroups.value }, null, 8 /* PROPS */, ["karaoke-groups"])
+ ], 64 /* STABLE_FRAGMENT */));
+ };
+ }
+ });
+
+ script.__file = "src/editor/components/application.component.vue";
+
+ vue.createApp({})
+ .component('application', script)
+ .mount('#app');
+
+})(Vue);
+//# sourceMappingURL=index.js.map
diff --git a/dist/editor/index.js.map b/dist/editor/index.js.map
new file mode 100644
index 0000000..9b83236
--- /dev/null
+++ b/dist/editor/index.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"index.js","sources":["../../node_modules/file-saver/dist/FileSaver.min.js","../../src/editor/components/srt-file-picker.component.vue","../../src/editor/file-loader.ts","../../src/common/timecodes.ts","../../src/common/captions.ts","../../src/editor/captions.service.ts","../../src/editor/components/indexes.component.vue","../../src/editor/components/words.component.vue","../../src/editor/components/cue.component.vue","../../src/editor/components/subtitles-table.component.vue","../../src/editor/components/application.component.vue","../../src/editor/index.ts"],"sourcesContent":["(function(a,b){if(\"function\"==typeof define&&define.amd)define([],b);else if(\"undefined\"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){\"use strict\";function b(a,b){return\"undefined\"==typeof b?b={autoBom:!1}:\"object\"!=typeof b&&(console.warn(\"Deprecated: Expected third argument to be a object\"),b={autoBom:!b}),b.autoBom&&/^\\s*(?:text\\/\\S*|application\\/xml|\\S*\\/\\S*\\+xml)\\s*;.*charset\\s*=\\s*utf-8/i.test(a.type)?new Blob([\"\\uFEFF\",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open(\"GET\",a),d.responseType=\"blob\",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error(\"could not download file\")},d.send()}function d(a){var b=new XMLHttpRequest;b.open(\"HEAD\",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent(\"click\"))}catch(c){var b=document.createEvent(\"MouseEvents\");b.initMouseEvent(\"click\",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f=\"object\"==typeof window&&window.window===window?window:\"object\"==typeof self&&self.self===self?self:\"object\"==typeof global&&global.global===global?global:void 0,a=f.navigator&&/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||(\"object\"!=typeof window||window!==f?function(){}:\"download\"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement(\"a\");g=g||b.name||\"download\",j.download=g,j.rel=\"noopener\",\"string\"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target=\"_blank\")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:\"msSaveOrOpenBlob\"in navigator?function(f,g,h){if(g=g||f.name||\"download\",\"string\"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement(\"a\");i.href=f,i.target=\"_blank\",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open(\"\",\"_blank\"),g&&(g.document.title=g.document.body.innerText=\"downloading...\"),\"string\"==typeof b)return c(b,d,e);var h=\"application/octet-stream\"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\\/[\\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&\"undefined\"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,\"data:attachment/file;\"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,\"undefined\"!=typeof module&&(module.exports=g)});\n\n//# sourceMappingURL=FileSaver.min.js.map","\n\n\n \n \n \n\n \n \n \n \n Pick subs file \n \n {{ selectedFile?.name }} \n \n
\n ","export async function loadFile(file: File): Promise {\n const reader = new FileReader();\n\n return new Promise((resolve, reject) => {\n reader.onload = (loadEvent) => {\n resolve(loadEvent.target?.result as string);\n };\n\n reader.onerror = (err) => {\n reject(err);\n };\n\n reader.readAsText(file);\n });\n}","export class Timecode {\n public readonly hours: number;\n public readonly minutes: number;\n public readonly seconds: number;\n public readonly millis: number;\n\n constructor(millis: number) {\n this.millis = millis % 1000;\n\n this.hours = Math.floor(millis / 3_600_000);\n const remainingMillisAfterHours = millis % 3_600_000;\n this.minutes = Math.floor(remainingMillisAfterHours / 60_000);\n const remainingMillisAfterMinutes = remainingMillisAfterHours % 60_000;\n this.seconds = Math.floor(remainingMillisAfterMinutes / 1000);\n }\n\n public get hh(): string {\n return String(this.hours).padStart(2, '0');\n }\n\n public get mm(): string {\n return String(this.minutes).padStart(2, '0');\n }\n\n public get ss(): string {\n return String(this.seconds).padStart(2, '0');\n }\n public get SSS(): string {\n return String(this.millis).padStart(3, '0');\n }\n\n public get asString(): string {\n return `${this.hh}:${this.mm}:${this.ss},${this.SSS}`;\n }\n}\n\nexport function toMillis(timecodes: string): number {\n const parts = timecodes.split(/[:,]/).map(Number);\n\n const hours = parts[0];\n const minutes = parts[1];\n const seconds = parts[2];\n const milliseconds = parts[3];\n\n return hours * 3_600_000 // hours to millis\n + minutes * 60_000 // minutes to millis\n + seconds * 1000 // second to millis\n + milliseconds;\n}","import {toMillis} from './timecodes';\n\nconst indexLinePattern = /^\\d+$/;\nconst timecodesLinePattern = /^(\\d{2}:\\d{2}:\\d{2},\\d{3}) --> (\\d{2}:\\d{2}:\\d{2},\\d{3})$/;\nconst highlightedWordPattern = /^\\[(.+)](?:\\((\\w+)\\))?$/;\n\nexport interface Word {\n rawWord: string;\n isHighlighted: boolean;\n isBeforeHighlighted: boolean;\n isAfterHighlighted: boolean;\n highlightClass?: string;\n}\n\nexport interface Caption {\n index: number;\n startTimeMs: number;\n endTimeMs: number;\n words: Word[];\n}\n\nexport function haveSameWords(caption1: Caption, caption2: Caption): boolean {\n if (caption1.words.length != caption2.words.length) {\n return false;\n }\n\n for (let i =0; i < caption1.words.length; i++) {\n if (caption1.words[i].rawWord != caption2.words[i].rawWord) {\n return false;\n }\n }\n\n return true;\n}\n\nexport function readCaptions(srtContent: string): Caption[] {\n const lines = srtContent.split('\\n');\n const captions: Caption[] = [];\n\n let index: number = 0;\n let timecodesStart: string | null = null;\n let timecodesEnd: string | null = null;\n\n for (const line of lines) {\n let match;\n if ((match = line.match(indexLinePattern))) {\n index = Number(line);\n } else if ((match = line.match(timecodesLinePattern))) {\n timecodesStart = match[1];\n timecodesEnd = match[2];\n } else if (line.length) {\n const start = toMillis(timecodesStart!);\n const end = toMillis(timecodesEnd!);\n\n const words = readWords(line);\n\n captions.push({\n index,\n words,\n startTimeMs: start,\n endTimeMs: end,\n });\n }\n }\n\n return captions;\n}\n\nexport function readWords(text: string): Word[] {\n const words = splitText(text);\n const highlightedIndex = words.findIndex(word => word.match(highlightedWordPattern));\n\n const res: Word[] = [];\n\n for (let i = 0; i < words.length; i++) {\n const word = words[i];\n const match = word.match(highlightedWordPattern);\n const rawWord = match ? match[1] : word;\n const highlightClass = match && match[2] ? match[2] : null;\n\n const isHighlighted = Boolean(match);\n const isBeforeHighlighted = Boolean(~highlightedIndex && !isHighlighted && i < highlightedIndex);\n const isAfterHighlighted = Boolean(~highlightedIndex && !isHighlighted && i > highlightedIndex);\n\n const wordObject: Word = {\n rawWord,\n isHighlighted,\n isBeforeHighlighted,\n isAfterHighlighted,\n };\n\n if (highlightClass) {\n wordObject.highlightClass = highlightClass;\n }\n\n res.push(wordObject);\n }\n\n return res;\n}\n\nexport function splitText(text: string): string[] {\n const words: string[] = [];\n\n let currentWord = '';\n let isCurrentHighlighted = false;\n\n for (let i = 0; i < text.length; i++) {\n const char = text[i];\n const isWhitespace = /^\\s$/.test(char);\n const isPunctuation = /[,.!?]/.test(char);\n\n if (!isWhitespace) {\n if (!isPunctuation) {\n currentWord += char;\n switch (char) {\n case '[':\n case '(':\n isCurrentHighlighted = true;\n break;\n case ']':\n case ')':\n isCurrentHighlighted = false;\n break;\n }\n } else {\n if (currentWord) {\n currentWord += char;\n } else {\n // Attach punctuation mark to the previous word\n words[words.length - 1] += ' ' + char;\n }\n }\n } else {\n // Is a whitespace\n if (isCurrentHighlighted) {\n currentWord += char;\n } else if (currentWord) {\n words.push(currentWord);\n currentWord = '';\n }\n }\n }\n\n if (currentWord) {\n words.push(currentWord);\n }\n\n return words;\n}","import {Caption, haveSameWords} from '../common/captions';\nimport {Timecode} from '../common/timecodes';\n\nexport interface KaraokeWord {\n rawWord: string;\n startTimeMs: number;\n endTimeMs: number;\n}\n\nexport class KaraokeGroup {\n constructor(public indexStart: number,\n public indexEnd: number,\n public readonly words: KaraokeWord[]) {\n }\n\n static fromCaptions(captions: Caption[]): KaraokeGroup {\n const indexStart = captions[0].index;\n const indexEnd = captions[captions.length - 1].index;\n const words: KaraokeWord[] = captions\n .map(caption => {\n const highlightedWord = caption.words.filter(word => word.isHighlighted)[0].rawWord;\n\n return {\n rawWord: highlightedWord,\n startTimeMs: caption.startTimeMs,\n endTimeMs: caption.endTimeMs,\n };\n })\n\n return new KaraokeGroup(indexStart, indexEnd, words);\n }\n\n public addAtBeginning(word: KaraokeWord) {\n this.indexStart--;\n this.words.unshift(word);\n }\n\n public removeFromBeginning(): KaraokeWord {\n this.indexStart++;\n return this.words.splice(0, 1)[0];\n }\n\n public addAtEnd(word: KaraokeWord) {\n this.indexEnd++;\n this.words.push(word);\n }\n\n public removeFromEnd(): KaraokeWord {\n this.indexEnd--;\n return this.words.pop()!;\n }\n\n public get id() {\n return `${this.indexStart}-${this.indexEnd}`;\n }\n\n public get startTimeMs(): number {\n return this.words[0].startTimeMs;\n }\n\n public get endTimeMs(): number {\n return this.words[this.words.length - 1].endTimeMs;\n }\n\n public get isEmpty(): boolean {\n return this.words.length === 0;\n }\n}\n\nexport class CaptionsService {\n private groups: KaraokeGroup[] = [];\n\n public readCaptions(captions: Caption[]) {\n this.groups = [];\n\n let lastCaption: Caption | null = null;\n let lastGroup = [];\n\n for (const caption of captions) {\n if (lastCaption && !haveSameWords(caption, lastCaption!)) {\n const karaokeGroup = KaraokeGroup.fromCaptions(lastGroup);\n this.groups.push(karaokeGroup);\n lastGroup = [];\n }\n\n lastGroup.push(caption);\n lastCaption = caption;\n }\n\n if (lastGroup.length) {\n const karaokeGroup = KaraokeGroup.fromCaptions(lastGroup);\n this.groups.push(karaokeGroup);\n }\n\n console.dir(this.groups, {depth: null});\n }\n\n public moveFirstWordToPrecedentGroup(groupId: number) {\n const karaokeGroup = this.groups[groupId];\n const firstWord = karaokeGroup.removeFromBeginning();\n\n if (groupId > 0) {\n this.groups[groupId - 1].addAtEnd(firstWord);\n } else {\n const index = karaokeGroup.indexStart - 1;\n const newKaraokeGroup = new KaraokeGroup(index, index, [ firstWord ]);\n this.groups.unshift(newKaraokeGroup);\n }\n\n if (karaokeGroup.isEmpty) {\n this.groups.splice(groupId, 1);\n }\n }\n\n public moveLastWordToNextGroup(groupId: number) {\n const karaokeGroup = this.groups[groupId];\n const lastWord = karaokeGroup.removeFromEnd();\n\n if (groupId < this.groups.length - 1) {\n this.groups[groupId + 1].addAtBeginning(lastWord);\n } else {\n const index = karaokeGroup.indexEnd + 1;\n const newKaraokeGroup = new KaraokeGroup(index, index, [ lastWord ]);\n this.groups.push(newKaraokeGroup);\n }\n\n if (karaokeGroup.isEmpty) {\n this.groups.splice(groupId, 1);\n }\n }\n\n public get karaokeGroups(): KaraokeGroup[] {\n return [...this.groups];\n }\n\n public get asSrt(): string {\n let srtText = '';\n\n for (const group of this.groups) {\n for (let i = 0; i < group.words.length; i++) {\n const captionIndex = group.indexStart + i;\n const highlightedWord = group.words[i];\n const startTimecode = new Timecode(highlightedWord.startTimeMs).asString;\n const endTimecode = new Timecode(highlightedWord.endTimeMs).asString;\n\n let captionWords = '';\n\n for (let j = 0; j < group.words.length; j++) {\n const word = group.words[j];\n captionWords += j === i\n ? `[${word.rawWord}] `\n : `${word.rawWord} `;\n }\n\n srtText += `${captionIndex}\\n${startTimecode} --> ${endTimecode}\\n${captionWords}\\n\\n`;\n }\n }\n\n return srtText;\n }\n}","\n\n\n {{ start }} \n\n \n \n {{ end }} \n \n ","\n\n\n \n 1 && index === 0\"\n @click=\"$emit('move-word-to-prec', word as KaraokeWord)\"\n class=\"button is-small is-rounded is-info\">\n \n \n \n \n\n {{ word.rawWord }} \n\n \n \n \n \n \n \n ","\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ","\n\n\n \n \n \n Indexes \n Start \n End \n Caption \n \n \n\n \n \n \n
\n ","\n\n\n \n \n
\n\n \n ","import {createApp} from 'vue';\nimport Application from './components/application.component.vue';\n\ncreateApp({})\n .component('application', Application)\n .mount('#app');"],"names":["this","global","ref","inject","watch","provide","saveAs","createApp","Application"],"mappings":"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","x_google_ignoreList":[0]}
\ No newline at end of file
diff --git a/dist/script/index.js b/dist/script/index.js
index 3b595a4..b48752c 100644
--- a/dist/script/index.js
+++ b/dist/script/index.js
@@ -1,15 +1,15 @@
'use strict';
+var fs = require('fs');
var require$$0 = require('commander');
var path = require('path');
var cliProgress = require('cli-progress');
-var fs = require('fs');
var tmp = require('tmp');
var pngjs = require('pngjs');
var ffmpeg = require('fluent-ffmpeg');
-var httpServer = require('http-server');
var puppeteer = require('puppeteer');
var stream = require('stream');
+var httpServer = require('http-server');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
@@ -123,6 +123,7 @@ var dependencies = {
bulma: "^1.0.2",
"cli-progress": "^3.12.0",
commander: "^12.1.0",
+ "file-saver": "^2.0.5",
"fluent-ffmpeg": "^2.1.3",
"get-port": "^7.1.0",
"http-server": "^14.1.1",
@@ -142,6 +143,7 @@ var devDependencies = {
"@rollup/plugin-node-resolve": "^15.3.0",
"@types/chai": "^5.0.1",
"@types/cli-progress": "^3.11.6",
+ "@types/file-saver": "^2.0.7",
"@types/fluent-ffmpeg": "^2.1.27",
"@types/http-server": "^0.12.4",
"@types/mocha": "^10.0.10",
@@ -152,6 +154,7 @@ var devDependencies = {
chai: "^5.1.2",
mocha: "^10.8.2",
rollup: "^4.27.3",
+ "rollup-plugin-copy": "^3.5.0",
"rollup-plugin-typescript2": "^0.36.0",
"rollup-plugin-vue": "^6.0.0",
tsx: "^4.19.2",
@@ -281,116 +284,6 @@ function createProgressBar() {
}, cliProgress__namespace.Presets.shades_classic);
}
-function toMillis(timecodes) {
- const parts = timecodes.split(/[:,]/).map(Number);
- const hours = parts[0];
- const minutes = parts[1];
- const seconds = parts[2];
- const milliseconds = parts[3];
- return hours * 3_600_000 // hours to millis
- + minutes * 60_000 // minutes to millis
- + seconds * 1000 // second to millis
- + milliseconds;
-}
-
-const indexLinePattern = /^\d+$/;
-const timecodesLinePattern = /^(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})$/;
-const highlightedWordPattern = /^\[(.+)](?:\((\w+)\))?$/;
-function parseCaptions(srtCaptionsFile) {
- const captionsSrc = fs.readFileSync(srtCaptionsFile, 'utf-8');
- return readCaptions(captionsSrc);
-}
-function readCaptions(srtContent) {
- const lines = srtContent.split('\n');
- const captions = [];
- let index = 0;
- let timecodesStart = null;
- let timecodesEnd = null;
- for (const line of lines) {
- let match;
- if ((match = line.match(indexLinePattern))) {
- index = Number(line);
- }
- else if ((match = line.match(timecodesLinePattern))) {
- timecodesStart = match[1];
- timecodesEnd = match[2];
- }
- else if (line.length) {
- const start = toMillis(timecodesStart);
- const end = toMillis(timecodesEnd);
- const words = readWords(line);
- captions.push({
- index,
- words,
- startTimeMs: start,
- endTimeMs: end,
- });
- }
- }
- return captions;
-}
-function readWords(text) {
- const words = splitText(text);
- const highlightedIndex = words.findIndex(word => word.match(highlightedWordPattern));
- const res = [];
- for (let i = 0; i < words.length; i++) {
- const word = words[i];
- const match = word.match(highlightedWordPattern);
- const rawWord = match ? match[1] : word;
- const highlightClass = match && match[2] ? match[2] : null;
- const isHighlighted = Boolean(match);
- const isBeforeHighlighted = Boolean(~highlightedIndex && !isHighlighted && i < highlightedIndex);
- const isAfterHighlighted = Boolean(~highlightedIndex && !isHighlighted && i > highlightedIndex);
- const wordObject = {
- rawWord,
- isHighlighted,
- isBeforeHighlighted,
- isAfterHighlighted,
- };
- if (highlightClass) {
- wordObject.highlightClass = highlightClass;
- }
- res.push(wordObject);
- }
- return res;
-}
-function splitText(text) {
- const words = [];
- let currentWord = '';
- let isCurrentHighlighted = false;
- for (let i = 0; i < text.length; i++) {
- const char = text[i];
- const isWhitespace = /^\s$/.test(char);
- if (!isWhitespace) {
- currentWord += char;
- switch (char) {
- case '[':
- case '(':
- isCurrentHighlighted = true;
- break;
- case ']':
- case ')':
- isCurrentHighlighted = false;
- break;
- }
- }
- else {
- // char is a whitespace
- if (isCurrentHighlighted) {
- currentWord += char;
- }
- else if (currentWord) {
- words.push(currentWord);
- currentWord = '';
- }
- }
- }
- if (currentWord) {
- words.push(currentWord);
- }
- return words;
-}
-
class WorkDir {
captions;
args;
@@ -543,45 +436,6 @@ class StepRenderer extends AbstractRenderer {
}
}
-class PreviewServer {
- wordDir;
- constructor(wordDir) {
- this.wordDir = wordDir;
- }
- async start() {
- return new Promise(async (resolve, reject) => {
- try {
- const server = httpServer.createServer({ root: this.wordDir.rootDir });
- const port = await PreviewServer.getFreePort();
- server.listen(port, async () => {
- try {
- const childProcess = await PreviewServer.openUrl(`http://127.0.0.1:${port}`);
- childProcess.on('close', () => {
- server.close(() => {
- resolve();
- });
- });
- }
- catch (error) {
- reject(error);
- }
- });
- }
- catch (error) {
- reject(error);
- }
- });
- }
- static async getFreePort() {
- const { default: getPort } = await import('get-port');
- return getPort();
- }
- static async openUrl(url) {
- const { default: open } = await import('open');
- return open(url, { wait: true });
- }
-}
-
class AbstractRecorder {
args;
browser = null;
@@ -765,6 +619,167 @@ class StepRecorder extends AbstractRecorder {
}
}
+function toMillis(timecodes) {
+ const parts = timecodes.split(/[:,]/).map(Number);
+ const hours = parts[0];
+ const minutes = parts[1];
+ const seconds = parts[2];
+ const milliseconds = parts[3];
+ return hours * 3_600_000 // hours to millis
+ + minutes * 60_000 // minutes to millis
+ + seconds * 1000 // second to millis
+ + milliseconds;
+}
+
+const indexLinePattern = /^\d+$/;
+const timecodesLinePattern = /^(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})$/;
+const highlightedWordPattern = /^\[(.+)](?:\((\w+)\))?$/;
+function readCaptions(srtContent) {
+ const lines = srtContent.split('\n');
+ const captions = [];
+ let index = 0;
+ let timecodesStart = null;
+ let timecodesEnd = null;
+ for (const line of lines) {
+ let match;
+ if ((match = line.match(indexLinePattern))) {
+ index = Number(line);
+ }
+ else if ((match = line.match(timecodesLinePattern))) {
+ timecodesStart = match[1];
+ timecodesEnd = match[2];
+ }
+ else if (line.length) {
+ const start = toMillis(timecodesStart);
+ const end = toMillis(timecodesEnd);
+ const words = readWords(line);
+ captions.push({
+ index,
+ words,
+ startTimeMs: start,
+ endTimeMs: end,
+ });
+ }
+ }
+ return captions;
+}
+function readWords(text) {
+ const words = splitText(text);
+ const highlightedIndex = words.findIndex(word => word.match(highlightedWordPattern));
+ const res = [];
+ for (let i = 0; i < words.length; i++) {
+ const word = words[i];
+ const match = word.match(highlightedWordPattern);
+ const rawWord = match ? match[1] : word;
+ const highlightClass = match && match[2] ? match[2] : null;
+ const isHighlighted = Boolean(match);
+ const isBeforeHighlighted = Boolean(~highlightedIndex && !isHighlighted && i < highlightedIndex);
+ const isAfterHighlighted = Boolean(~highlightedIndex && !isHighlighted && i > highlightedIndex);
+ const wordObject = {
+ rawWord,
+ isHighlighted,
+ isBeforeHighlighted,
+ isAfterHighlighted,
+ };
+ if (highlightClass) {
+ wordObject.highlightClass = highlightClass;
+ }
+ res.push(wordObject);
+ }
+ return res;
+}
+function splitText(text) {
+ const words = [];
+ let currentWord = '';
+ let isCurrentHighlighted = false;
+ for (let i = 0; i < text.length; i++) {
+ const char = text[i];
+ const isWhitespace = /^\s$/.test(char);
+ const isPunctuation = /[,.!?]/.test(char);
+ if (!isWhitespace) {
+ if (!isPunctuation) {
+ currentWord += char;
+ switch (char) {
+ case '[':
+ case '(':
+ isCurrentHighlighted = true;
+ break;
+ case ']':
+ case ')':
+ isCurrentHighlighted = false;
+ break;
+ }
+ }
+ else {
+ if (currentWord) {
+ currentWord += char;
+ }
+ else {
+ // Attach punctuation mark to the previous word
+ words[words.length - 1] += ' ' + char;
+ }
+ }
+ }
+ else {
+ // Is a whitespace
+ if (isCurrentHighlighted) {
+ currentWord += char;
+ }
+ else if (currentWord) {
+ words.push(currentWord);
+ currentWord = '';
+ }
+ }
+ }
+ if (currentWord) {
+ words.push(currentWord);
+ }
+ return words;
+}
+
+class WebServer {
+ rootDir;
+ constructor(rootDir) {
+ this.rootDir = rootDir;
+ }
+ async start(relativePath = '') {
+ return new Promise(async (resolve, reject) => {
+ try {
+ const server = httpServer.createServer({ root: this.rootDir });
+ const port = await WebServer.getFreePort();
+ server.listen(port, async () => {
+ try {
+ const childProcess = await WebServer.openUrl(`http://127.0.0.1:${port}${relativePath}`);
+ childProcess.on('close', () => {
+ server.close(() => {
+ resolve();
+ });
+ });
+ }
+ catch (error) {
+ reject(error);
+ }
+ });
+ }
+ catch (error) {
+ reject(error);
+ }
+ });
+ }
+ static async getFreePort() {
+ const { default: getPort } = await import('get-port');
+ return getPort();
+ }
+ static async openUrl(url) {
+ const { default: open } = await import('open');
+ return open(url, { wait: true });
+ }
+}
+
+function parseCaptions(srtCaptionsFile) {
+ const captionsSrc = fs.readFileSync(srtCaptionsFile, 'utf-8');
+ return readCaptions(captionsSrc);
+}
function createRecorder(args, captions, workDir) {
if (args.css3Animations) {
const realTimeRenderer = new RealTimeRenderer(args);
@@ -789,7 +804,7 @@ const workDir = new WorkDir(captions, cliArgs);
}
else {
console.log('Launching preview server...');
- const previewServer = new PreviewServer(workDir);
+ const previewServer = new WebServer(workDir.rootDir);
await previewServer.start();
}
console.log('Done!');
diff --git a/dist/script/index.js.map b/dist/script/index.js.map
index 008eb96..362ad1f 100644
--- a/dist/script/index.js.map
+++ b/dist/script/index.js.map
@@ -1 +1 @@
-{"version":3,"file":"index.js","sources":["../../node_modules/@commander-js/extra-typings/index.js","../../node_modules/@commander-js/extra-typings/esm.mjs","../../src/script/assets.ts","../../src/script/cli.ts","../../src/common/timecodes.ts","../../src/script/srt-captions-reader.ts","../../src/script/work-dir.ts","../../src/script/stats-printer.ts","../../src/script/abstract-renderer.ts","../../src/script/step-renderer.ts","../../src/script/preview-server.ts","../../src/script/abstract-recorder.ts","../../src/script/real-time-recorder.ts","../../src/script/real-time-renderer.ts","../../src/script/step-recorder.ts","../../src/script/index.ts"],"sourcesContent":["const commander = require('commander');\n\nexports = module.exports = {};\n\n// Return a different global program than commander,\n// and don't also return it as default export.\nexports.program = new commander.Command();\n\n/**\n * Expose classes. The FooT versions are just types, so return Commander original implementations!\n */\n\nexports.Argument = commander.Argument;\nexports.Command = commander.Command;\nexports.CommanderError = commander.CommanderError;\nexports.Help = commander.Help;\nexports.InvalidArgumentError = commander.InvalidArgumentError;\nexports.InvalidOptionArgumentError = commander.InvalidArgumentError; // Deprecated\nexports.Option = commander.Option;\n\n// In Commander, the create routines end up being aliases for the matching\n// methods on the global program due to the (deprecated) legacy default export.\n// Here we roll our own, the way Commander might in future.\nexports.createCommand = (name) => new commander.Command(name);\nexports.createOption = (flags, description) =>\n new commander.Option(flags, description);\nexports.createArgument = (name, description) =>\n new commander.Argument(name, description);\n","import extraTypingsCommander from './index.js';\n\n// wrapper to provide named exports for ESM.\nexport const {\n program,\n createCommand,\n createArgument,\n createOption,\n CommanderError,\n InvalidArgumentError,\n InvalidOptionArgumentError, // deprecated old name\n Command,\n Argument,\n Option,\n Help,\n} = extraTypingsCommander;\n","import * as path from 'path';\n\nexport const assetsFolder = path.join(__dirname, '..', '..', 'assets');\nexport const defaultStylesCss = path.join(assetsFolder, 'captions.css');\nexport const indexHtml = path.join(assetsFolder, 'index.html');\n\nexport const indexJs = path.join(__dirname, '..', 'web', 'index.js');\n\nexport const nodeModules = path.join(__dirname, '..', '..', 'node_modules');\n","import {Command} from '@commander-js/extra-typings';\nimport packageJson from '../../package.json';\nimport {defaultStylesCss} from './assets';\nimport * as cliProgress from 'cli-progress';\nimport * as path from 'path';\n\nexport interface Args {\n srtInputFile: string;\n movOutputFile: string;\n videoWidth: number;\n videoHeight: number;\n fps: number;\n styleFile: string;\n css3Animations: boolean;\n isPreview: boolean;\n}\n\nfunction parseIntAndAssert(...assertions: ((v: number) => void)[]): (v: string) => number {\n return (value: string) => {\n const int = parseInt(value, 10);\n assertions.forEach(assertion => assertion(int));\n return int;\n }\n}\n\nfunction assertPositive(option: string): (v: number) => void {\n return (value: number) => {\n if (value < 0) {\n throw new Error(`${option} should be positive!`);\n }\n };\n}\n\nfunction assertMinMax(option: string, min: number, max: number): (v: number) => void {\n return (value: number) => {\n if (value < min || value > max) {\n throw new Error(`${option} should be between ${min} and ${max}!`);\n }\n };\n}\n\nfunction assertFileExtension(ext: string): (v: string) => void {\n return (value: string) => {\n if (!value.endsWith(ext)) {\n throw new Error(`File should have extension ${ext}!`);\n }\n return value;\n };\n}\n\nconst program = new Command();\n\nprogram\n .name('pupcaps')\n .description('Tool to add stylish captions to your video.')\n .version(packageJson.version)\n .argument('', 'Path to the input SubRip Subtitle (.srt) file.', assertFileExtension('.srt'))\n .option('-o, --output ',\n 'Full or relative path where the created Films Apple QuickTime (MOV) file should be written. ' +\n 'By default, it will be saved in the same directory as the input subtitle file.',\n assertFileExtension('.mov'))\n .option('-w, --width ',\n 'Width of the video in pixels.',\n parseIntAndAssert(assertPositive('Width')),\n 1080)\n .option('-h, --height ',\n 'Height of the video in pixels.',\n parseIntAndAssert(assertPositive('Height')),\n 1920)\n .option('-r, --fps ',\n 'Specifies the frame rate (FPS) of the output video. Valid values are between 1 and 60.',\n parseIntAndAssert(assertMinMax('FPS', 1, 60)),\n 30)\n .option('-s, --style ',\n 'Full or relative path to the styles .css file. ' +\n 'If not provided, default styles for captions will be used.',\n assertFileExtension('.css'))\n .option('-a, --animate',\n 'Records captions with CSS3 animations. ' +\n 'Note: The recording will run for the entire duration of the video. ' +\n 'Use this option only if your captions involve CSS3 animations.')\n .option('--preview',\n 'Prevents the script from generating a video file. ' +\n 'Instead, captions are displayed in the browser for debugging and preview purposes.')\n .action((inputFile, options: any) => {\n if (!options.output) {\n const fileBasename = (inputFile as any as string).slice(0, -4);\n options.output = `${fileBasename}.mov`;\n }\n\n if (!options.style) {\n options.style = defaultStylesCss;\n } else {\n options.style = path.resolve(options.style);\n }\n });\n\nexport function parseArgs(): Args {\n program.parse();\n const opts = program.opts() as any;\n\n return {\n srtInputFile: program.args[0],\n movOutputFile: opts.output,\n videoWidth: opts.width,\n videoHeight: opts.height,\n fps: opts.fps,\n styleFile: opts.style,\n css3Animations: opts.animate,\n isPreview: opts.preview,\n };\n}\n\nexport function printArgs(args: Args) {\n const styles = args.styleFile === defaultStylesCss\n ? '(Default)'\n : args.styleFile;\n\n const srt = `\n Output: ${args.movOutputFile}\n Width: ${args.videoWidth} px\n Height: ${args.videoHeight} px\n FPS: ${args.fps}\n Styles: ${styles}\n Animations: ${ args.css3Animations ? 'yes' : 'no' }\n `;\n\n console.log(srt);\n}\n\nexport function createProgressBar(): cliProgress.SingleBar {\n return new cliProgress.SingleBar({\n format: 'Progress |{bar}| {percentage}% || {value}/{total} Captions',\n barCompleteChar: '\\u2588',\n barIncompleteChar: '\\u2591',\n hideCursor: true,\n }, cliProgress.Presets.shades_classic);\n}","export function toMillis(timecodes: string): number {\n const parts = timecodes.split(/[:,]/).map(Number);\n\n const hours = parts[0];\n const minutes = parts[1];\n const seconds = parts[2];\n const milliseconds = parts[3];\n\n return hours * 3_600_000 // hours to millis\n + minutes * 60_000 // minutes to millis\n + seconds * 1000 // second to millis\n + milliseconds;\n}","import {readFileSync} from 'fs';\nimport {Caption, Word} from '../common/caption';\nimport {toMillis} from '../common/timecodes';\n\nconst indexLinePattern = /^\\d+$/;\nconst timecodesLinePattern = /^(\\d{2}:\\d{2}:\\d{2},\\d{3}) --> (\\d{2}:\\d{2}:\\d{2},\\d{3})$/;\nconst highlightedWordPattern = /^\\[(.+)](?:\\((\\w+)\\))?$/;\n\nexport function parseCaptions(srtCaptionsFile: string): Caption[] {\n const captionsSrc = readFileSync(srtCaptionsFile, 'utf-8');\n return readCaptions(captionsSrc);\n}\n\nfunction readCaptions(srtContent: string): Caption[] {\n const lines = srtContent.split('\\n');\n const captions: Caption[] = [];\n\n let index: number = 0;\n let timecodesStart: string | null = null;\n let timecodesEnd: string | null = null;\n\n for (const line of lines) {\n let match;\n if ((match = line.match(indexLinePattern))) {\n index = Number(line);\n } else if ((match = line.match(timecodesLinePattern))) {\n timecodesStart = match[1];\n timecodesEnd = match[2];\n } else if (line.length) {\n const start = toMillis(timecodesStart!);\n const end = toMillis(timecodesEnd!);\n\n const words = readWords(line);\n\n captions.push({\n index,\n words,\n startTimeMs: start,\n endTimeMs: end,\n });\n }\n }\n\n return captions;\n}\n\nexport function readWords(text: string): Word[] {\n const words = splitText(text);\n const highlightedIndex = words.findIndex(word => word.match(highlightedWordPattern));\n\n const res: Word[] = [];\n\n for (let i = 0; i < words.length; i++) {\n const word = words[i];\n const match = word.match(highlightedWordPattern);\n const rawWord = match ? match[1] : word;\n const highlightClass = match && match[2] ? match[2] : null;\n\n const isHighlighted = Boolean(match);\n const isBeforeHighlighted = Boolean(~highlightedIndex && !isHighlighted && i < highlightedIndex);\n const isAfterHighlighted = Boolean(~highlightedIndex && !isHighlighted && i > highlightedIndex);\n\n const wordObject: Word = {\n rawWord,\n isHighlighted,\n isBeforeHighlighted,\n isAfterHighlighted,\n };\n\n if (highlightClass) {\n wordObject.highlightClass = highlightClass;\n }\n\n res.push(wordObject);\n }\n\n return res;\n}\n\nfunction splitText(text: string): string[] {\n const words: string[] = [];\n\n let currentWord = '';\n let isCurrentHighlighted = false;\n\n for (let i = 0; i < text.length; i++) {\n const char = text[i];\n const isWhitespace = /^\\s$/.test(char);\n\n if (!isWhitespace) {\n currentWord += char;\n switch (char) {\n case '[':\n case '(':\n isCurrentHighlighted = true;\n break;\n case ']':\n case ')':\n isCurrentHighlighted = false;\n break;\n }\n } else {\n // char is a whitespace\n if (isCurrentHighlighted) {\n currentWord += char;\n } else if (currentWord) {\n words.push(currentWord);\n currentWord = '';\n }\n }\n }\n\n if (currentWord) {\n words.push(currentWord);\n }\n\n return words;\n}","import * as tmp from 'tmp';\nimport * as path from 'path';\nimport {writeFileSync, symlinkSync, rmSync, mkdirSync} from 'fs';\nimport {Caption} from '../common/caption';\nimport {Args} from './cli';\nimport {indexHtml, indexJs, nodeModules} from './assets';\nimport {PlayerArgs} from '../common/player-args';\n\nexport class WorkDir {\n private readonly workDir = tmp.dirSync({ template: 'pupcaps-XXXXXX' });\n\n constructor(private readonly captions: Caption[],\n private readonly args: Args) {\n }\n\n public setup(): string {\n const index = path.join(this.workDir.name, 'index.html');\n\n symlinkSync(indexHtml, index);\n symlinkSync(indexJs, path.join(this.workDir.name, 'index.js'));\n symlinkSync(this.args.styleFile, path.join(this.workDir.name, 'captions.css'));\n symlinkSync(nodeModules, path.join(this.workDir.name, 'node_modules'));\n\n this.setupCaptions();\n this.setupPlayerArgs();\n this.setupVideoSizeCss();\n\n mkdirSync(this.screenShotsDir);\n\n return index;\n }\n\n public clear() {\n rmSync(this.workDir.name, { recursive: true, force: true });\n }\n\n public get screenShotsDir(): string {\n return path.join(this.workDir.name, 'screenshots');\n }\n\n public get rootDir(): string {\n return this.workDir.name;\n }\n\n private setupVideoSizeCss() {\n const css= `#video {\n width: ${this.args.videoWidth}px;\n height: ${this.args.videoHeight}px;\n }`;\n const videoSizeFile = path.join(this.workDir.name, 'video.size.css');\n\n writeFileSync(videoSizeFile, css);\n }\n\n private setupCaptions() {\n const captionsJs = 'window.captions = ' + JSON.stringify(this.captions, null, 2);\n const captionsJsFile = path.join(this.workDir.name, 'captions.js');\n\n writeFileSync(captionsJsFile, captionsJs);\n }\n\n private setupPlayerArgs() {\n const playerArgs: PlayerArgs = {\n isPreview: this.args.isPreview,\n };\n const argsJs = 'window.playerArgs = ' + JSON.stringify(playerArgs, null, 2);\n const argsJsFile = path.join(this.workDir.name, 'player.args.js');\n\n writeFileSync(argsJsFile, argsJs);\n }\n}","export class StatsPrinter {\n private statsPrinted = false;\n\n public print(stats: Object) {\n const lines = Object\n .entries(stats)\n .map(([key, value]) => `${key}: ${value}`);\n\n if (this.statsPrinted) {\n process.stdout.write(`\\x1b[${lines.length}A`); // Move up N lines\n }\n\n lines.forEach((line) => {\n process.stdout.write(`\\r${line.padEnd(40)}\\n`); // Ensure the line is fully overwritten\n });\n\n this.statsPrinted = true;\n }\n}","import ffmpeg, { setFfmpegPath } from 'fluent-ffmpeg';\nimport {Args} from './cli';\n\n(() => {\n try {\n const ffmpegInstaller = require('@ffmpeg-installer/ffmpeg');\n setFfmpegPath(ffmpegInstaller!.path);\n } catch (error) {\n console.warn('Impossible to install FFMpeg. Use system-provided ffmpeg.');\n }\n})();\n\nexport abstract class AbstractRenderer {\n protected constructor(protected readonly args : Args) {\n }\n\n public abstract startEncoding(): void;\n public abstract endEncoding(): void;\n\n protected baseFfmpegCommand(): ffmpeg.FfmpegCommand {\n return ffmpeg()\n .outputOptions([\n '-c:v prores_ks', // codec for Films Apple QuickTime (MOV)\n '-profile:v 4444', // enable the best quality\n '-pix_fmt yuva444p10le', // lossless setting\n '-q:v 0', // lossless setting\n '-vendor ap10' // ensures the output MOV file is compatible with Apple QuickTime\n ])\n .output(this.args.movOutputFile);\n }\n}","import {Args} from './cli';\nimport {PNG, PNGWithMetadata} from 'pngjs';\nimport * as path from 'path';\nimport {appendFileSync, writeFileSync} from 'fs';\nimport {WorkDir} from './work-dir';\nimport {Caption} from '../common/caption';\nimport {StatsPrinter} from './stats-printer';\nimport {AbstractRenderer} from './abstract-renderer';\n\nexport class StepRenderer extends AbstractRenderer {\n private readonly framesFileName: string;\n private readonly emptyFrameFileName: string;\n\n constructor(args : Args,\n private readonly workDir: WorkDir) {\n super(args);\n this.framesFileName = path.join(workDir.screenShotsDir, 'frames.txt');\n this.emptyFrameFileName = path.join(workDir.screenShotsDir, 'empty.png');\n }\n\n public startEncoding() {\n const empty = new PNG({\n width: this.args.videoWidth,\n height: this.args.videoHeight,\n colorType: 6,\n });\n writeFileSync(this.emptyFrameFileName, PNG.sync.write(empty));\n }\n\n public addEmptyFrame(durationMs?: number) {\n let frameDef = `file '${this.emptyFrameFileName}'\\n`;\n\n if (durationMs) {\n const durationSec = durationMs / 1000;\n frameDef += `duration ${durationSec}\\n`;\n }\n\n appendFileSync(this.framesFileName, frameDef, 'utf8');\n }\n\n public addFrame(caption: Caption, png: PNGWithMetadata) {\n const screenShotFileName = path.join(this.workDir.screenShotsDir, `screenshot_${caption.index}.png`);\n writeFileSync(screenShotFileName, PNG.sync.write(png));\n\n const durationSec = (caption.endTimeMs - caption.startTimeMs) / 1000;\n\n appendFileSync(\n this.framesFileName,\n `file '${screenShotFileName}'\\nduration ${durationSec}\\n`,\n 'utf8');\n }\n\n public async endEncoding() {\n console.log(`Encoding ${this.args.movOutputFile}...\\n`);\n const statsPrinter = new StatsPrinter();\n\n await new Promise((resolve, reject) => {\n this.baseFfmpegCommand()\n .input(this.framesFileName)\n .inputOptions([\n '-f concat', // concat frames from the frame list\n '-safe 0' // to prevent errors related to unsafe filenames\n ])\n .on('progress', (progress: Object) => {\n statsPrinter.print(progress);\n })\n .on('end', () => {\n console.log(`${this.args.movOutputFile} encoded`);\n resolve(this.args.movOutputFile);\n })\n .on('error', (err: any) => {\n reject(err);\n })\n .run();\n });\n }\n}","import {createServer} from 'http-server';\nimport {WorkDir} from './work-dir';\n\nexport class PreviewServer {\n constructor(private readonly wordDir: WorkDir) {\n }\n\n public async start() {\n return new Promise(async (resolve, reject) => {\n try {\n const server = createServer({ root: this.wordDir.rootDir });\n const port = await PreviewServer.getFreePort();\n\n server.listen(port, async () => {\n try {\n const childProcess = await PreviewServer.openUrl(`http://127.0.0.1:${port}`);\n\n childProcess.on('close', () => {\n server.close(() => {\n resolve();\n });\n });\n } catch (error) {\n reject(error);\n }\n });\n } catch (error) {\n reject(error);\n }\n });\n }\n\n private static async getFreePort(): Promise {\n const { default: getPort } = await import('get-port');\n return getPort();\n }\n\n private static async openUrl(url: string) {\n const { default: open } = await import('open');\n return open(url, { wait: true });\n }\n}","import * as puppeteer from 'puppeteer';\nimport {Args} from './cli';\n\nexport abstract class AbstractRecorder {\n protected browser: puppeteer.Browser | null = null;\n protected page: puppeteer.Page | null = null;\n\n protected constructor(protected readonly args: Args) {\n }\n\n public abstract recordCaptionsVideo(indexHtml: string): Promise;\n\n protected async launchBrowser(indexHtml: string): Promise {\n this.browser = await puppeteer.launch({\n args: [\n '--disable-web-security', // Disable CORS\n '--allow-file-access-from-files', // Allow file access\n ],\n headless: true,\n });\n this.page = await this.browser.newPage();\n await this.page.goto(`file://${indexHtml}`);\n await this.page.setViewport({\n width: this.args.videoWidth,\n height: this.args.videoHeight,\n });\n await this.page.evaluate(() => {\n return window.ready;\n });\n\n return this.page.$('#video');\n }\n}","import * as puppeteer from 'puppeteer';\nimport {RealTimeRenderer} from './real-time-renderer';\nimport {Args} from './cli';\nimport {AbstractRecorder} from './abstract-recorder';\n\nexport class RealTimeRecorder extends AbstractRecorder {\n constructor(args: Args,\n private readonly videoRenderer: RealTimeRenderer) {\n super(args);\n }\n\n public async recordCaptionsVideo(indexHtml: string) {\n try {\n await this.launchBrowser(indexHtml);\n const cdpSession = await this.page!.createCDPSession();\n\n await cdpSession.send(\n 'Emulation.setDefaultBackgroundColorOverride',\n { color: { r: 0, g: 0, b: 0, a: 0 } }\n );\n await cdpSession.send('Animation.setPlaybackRate', {\n playbackRate: 1,\n });\n\n cdpSession.on('Page.screencastFrame',\n (frame) => this.handleScreenCastFrame(cdpSession, frame));\n\n this.videoRenderer.startEncoding();\n\n await cdpSession.send('Page.startScreencast', {\n everyNthFrame: 1,\n format: 'png',\n quality: 100,\n });\n\n await this.page!.evaluate(() => {\n return new Promise((resolve) => {\n window.Player.onStop = resolve;\n window.Player.play();\n })\n });\n\n await cdpSession.send('Page.stopScreencast');\n\n this.videoRenderer.endEncoding();\n } catch (error) {\n console.error('Error during Puppeteer operation:', error);\n } finally {\n await this.browser?.close();\n }\n }\n\n private async handleScreenCastFrame(cdpSession: puppeteer.CDPSession,\n frame: puppeteer.Protocol.Page.ScreencastFrameEvent) {\n const { sessionId, data } = frame;\n await cdpSession.send('Page.screencastFrameAck', { sessionId });\n const frameBuffer = Buffer.from(data, 'base64');\n this.videoRenderer.addFrame(frameBuffer);\n }\n}","import {PassThrough} from 'stream';\nimport {PNG} from 'pngjs';\nimport {Args} from './cli';\nimport {StatsPrinter} from './stats-printer';\nimport {AbstractRenderer} from './abstract-renderer';\n\nexport class RealTimeRenderer extends AbstractRenderer {\n private inputStream: PassThrough | null = null;\n private intervalId: NodeJS.Timeout | null = null;\n private lastFrame: Buffer;\n\n constructor(args: Args) {\n super(args);\n const empty = new PNG({\n width: this.args.videoWidth,\n height: this.args.videoHeight,\n colorType: 6,\n });\n this.lastFrame = PNG.sync.write(empty);\n }\n\n public startEncoding() {\n this.inputStream = new PassThrough();\n const statsPrinter = new StatsPrinter();\n\n const command = this.baseFfmpegCommand()\n .input(this.inputStream)\n .inputOptions([\n '-f image2pipe', // Format of input frames\n '-pix_fmt yuva444p10le', // Lossless setting\n `-s ${this.args.videoWidth}x${this.args.videoHeight}`, // Frame size\n `-r ${this.args.fps}`, // Framerate\n ])\n .on('start', () => {\n console.log('FFmpeg process started.');\n })\n .on('progress', (progress) => {\n statsPrinter.print(progress);\n })\n .on('end', () => {\n console.log('FFmpeg process completed.');\n })\n .on('error', (err) => {\n console.error('An error occurred:', err.message);\n });\n\n command.run();\n\n // Produce frames in required rate\n const intervalDuration = Math.round(1000 / 30);\n this.intervalId = setInterval(() => {\n this.inputStream!.write(this.lastFrame);\n }, intervalDuration);\n }\n\n public addFrame(frame: Buffer) {\n this.lastFrame = frame;\n }\n\n public endEncoding() {\n clearTimeout(this.intervalId!);\n this.inputStream!.end();\n }\n}","import * as puppeteer from 'puppeteer';\nimport * as cliProgress from 'cli-progress';\nimport {PNG, PNGWithMetadata} from 'pngjs';\nimport {Caption} from '../common/caption';\nimport {StepRenderer} from './step-renderer';\nimport {Args} from './cli';\nimport {AbstractRecorder} from './abstract-recorder';\n\nexport class StepRecorder extends AbstractRecorder {\n constructor(args: Args,\n private readonly captions: Caption[],\n private readonly renderer: StepRenderer,\n private readonly progressBar: cliProgress.SingleBar) {\n super(args);\n }\n\n public async recordCaptionsVideo(indexHtml: string) {\n this.progressBar.start(this.captions.length, 0);\n\n try {\n const videoElem = await this.launchBrowser(indexHtml);\n\n this.renderer.startEncoding();\n\n // Add empty frame before captions starts\n const beginningTime = this.captions[0].startTimeMs;\n this.renderer.addEmptyFrame(beginningTime);\n\n for (let i = 0; i < this.captions.length; i++) {\n const caption = this.captions[i];\n\n await this.nextStep();\n\n const screenShot = await this.takeScreenShot(videoElem!);\n this.renderer.addFrame(caption, screenShot);\n\n // Add delay before the next frame\n if (i < this.captions.length - 1) {\n const idleDelay = this.captions[i + 1].startTimeMs - caption.endTimeMs;\n if (idleDelay) {\n this.renderer.addEmptyFrame(idleDelay);\n }\n }\n\n this.progressBar.increment();\n }\n\n // Finish with en empty frame\n this.renderer.addEmptyFrame();\n\n this.progressBar.stop();\n await this.renderer.endEncoding();\n } catch (error) {\n console.error('Error during Puppeteer operation:', error);\n } finally {\n await this.browser?.close();\n }\n }\n\n private async nextStep() {\n await this.page!.evaluate(() => {\n window.Player.next();\n });\n }\n\n private async takeScreenShot(elem: puppeteer.ElementHandle): Promise {\n const screenshotBuffer = await elem.screenshot({\n encoding: 'binary',\n omitBackground: true,\n });\n return PNG.sync.read(Buffer.from(screenshotBuffer));\n }\n}","import {Args, createProgressBar, parseArgs, printArgs} from './cli';\nimport {parseCaptions} from './srt-captions-reader';\nimport {WorkDir} from './work-dir';\nimport {StepRenderer} from './step-renderer';\nimport {PreviewServer} from './preview-server';\nimport {RealTimeRecorder} from './real-time-recorder';\nimport {RealTimeRenderer} from './real-time-renderer';\nimport {StepRecorder} from './step-recorder';\nimport {AbstractRecorder} from './abstract-recorder';\nimport {Caption} from '../common/caption';\n\nfunction createRecorder(args: Args, captions: Caption[], workDir: WorkDir): AbstractRecorder {\n if (args.css3Animations) {\n const realTimeRenderer = new RealTimeRenderer(args);\n return new RealTimeRecorder(args, realTimeRenderer);\n } else {\n const progressBar = createProgressBar();\n const stepRenderer = new StepRenderer(args, workDir);\n return new StepRecorder(args, captions, stepRenderer, progressBar);\n }\n}\n\nconst cliArgs = parseArgs();\nconst captions = parseCaptions(cliArgs.srtInputFile);\nconst workDir = new WorkDir(captions, cliArgs);\n\n(async () => {\n try {\n const indexHtml = workDir.setup();\n printArgs(cliArgs);\n\n if (!cliArgs.isPreview) {\n const recorder = createRecorder(cliArgs, captions, workDir);\n await recorder.recordCaptionsVideo(indexHtml);\n } else {\n console.log('Launching preview server...');\n const previewServer = new PreviewServer(workDir);\n await previewServer.start();\n }\n console.log('Done!');\n } catch (err) {\n console.error('Error occurred:', err);\n } finally {\n workDir.clear();\n }\n})();\n"],"names":["program","path","cliProgress","readFileSync","tmp","symlinkSync","mkdirSync","rmSync","writeFileSync","setFfmpegPath","PNG","appendFileSync","createServer","puppeteer","PassThrough"],"mappings":"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","x_google_ignoreList":[0,1]}
\ No newline at end of file
+{"version":3,"file":"index.js","sources":["../../node_modules/@commander-js/extra-typings/index.js","../../node_modules/@commander-js/extra-typings/esm.mjs","../../src/script/assets.ts","../../src/script/cli.ts","../../src/script/work-dir.ts","../../src/script/stats-printer.ts","../../src/script/abstract-renderer.ts","../../src/script/step-renderer.ts","../../src/script/abstract-recorder.ts","../../src/script/real-time-recorder.ts","../../src/script/real-time-renderer.ts","../../src/script/step-recorder.ts","../../src/common/timecodes.ts","../../src/common/captions.ts","../../src/common/web-server.ts","../../src/script/index.ts"],"sourcesContent":["const commander = require('commander');\n\nexports = module.exports = {};\n\n// Return a different global program than commander,\n// and don't also return it as default export.\nexports.program = new commander.Command();\n\n/**\n * Expose classes. The FooT versions are just types, so return Commander original implementations!\n */\n\nexports.Argument = commander.Argument;\nexports.Command = commander.Command;\nexports.CommanderError = commander.CommanderError;\nexports.Help = commander.Help;\nexports.InvalidArgumentError = commander.InvalidArgumentError;\nexports.InvalidOptionArgumentError = commander.InvalidArgumentError; // Deprecated\nexports.Option = commander.Option;\n\n// In Commander, the create routines end up being aliases for the matching\n// methods on the global program due to the (deprecated) legacy default export.\n// Here we roll our own, the way Commander might in future.\nexports.createCommand = (name) => new commander.Command(name);\nexports.createOption = (flags, description) =>\n new commander.Option(flags, description);\nexports.createArgument = (name, description) =>\n new commander.Argument(name, description);\n","import extraTypingsCommander from './index.js';\n\n// wrapper to provide named exports for ESM.\nexport const {\n program,\n createCommand,\n createArgument,\n createOption,\n CommanderError,\n InvalidArgumentError,\n InvalidOptionArgumentError, // deprecated old name\n Command,\n Argument,\n Option,\n Help,\n} = extraTypingsCommander;\n","import * as path from 'path';\n\nexport const assetsFolder = path.join(__dirname, '..', '..', 'assets');\nexport const defaultStylesCss = path.join(assetsFolder, 'captions.css');\nexport const indexHtml = path.join(assetsFolder, 'index.html');\n\nexport const indexJs = path.join(__dirname, '..', 'web', 'index.js');\n\nexport const nodeModules = path.join(__dirname, '..', '..', 'node_modules');\n","import {Command} from '@commander-js/extra-typings';\nimport packageJson from '../../package.json';\nimport {defaultStylesCss} from './assets';\nimport * as cliProgress from 'cli-progress';\nimport * as path from 'path';\n\nexport interface Args {\n srtInputFile: string;\n movOutputFile: string;\n videoWidth: number;\n videoHeight: number;\n fps: number;\n styleFile: string;\n css3Animations: boolean;\n isPreview: boolean;\n}\n\nfunction parseIntAndAssert(...assertions: ((v: number) => void)[]): (v: string) => number {\n return (value: string) => {\n const int = parseInt(value, 10);\n assertions.forEach(assertion => assertion(int));\n return int;\n }\n}\n\nfunction assertPositive(option: string): (v: number) => void {\n return (value: number) => {\n if (value < 0) {\n throw new Error(`${option} should be positive!`);\n }\n };\n}\n\nfunction assertMinMax(option: string, min: number, max: number): (v: number) => void {\n return (value: number) => {\n if (value < min || value > max) {\n throw new Error(`${option} should be between ${min} and ${max}!`);\n }\n };\n}\n\nfunction assertFileExtension(ext: string): (v: string) => void {\n return (value: string) => {\n if (!value.endsWith(ext)) {\n throw new Error(`File should have extension ${ext}!`);\n }\n return value;\n };\n}\n\nconst program = new Command();\n\nprogram\n .name('pupcaps')\n .description('Tool to add stylish captions to your video.')\n .version(packageJson.version)\n .argument('', 'Path to the input SubRip Subtitle (.srt) file.', assertFileExtension('.srt'))\n .option('-o, --output ',\n 'Full or relative path where the created Films Apple QuickTime (MOV) file should be written. ' +\n 'By default, it will be saved in the same directory as the input subtitle file.',\n assertFileExtension('.mov'))\n .option('-w, --width ',\n 'Width of the video in pixels.',\n parseIntAndAssert(assertPositive('Width')),\n 1080)\n .option('-h, --height ',\n 'Height of the video in pixels.',\n parseIntAndAssert(assertPositive('Height')),\n 1920)\n .option('-r, --fps ',\n 'Specifies the frame rate (FPS) of the output video. Valid values are between 1 and 60.',\n parseIntAndAssert(assertMinMax('FPS', 1, 60)),\n 30)\n .option('-s, --style ',\n 'Full or relative path to the styles .css file. ' +\n 'If not provided, default styles for captions will be used.',\n assertFileExtension('.css'))\n .option('-a, --animate',\n 'Records captions with CSS3 animations. ' +\n 'Note: The recording will run for the entire duration of the video. ' +\n 'Use this option only if your captions involve CSS3 animations.')\n .option('--preview',\n 'Prevents the script from generating a video file. ' +\n 'Instead, captions are displayed in the browser for debugging and preview purposes.')\n .action((inputFile, options: any) => {\n if (!options.output) {\n const fileBasename = (inputFile as any as string).slice(0, -4);\n options.output = `${fileBasename}.mov`;\n }\n\n if (!options.style) {\n options.style = defaultStylesCss;\n } else {\n options.style = path.resolve(options.style);\n }\n });\n\nexport function parseArgs(): Args {\n program.parse();\n const opts = program.opts() as any;\n\n return {\n srtInputFile: program.args[0],\n movOutputFile: opts.output,\n videoWidth: opts.width,\n videoHeight: opts.height,\n fps: opts.fps,\n styleFile: opts.style,\n css3Animations: opts.animate,\n isPreview: opts.preview,\n };\n}\n\nexport function printArgs(args: Args) {\n const styles = args.styleFile === defaultStylesCss\n ? '(Default)'\n : args.styleFile;\n\n const srt = `\n Output: ${args.movOutputFile}\n Width: ${args.videoWidth} px\n Height: ${args.videoHeight} px\n FPS: ${args.fps}\n Styles: ${styles}\n Animations: ${ args.css3Animations ? 'yes' : 'no' }\n `;\n\n console.log(srt);\n}\n\nexport function createProgressBar(): cliProgress.SingleBar {\n return new cliProgress.SingleBar({\n format: 'Progress |{bar}| {percentage}% || {value}/{total} Captions',\n barCompleteChar: '\\u2588',\n barIncompleteChar: '\\u2591',\n hideCursor: true,\n }, cliProgress.Presets.shades_classic);\n}","import * as tmp from 'tmp';\nimport * as path from 'path';\nimport {writeFileSync, symlinkSync, rmSync, mkdirSync} from 'fs';\nimport {Caption} from '../common/captions';\nimport {Args} from './cli';\nimport {indexHtml, indexJs, nodeModules} from './assets';\nimport {PlayerArgs} from '../common/player-args';\n\nexport class WorkDir {\n private readonly workDir = tmp.dirSync({ template: 'pupcaps-XXXXXX' });\n\n constructor(private readonly captions: Caption[],\n private readonly args: Args) {\n }\n\n public setup(): string {\n const index = path.join(this.workDir.name, 'index.html');\n\n symlinkSync(indexHtml, index);\n symlinkSync(indexJs, path.join(this.workDir.name, 'index.js'));\n symlinkSync(this.args.styleFile, path.join(this.workDir.name, 'captions.css'));\n symlinkSync(nodeModules, path.join(this.workDir.name, 'node_modules'));\n\n this.setupCaptions();\n this.setupPlayerArgs();\n this.setupVideoSizeCss();\n\n mkdirSync(this.screenShotsDir);\n\n return index;\n }\n\n public clear() {\n rmSync(this.workDir.name, { recursive: true, force: true });\n }\n\n public get screenShotsDir(): string {\n return path.join(this.workDir.name, 'screenshots');\n }\n\n public get rootDir(): string {\n return this.workDir.name;\n }\n\n private setupVideoSizeCss() {\n const css= `#video {\n width: ${this.args.videoWidth}px;\n height: ${this.args.videoHeight}px;\n }`;\n const videoSizeFile = path.join(this.workDir.name, 'video.size.css');\n\n writeFileSync(videoSizeFile, css);\n }\n\n private setupCaptions() {\n const captionsJs = 'window.captions = ' + JSON.stringify(this.captions, null, 2);\n const captionsJsFile = path.join(this.workDir.name, 'captions.js');\n\n writeFileSync(captionsJsFile, captionsJs);\n }\n\n private setupPlayerArgs() {\n const playerArgs: PlayerArgs = {\n isPreview: this.args.isPreview,\n };\n const argsJs = 'window.playerArgs = ' + JSON.stringify(playerArgs, null, 2);\n const argsJsFile = path.join(this.workDir.name, 'player.args.js');\n\n writeFileSync(argsJsFile, argsJs);\n }\n}","export class StatsPrinter {\n private statsPrinted = false;\n\n public print(stats: Object) {\n const lines = Object\n .entries(stats)\n .map(([key, value]) => `${key}: ${value}`);\n\n if (this.statsPrinted) {\n process.stdout.write(`\\x1b[${lines.length}A`); // Move up N lines\n }\n\n lines.forEach((line) => {\n process.stdout.write(`\\r${line.padEnd(40)}\\n`); // Ensure the line is fully overwritten\n });\n\n this.statsPrinted = true;\n }\n}","import ffmpeg, { setFfmpegPath } from 'fluent-ffmpeg';\nimport {Args} from './cli';\n\n(() => {\n try {\n const ffmpegInstaller = require('@ffmpeg-installer/ffmpeg');\n setFfmpegPath(ffmpegInstaller!.path);\n } catch (error) {\n console.warn('Impossible to install FFMpeg. Use system-provided ffmpeg.');\n }\n})();\n\nexport abstract class AbstractRenderer {\n protected constructor(protected readonly args : Args) {\n }\n\n public abstract startEncoding(): void;\n public abstract endEncoding(): void;\n\n protected baseFfmpegCommand(): ffmpeg.FfmpegCommand {\n return ffmpeg()\n .outputOptions([\n '-c:v prores_ks', // codec for Films Apple QuickTime (MOV)\n '-profile:v 4444', // enable the best quality\n '-pix_fmt yuva444p10le', // lossless setting\n '-q:v 0', // lossless setting\n '-vendor ap10' // ensures the output MOV file is compatible with Apple QuickTime\n ])\n .output(this.args.movOutputFile);\n }\n}","import {Args} from './cli';\nimport {PNG, PNGWithMetadata} from 'pngjs';\nimport * as path from 'path';\nimport {appendFileSync, writeFileSync} from 'fs';\nimport {WorkDir} from './work-dir';\nimport {Caption} from '../common/captions';\nimport {StatsPrinter} from './stats-printer';\nimport {AbstractRenderer} from './abstract-renderer';\n\nexport class StepRenderer extends AbstractRenderer {\n private readonly framesFileName: string;\n private readonly emptyFrameFileName: string;\n\n constructor(args : Args,\n private readonly workDir: WorkDir) {\n super(args);\n this.framesFileName = path.join(workDir.screenShotsDir, 'frames.txt');\n this.emptyFrameFileName = path.join(workDir.screenShotsDir, 'empty.png');\n }\n\n public startEncoding() {\n const empty = new PNG({\n width: this.args.videoWidth,\n height: this.args.videoHeight,\n colorType: 6,\n });\n writeFileSync(this.emptyFrameFileName, PNG.sync.write(empty));\n }\n\n public addEmptyFrame(durationMs?: number) {\n let frameDef = `file '${this.emptyFrameFileName}'\\n`;\n\n if (durationMs) {\n const durationSec = durationMs / 1000;\n frameDef += `duration ${durationSec}\\n`;\n }\n\n appendFileSync(this.framesFileName, frameDef, 'utf8');\n }\n\n public addFrame(caption: Caption, png: PNGWithMetadata) {\n const screenShotFileName = path.join(this.workDir.screenShotsDir, `screenshot_${caption.index}.png`);\n writeFileSync(screenShotFileName, PNG.sync.write(png));\n\n const durationSec = (caption.endTimeMs - caption.startTimeMs) / 1000;\n\n appendFileSync(\n this.framesFileName,\n `file '${screenShotFileName}'\\nduration ${durationSec}\\n`,\n 'utf8');\n }\n\n public async endEncoding() {\n console.log(`Encoding ${this.args.movOutputFile}...\\n`);\n const statsPrinter = new StatsPrinter();\n\n await new Promise((resolve, reject) => {\n this.baseFfmpegCommand()\n .input(this.framesFileName)\n .inputOptions([\n '-f concat', // concat frames from the frame list\n '-safe 0' // to prevent errors related to unsafe filenames\n ])\n .on('progress', (progress: Object) => {\n statsPrinter.print(progress);\n })\n .on('end', () => {\n console.log(`${this.args.movOutputFile} encoded`);\n resolve(this.args.movOutputFile);\n })\n .on('error', (err: any) => {\n reject(err);\n })\n .run();\n });\n }\n}","import * as puppeteer from 'puppeteer';\nimport {Args} from './cli';\n\nexport abstract class AbstractRecorder {\n protected browser: puppeteer.Browser | null = null;\n protected page: puppeteer.Page | null = null;\n\n protected constructor(protected readonly args: Args) {\n }\n\n public abstract recordCaptionsVideo(indexHtml: string): Promise;\n\n protected async launchBrowser(indexHtml: string): Promise {\n this.browser = await puppeteer.launch({\n args: [\n '--disable-web-security', // Disable CORS\n '--allow-file-access-from-files', // Allow file access\n ],\n headless: true,\n });\n this.page = await this.browser.newPage();\n await this.page.goto(`file://${indexHtml}`);\n await this.page.setViewport({\n width: this.args.videoWidth,\n height: this.args.videoHeight,\n });\n await this.page.evaluate(() => {\n return window.ready;\n });\n\n return this.page.$('#video');\n }\n}","import * as puppeteer from 'puppeteer';\nimport {RealTimeRenderer} from './real-time-renderer';\nimport {Args} from './cli';\nimport {AbstractRecorder} from './abstract-recorder';\n\nexport class RealTimeRecorder extends AbstractRecorder {\n constructor(args: Args,\n private readonly videoRenderer: RealTimeRenderer) {\n super(args);\n }\n\n public async recordCaptionsVideo(indexHtml: string) {\n try {\n await this.launchBrowser(indexHtml);\n const cdpSession = await this.page!.createCDPSession();\n\n await cdpSession.send(\n 'Emulation.setDefaultBackgroundColorOverride',\n { color: { r: 0, g: 0, b: 0, a: 0 } }\n );\n await cdpSession.send('Animation.setPlaybackRate', {\n playbackRate: 1,\n });\n\n cdpSession.on('Page.screencastFrame',\n (frame) => this.handleScreenCastFrame(cdpSession, frame));\n\n this.videoRenderer.startEncoding();\n\n await cdpSession.send('Page.startScreencast', {\n everyNthFrame: 1,\n format: 'png',\n quality: 100,\n });\n\n await this.page!.evaluate(() => {\n return new Promise((resolve) => {\n window.Player.onStop = resolve;\n window.Player.play();\n })\n });\n\n await cdpSession.send('Page.stopScreencast');\n\n this.videoRenderer.endEncoding();\n } catch (error) {\n console.error('Error during Puppeteer operation:', error);\n } finally {\n await this.browser?.close();\n }\n }\n\n private async handleScreenCastFrame(cdpSession: puppeteer.CDPSession,\n frame: puppeteer.Protocol.Page.ScreencastFrameEvent) {\n const { sessionId, data } = frame;\n await cdpSession.send('Page.screencastFrameAck', { sessionId });\n const frameBuffer = Buffer.from(data, 'base64');\n this.videoRenderer.addFrame(frameBuffer);\n }\n}","import {PassThrough} from 'stream';\nimport {PNG} from 'pngjs';\nimport {Args} from './cli';\nimport {StatsPrinter} from './stats-printer';\nimport {AbstractRenderer} from './abstract-renderer';\n\nexport class RealTimeRenderer extends AbstractRenderer {\n private inputStream: PassThrough | null = null;\n private intervalId: NodeJS.Timeout | null = null;\n private lastFrame: Buffer;\n\n constructor(args: Args) {\n super(args);\n const empty = new PNG({\n width: this.args.videoWidth,\n height: this.args.videoHeight,\n colorType: 6,\n });\n this.lastFrame = PNG.sync.write(empty);\n }\n\n public startEncoding() {\n this.inputStream = new PassThrough();\n const statsPrinter = new StatsPrinter();\n\n const command = this.baseFfmpegCommand()\n .input(this.inputStream)\n .inputOptions([\n '-f image2pipe', // Format of input frames\n '-pix_fmt yuva444p10le', // Lossless setting\n `-s ${this.args.videoWidth}x${this.args.videoHeight}`, // Frame size\n `-r ${this.args.fps}`, // Framerate\n ])\n .on('start', () => {\n console.log('FFmpeg process started.');\n })\n .on('progress', (progress) => {\n statsPrinter.print(progress);\n })\n .on('end', () => {\n console.log('FFmpeg process completed.');\n })\n .on('error', (err) => {\n console.error('An error occurred:', err.message);\n });\n\n command.run();\n\n // Produce frames in required rate\n const intervalDuration = Math.round(1000 / 30);\n this.intervalId = setInterval(() => {\n this.inputStream!.write(this.lastFrame);\n }, intervalDuration);\n }\n\n public addFrame(frame: Buffer) {\n this.lastFrame = frame;\n }\n\n public endEncoding() {\n clearTimeout(this.intervalId!);\n this.inputStream!.end();\n }\n}","import * as puppeteer from 'puppeteer';\nimport * as cliProgress from 'cli-progress';\nimport {PNG, PNGWithMetadata} from 'pngjs';\nimport {Caption} from '../common/captions';\nimport {StepRenderer} from './step-renderer';\nimport {Args} from './cli';\nimport {AbstractRecorder} from './abstract-recorder';\n\nexport class StepRecorder extends AbstractRecorder {\n constructor(args: Args,\n private readonly captions: Caption[],\n private readonly renderer: StepRenderer,\n private readonly progressBar: cliProgress.SingleBar) {\n super(args);\n }\n\n public async recordCaptionsVideo(indexHtml: string) {\n this.progressBar.start(this.captions.length, 0);\n\n try {\n const videoElem = await this.launchBrowser(indexHtml);\n\n this.renderer.startEncoding();\n\n // Add empty frame before captions starts\n const beginningTime = this.captions[0].startTimeMs;\n this.renderer.addEmptyFrame(beginningTime);\n\n for (let i = 0; i < this.captions.length; i++) {\n const caption = this.captions[i];\n\n await this.nextStep();\n\n const screenShot = await this.takeScreenShot(videoElem!);\n this.renderer.addFrame(caption, screenShot);\n\n // Add delay before the next frame\n if (i < this.captions.length - 1) {\n const idleDelay = this.captions[i + 1].startTimeMs - caption.endTimeMs;\n if (idleDelay) {\n this.renderer.addEmptyFrame(idleDelay);\n }\n }\n\n this.progressBar.increment();\n }\n\n // Finish with en empty frame\n this.renderer.addEmptyFrame();\n\n this.progressBar.stop();\n await this.renderer.endEncoding();\n } catch (error) {\n console.error('Error during Puppeteer operation:', error);\n } finally {\n await this.browser?.close();\n }\n }\n\n private async nextStep() {\n await this.page!.evaluate(() => {\n window.Player.next();\n });\n }\n\n private async takeScreenShot(elem: puppeteer.ElementHandle): Promise {\n const screenshotBuffer = await elem.screenshot({\n encoding: 'binary',\n omitBackground: true,\n });\n return PNG.sync.read(Buffer.from(screenshotBuffer));\n }\n}","export class Timecode {\n public readonly hours: number;\n public readonly minutes: number;\n public readonly seconds: number;\n public readonly millis: number;\n\n constructor(millis: number) {\n this.millis = millis % 1000;\n\n this.hours = Math.floor(millis / 3_600_000);\n const remainingMillisAfterHours = millis % 3_600_000;\n this.minutes = Math.floor(remainingMillisAfterHours / 60_000);\n const remainingMillisAfterMinutes = remainingMillisAfterHours % 60_000;\n this.seconds = Math.floor(remainingMillisAfterMinutes / 1000);\n }\n\n public get hh(): string {\n return String(this.hours).padStart(2, '0');\n }\n\n public get mm(): string {\n return String(this.minutes).padStart(2, '0');\n }\n\n public get ss(): string {\n return String(this.seconds).padStart(2, '0');\n }\n public get SSS(): string {\n return String(this.millis).padStart(3, '0');\n }\n\n public get asString(): string {\n return `${this.hh}:${this.mm}:${this.ss},${this.SSS}`;\n }\n}\n\nexport function toMillis(timecodes: string): number {\n const parts = timecodes.split(/[:,]/).map(Number);\n\n const hours = parts[0];\n const minutes = parts[1];\n const seconds = parts[2];\n const milliseconds = parts[3];\n\n return hours * 3_600_000 // hours to millis\n + minutes * 60_000 // minutes to millis\n + seconds * 1000 // second to millis\n + milliseconds;\n}","import {toMillis} from './timecodes';\n\nconst indexLinePattern = /^\\d+$/;\nconst timecodesLinePattern = /^(\\d{2}:\\d{2}:\\d{2},\\d{3}) --> (\\d{2}:\\d{2}:\\d{2},\\d{3})$/;\nconst highlightedWordPattern = /^\\[(.+)](?:\\((\\w+)\\))?$/;\n\nexport interface Word {\n rawWord: string;\n isHighlighted: boolean;\n isBeforeHighlighted: boolean;\n isAfterHighlighted: boolean;\n highlightClass?: string;\n}\n\nexport interface Caption {\n index: number;\n startTimeMs: number;\n endTimeMs: number;\n words: Word[];\n}\n\nexport function haveSameWords(caption1: Caption, caption2: Caption): boolean {\n if (caption1.words.length != caption2.words.length) {\n return false;\n }\n\n for (let i =0; i < caption1.words.length; i++) {\n if (caption1.words[i].rawWord != caption2.words[i].rawWord) {\n return false;\n }\n }\n\n return true;\n}\n\nexport function readCaptions(srtContent: string): Caption[] {\n const lines = srtContent.split('\\n');\n const captions: Caption[] = [];\n\n let index: number = 0;\n let timecodesStart: string | null = null;\n let timecodesEnd: string | null = null;\n\n for (const line of lines) {\n let match;\n if ((match = line.match(indexLinePattern))) {\n index = Number(line);\n } else if ((match = line.match(timecodesLinePattern))) {\n timecodesStart = match[1];\n timecodesEnd = match[2];\n } else if (line.length) {\n const start = toMillis(timecodesStart!);\n const end = toMillis(timecodesEnd!);\n\n const words = readWords(line);\n\n captions.push({\n index,\n words,\n startTimeMs: start,\n endTimeMs: end,\n });\n }\n }\n\n return captions;\n}\n\nexport function readWords(text: string): Word[] {\n const words = splitText(text);\n const highlightedIndex = words.findIndex(word => word.match(highlightedWordPattern));\n\n const res: Word[] = [];\n\n for (let i = 0; i < words.length; i++) {\n const word = words[i];\n const match = word.match(highlightedWordPattern);\n const rawWord = match ? match[1] : word;\n const highlightClass = match && match[2] ? match[2] : null;\n\n const isHighlighted = Boolean(match);\n const isBeforeHighlighted = Boolean(~highlightedIndex && !isHighlighted && i < highlightedIndex);\n const isAfterHighlighted = Boolean(~highlightedIndex && !isHighlighted && i > highlightedIndex);\n\n const wordObject: Word = {\n rawWord,\n isHighlighted,\n isBeforeHighlighted,\n isAfterHighlighted,\n };\n\n if (highlightClass) {\n wordObject.highlightClass = highlightClass;\n }\n\n res.push(wordObject);\n }\n\n return res;\n}\n\nexport function splitText(text: string): string[] {\n const words: string[] = [];\n\n let currentWord = '';\n let isCurrentHighlighted = false;\n\n for (let i = 0; i < text.length; i++) {\n const char = text[i];\n const isWhitespace = /^\\s$/.test(char);\n const isPunctuation = /[,.!?]/.test(char);\n\n if (!isWhitespace) {\n if (!isPunctuation) {\n currentWord += char;\n switch (char) {\n case '[':\n case '(':\n isCurrentHighlighted = true;\n break;\n case ']':\n case ')':\n isCurrentHighlighted = false;\n break;\n }\n } else {\n if (currentWord) {\n currentWord += char;\n } else {\n // Attach punctuation mark to the previous word\n words[words.length - 1] += ' ' + char;\n }\n }\n } else {\n // Is a whitespace\n if (isCurrentHighlighted) {\n currentWord += char;\n } else if (currentWord) {\n words.push(currentWord);\n currentWord = '';\n }\n }\n }\n\n if (currentWord) {\n words.push(currentWord);\n }\n\n return words;\n}","import {createServer} from 'http-server';\n\nexport class WebServer {\n constructor(private readonly rootDir: string) {\n }\n\n public async start(relativePath = '') {\n return new Promise(async (resolve, reject) => {\n try {\n const server = createServer({ root: this.rootDir });\n const port = await WebServer.getFreePort();\n\n server.listen(port, async () => {\n try {\n const childProcess = await WebServer.openUrl(`http://127.0.0.1:${port}${relativePath}`);\n\n childProcess.on('close', () => {\n server.close(() => {\n resolve();\n });\n });\n } catch (error) {\n reject(error);\n }\n });\n } catch (error) {\n reject(error);\n }\n });\n }\n\n private static async getFreePort(): Promise {\n const { default: getPort } = await import('get-port');\n return getPort();\n }\n\n private static async openUrl(url: string) {\n const { default: open } = await import('open');\n return open(url, { wait: true });\n }\n}","import {readFileSync} from 'fs';\nimport {Args, createProgressBar, parseArgs, printArgs} from './cli';\nimport {WorkDir} from './work-dir';\nimport {StepRenderer} from './step-renderer';\nimport {RealTimeRecorder} from './real-time-recorder';\nimport {RealTimeRenderer} from './real-time-renderer';\nimport {StepRecorder} from './step-recorder';\nimport {AbstractRecorder} from './abstract-recorder';\nimport {Caption, readCaptions} from '../common/captions';\nimport {WebServer} from '../common/web-server';\n\nfunction parseCaptions(srtCaptionsFile: string): Caption[] {\n const captionsSrc = readFileSync(srtCaptionsFile, 'utf-8');\n return readCaptions(captionsSrc);\n}\n\nfunction createRecorder(args: Args, captions: Caption[], workDir: WorkDir): AbstractRecorder {\n if (args.css3Animations) {\n const realTimeRenderer = new RealTimeRenderer(args);\n return new RealTimeRecorder(args, realTimeRenderer);\n } else {\n const progressBar = createProgressBar();\n const stepRenderer = new StepRenderer(args, workDir);\n return new StepRecorder(args, captions, stepRenderer, progressBar);\n }\n}\n\nconst cliArgs = parseArgs();\nconst captions = parseCaptions(cliArgs.srtInputFile);\nconst workDir = new WorkDir(captions, cliArgs);\n\n(async () => {\n try {\n const indexHtml = workDir.setup();\n printArgs(cliArgs);\n\n if (!cliArgs.isPreview) {\n const recorder = createRecorder(cliArgs, captions, workDir);\n await recorder.recordCaptionsVideo(indexHtml);\n } else {\n console.log('Launching preview server...');\n const previewServer = new WebServer(workDir.rootDir);\n await previewServer.start();\n }\n console.log('Done!');\n } catch (err) {\n console.error('Error occurred:', err);\n } finally {\n workDir.clear();\n }\n})();\n"],"names":["program","path","cliProgress","tmp","symlinkSync","mkdirSync","rmSync","writeFileSync","setFfmpegPath","PNG","appendFileSync","puppeteer","PassThrough","createServer","readFileSync"],"mappings":"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","x_google_ignoreList":[0,1]}
\ No newline at end of file
diff --git a/dist/script/srt-editor.js b/dist/script/srt-editor.js
new file mode 100644
index 0000000..0761860
--- /dev/null
+++ b/dist/script/srt-editor.js
@@ -0,0 +1,69 @@
+'use strict';
+
+var path = require('path');
+var httpServer = require('http-server');
+
+function _interopNamespaceDefault(e) {
+ var n = Object.create(null);
+ if (e) {
+ Object.keys(e).forEach(function (k) {
+ if (k !== 'default') {
+ var d = Object.getOwnPropertyDescriptor(e, k);
+ Object.defineProperty(n, k, d.get ? d : {
+ enumerable: true,
+ get: function () { return e[k]; }
+ });
+ }
+ });
+ }
+ n.default = e;
+ return Object.freeze(n);
+}
+
+var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
+
+class WebServer {
+ rootDir;
+ constructor(rootDir) {
+ this.rootDir = rootDir;
+ }
+ async start(relativePath = '') {
+ return new Promise(async (resolve, reject) => {
+ try {
+ const server = httpServer.createServer({ root: this.rootDir });
+ const port = await WebServer.getFreePort();
+ server.listen(port, async () => {
+ try {
+ const childProcess = await WebServer.openUrl(`http://127.0.0.1:${port}${relativePath}`);
+ childProcess.on('close', () => {
+ server.close(() => {
+ resolve();
+ });
+ });
+ }
+ catch (error) {
+ reject(error);
+ }
+ });
+ }
+ catch (error) {
+ reject(error);
+ }
+ });
+ }
+ static async getFreePort() {
+ const { default: getPort } = await import('get-port');
+ return getPort();
+ }
+ static async openUrl(url) {
+ const { default: open } = await import('open');
+ return open(url, { wait: true });
+ }
+}
+
+const rootDir = path__namespace.join(__dirname, '..', '..');
+const webServer = new WebServer(rootDir);
+(async () => {
+ await webServer.start('/dist/editor/index.html');
+})();
+//# sourceMappingURL=srt-editor.js.map
diff --git a/dist/script/srt-editor.js.map b/dist/script/srt-editor.js.map
new file mode 100644
index 0000000..9dbd323
--- /dev/null
+++ b/dist/script/srt-editor.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"srt-editor.js","sources":["../../src/common/web-server.ts","../../src/script/srt-editor.ts"],"sourcesContent":["import {createServer} from 'http-server';\n\nexport class WebServer {\n constructor(private readonly rootDir: string) {\n }\n\n public async start(relativePath = '') {\n return new Promise(async (resolve, reject) => {\n try {\n const server = createServer({ root: this.rootDir });\n const port = await WebServer.getFreePort();\n\n server.listen(port, async () => {\n try {\n const childProcess = await WebServer.openUrl(`http://127.0.0.1:${port}${relativePath}`);\n\n childProcess.on('close', () => {\n server.close(() => {\n resolve();\n });\n });\n } catch (error) {\n reject(error);\n }\n });\n } catch (error) {\n reject(error);\n }\n });\n }\n\n private static async getFreePort(): Promise {\n const { default: getPort } = await import('get-port');\n return getPort();\n }\n\n private static async openUrl(url: string) {\n const { default: open } = await import('open');\n return open(url, { wait: true });\n }\n}","import * as path from 'path';\nimport {WebServer} from '../common/web-server';\n\nconst rootDir = path.join(__dirname, '..', '..');\nconst webServer = new WebServer(rootDir);\n\n(async () => {\n await webServer.start('/dist/editor/index.html');\n})();"],"names":["createServer","path"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;MAEa,SAAS,CAAA;AACW,IAAA,OAAA;AAA7B,IAAA,WAAA,CAA6B,OAAe,EAAA;QAAf,IAAO,CAAA,OAAA,GAAP,OAAO;;AAG7B,IAAA,MAAM,KAAK,CAAC,YAAY,GAAG,EAAE,EAAA;QAChC,OAAO,IAAI,OAAO,CAAO,OAAO,OAAO,EAAE,MAAM,KAAI;AAC/C,YAAA,IAAI;AACA,gBAAA,MAAM,MAAM,GAAGA,uBAAY,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;AACnD,gBAAA,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,WAAW,EAAE;AAE1C,gBAAA,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAW;AAC3B,oBAAA,IAAI;AACA,wBAAA,MAAM,YAAY,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAA,iBAAA,EAAoB,IAAI,CAAA,EAAG,YAAY,CAAA,CAAE,CAAC;AAEvF,wBAAA,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAK;AAC1B,4BAAA,MAAM,CAAC,KAAK,CAAC,MAAK;AACd,gCAAA,OAAO,EAAE;AACb,6BAAC,CAAC;AACN,yBAAC,CAAC;;oBACJ,OAAO,KAAK,EAAE;wBACZ,MAAM,CAAC,KAAK,CAAC;;AAErB,iBAAC,CAAC;;YACJ,OAAO,KAAK,EAAE;gBACZ,MAAM,CAAC,KAAK,CAAC;;AAErB,SAAC,CAAC;;IAGE,aAAa,WAAW,GAAA;QAC5B,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,OAAO,UAAU,CAAC;QACrD,OAAO,OAAO,EAAE;;AAGZ,IAAA,aAAa,OAAO,CAAC,GAAW,EAAA;QACpC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,MAAM,CAAC;QAC9C,OAAO,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;;AAEvC;;ACrCD,MAAM,OAAO,GAAGC,eAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC;AAChD,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,OAAO,CAAC;AAExC,CAAC,YAAW;AACR,IAAA,MAAM,SAAS,CAAC,KAAK,CAAC,yBAAyB,CAAC;AACpD,CAAC,GAAG;;"}
\ No newline at end of file
diff --git a/dist/web/index.js.map b/dist/web/index.js.map
index 6b2136e..0bf5f81 100644
--- a/dist/web/index.js.map
+++ b/dist/web/index.js.map
@@ -1 +1 @@
-{"version":3,"file":"index.js","sources":["../../src/web/player.ts","../../src/web/components/player.component.vue","../../src/common/timecodes.ts","../../src/script/dynamic-css-rules.ts","../../src/web/css-processor.ts","../../src/web/caption-renderer.ts","../../src/web/index.ts"],"sourcesContent":["import {Caption} from '../common/caption';\nimport {CaptionRenderer} from './caption-renderer';\nimport {CssProcessor} from './css-processor';\n\nexport class Player {\n public onStop = () => {};\n\n private readonly captionsContainer: HTMLDivElement;\n private readonly rendered: HTMLDivElement[] = [];\n private timeoutIds: NodeJS.Timeout[] = [];\n private displayedCaptionId = 0;\n\n constructor(private readonly videoElem: HTMLElement,\n private readonly captions: Caption[],\n private readonly cssProcessor: CssProcessor,\n renderer: CaptionRenderer) {\n this.captionsContainer = this.videoElem.querySelector('.captions')!;\n\n for (const caption of captions) {\n this.rendered[caption.index] = renderer.renderCaption(caption);\n }\n }\n\n public play() {\n this.rendered.forEach(captionElem => captionElem.remove());\n\n if (this.captions.length === 0) {\n return;\n }\n\n for (let i = 0; i < this.captions.length; i++) {\n const caption = this.captions[i];\n const displayTimeoutId = setTimeout(() => {\n this.displayCaption(caption.index);\n }, caption.startTimeMs);\n\n let hideTimeoutId: NodeJS.Timeout;\n if (i < this.captions.length - 1) {\n hideTimeoutId = setTimeout(() => {\n this.hideCaption(caption.index);\n }, caption.endTimeMs);\n } else {\n hideTimeoutId = setTimeout(() => {\n this.hideCaption(caption.index);\n this.stop();\n }, caption.endTimeMs);\n }\n\n this.timeoutIds.push(displayTimeoutId, hideTimeoutId);\n }\n }\n\n public stop() {\n if (this.displayedCaptionId) {\n this.rendered[this.displayedCaptionId].remove();\n this.displayedCaptionId = 0;\n }\n\n while (this.timeoutIds.length) {\n clearTimeout(this.timeoutIds.pop());\n }\n\n this.onStop();\n }\n\n public prec() {\n if (!this.isBeginning) {\n let precId = this.displayedCaptionId - 1;\n if (precId) {\n this.displayCaption(precId);\n } else {\n this.hideCaption(this.displayedCaptionId)\n }\n }\n }\n\n public next() {\n if (!this.isEnd) {\n this.displayCaption(this.displayedCaptionId + 1);\n }\n }\n\n public get isBeginning(): boolean {\n return this.displayedCaptionId === 0;\n }\n\n public get isEnd(): boolean {\n return this.displayedCaptionId === this.captions.length\n }\n\n private displayCaption(index: number) {\n if (this.displayedCaptionId === index) {\n return; // Displayed already, do nothing\n }\n\n if (this.displayedCaptionId) {\n this.rendered[this.displayedCaptionId].remove();\n }\n\n this.dynamicallyStyleContainers(index);\n\n this.captionsContainer.appendChild(this.rendered[index]);\n this.displayedCaptionId = index;\n }\n\n private dynamicallyStyleContainers(index: number) {\n this.videoElem.setAttribute('class', '');\n this.captionsContainer.setAttribute('class', 'captions');\n\n const caption = this.captions[index - 1];\n const captionWords = caption.words.map(word => word.rawWord);\n this.cssProcessor.applyDynamicClasses(this.videoElem, index, caption.startTimeMs, captionWords);\n this.cssProcessor.applyDynamicClasses(this.captionsContainer, index, caption.startTimeMs, captionWords);\n }\n\n private hideCaption(index: number) {\n if (this.displayedCaptionId != index) {\n return; // Removed already, do nothing\n }\n\n this.rendered[index].remove();\n this.displayedCaptionId = 0;\n }\n}","\n\n\n \n \n
\n \n \n \n \n Prec \n \n
\n
\n \n \n \n \n \n Play \n \n
\n
\n \n \n \n \n Next \n \n
\n
\n \n ","export function toMillis(timecodes: string): number {\n const parts = timecodes.split(/[:,]/).map(Number);\n\n const hours = parts[0];\n const minutes = parts[1];\n const seconds = parts[2];\n const milliseconds = parts[3];\n\n return hours * 3_600_000 // hours to millis\n + minutes * 60_000 // minutes to millis\n + seconds * 1000 // second to millis\n + milliseconds;\n}","import {toMillis} from '../common/timecodes';\n\nexport type FilterType = 'indexes' | 'timecodes' | 'words';\n\nexport interface Filter {\n cssClass: string;\n type: FilterType;\n args: string[];\n}\n\nexport function normalizeTimecode(timecode: string): string {\n const [ hh, mm, ss, ms ] = timecode.split(/[^\\d]+/);\n return `${hh}:${mm}:${ss}.${ms}`;\n}\n\nexport abstract class AbstractDynamicCssRule {\n protected constructor(protected readonly targetSelectors: string[],\n public readonly appliedCssClass: string) {\n }\n\n public isApplied(target: HTMLElement, captionIndex: number, timeMs: number, words: string[]): boolean {\n let targetClasses = target.getAttribute('class')?.split(' ') || [];\n\n for (const targetSelector of this.targetSelectors) {\n if (targetSelector.startsWith('#')) {\n const idSelector = targetSelector.slice(1);\n if (target.getAttribute('id') != idSelector) {\n return false;\n }\n } else if (targetSelector.startsWith('.')) {\n const classSelector = targetSelector.slice(1);\n if (!targetClasses.includes(classSelector)) {\n return false;\n }\n } else {\n throw new Error(`Unsupported target selector: '${targetSelector}'`);\n }\n }\n\n return true;\n }\n}\n\nexport class IndexesDynamicCssRule extends AbstractDynamicCssRule {\n constructor(targetSelectors: string[],\n appliedCssClass: string,\n private readonly startIndexInclusive: number,\n private readonly endIndexInclusive?: number) {\n super(targetSelectors, appliedCssClass);\n }\n\n isApplied(target: HTMLElement, captionIndex: number, timeMs: number, words: string[]): boolean {\n return super.isApplied(target, captionIndex, timeMs, words)\n && this.startIndexInclusive <= captionIndex\n && (this.endIndexInclusive ? this.endIndexInclusive >= captionIndex : true);\n }\n}\n\nexport class TimecodesDynamicCssRule extends AbstractDynamicCssRule {\n constructor(targetSelectors: string[],\n appliedCssClass: string,\n private readonly startTimeMsInclusive: number,\n private readonly endTimeMsInclusive?: number) {\n super(targetSelectors, appliedCssClass);\n }\n\n isApplied(target: HTMLElement, captionIndex: number, timeMs: number, words: string[]): boolean {\n return super.isApplied(target, captionIndex, timeMs, words)\n && this.startTimeMsInclusive <= timeMs\n && (this.endTimeMsInclusive ? this.endTimeMsInclusive >= timeMs : true);\n }\n}\n\nexport function createDynamicCssRule(targetSelectors: string[], filter: Filter): AbstractDynamicCssRule {\n switch (filter.type) {\n case 'indexes':\n const [ startIndex, endIndex ] = filter.args.map(arg => Number(arg));\n return new IndexesDynamicCssRule(targetSelectors, filter.cssClass, startIndex, endIndex);\n case 'timecodes':\n const [ startMs, endMs ] = filter.args.map(normalizeTimecode).map(toMillis);\n return new TimecodesDynamicCssRule(targetSelectors, filter.cssClass, startMs, endMs);\n default:\n throw new Error(`Unknown filter type '${filter.type}'!`);\n }\n}","import {AbstractDynamicCssRule, createDynamicCssRule, Filter, FilterType} from '../script/dynamic-css-rules';\n\nconst dynamicCssClassPattern = /^\\.pup-(\\w+)((?:-[^-]+)+)$/;\n\nexport class CssProcessor {\n private readonly dynamicCssRules: AbstractDynamicCssRule[] = [];\n\n constructor() {\n for (const styleSheet of document.styleSheets) {\n for (const styleRule of styleSheet.cssRules) {\n const selectorText = (styleRule as CSSStyleRule).selectorText || '';\n\n if (selectorText.includes('.pup-')) {\n const selectors = CssProcessor.parseSelectors(selectorText);\n const targetSelectors: string[] = [];\n let filter: Filter | null = null;\n\n for (const selector of selectors) {\n if (selector.match(dynamicCssClassPattern)) {\n if (filter) {\n throw new Error(\n `Only one dynamic CSS class is allowed per style rule. \n Two dynamic classes were found: .${filter.cssClass} and ${selector}`);\n }\n\n filter = CssProcessor.parseFilter(selector);\n } else {\n targetSelectors.push(selector);\n }\n }\n\n const rule = createDynamicCssRule(targetSelectors, filter!);\n this.dynamicCssRules.push(rule);\n }\n }\n }\n }\n\n public applyDynamicClasses(target: T, captionIndex: number, timeMs: number, words: string[]): T {\n const dynamicCssClasses = this.dynamicCssClasses(target, captionIndex, timeMs, words);\n let cssClass = target.getAttribute('class') || '';\n dynamicCssClasses.forEach(dynamicClass => cssClass += ' ' + dynamicClass);\n target.setAttribute('class', cssClass);\n return target;\n }\n\n private dynamicCssClasses(target: HTMLElement, captionIndex: number, timeMs: number, words: string[]): string[] {\n return this.dynamicCssRules\n .filter(rule => rule.isApplied(target, captionIndex, timeMs, words))\n .map(rule => rule.appliedCssClass);\n }\n\n static parseFilter(dynamicCssClass: string): Filter {\n const match = dynamicCssClass.match(dynamicCssClassPattern);\n\n if (!match) {\n throw new Error(`CSS class ${dynamicCssClass} do not match required pattern!`);\n }\n\n const cssClass = dynamicCssClass.slice(1);\n const filterType = match[1] as FilterType;\n const filterArgs = match[2].split('-').slice(1);\n\n return {\n cssClass,\n type: filterType,\n args: filterArgs,\n }\n }\n\n static parseSelectors(selectorText: string): string[] {\n const selectors: string[] = [];\n let currentToken = '';\n let lastIsEscaped = false;\n\n for (let i = 0; i < selectorText.length; i++) {\n const char = selectorText[i];\n if ((char === '.' || char === '#') && !lastIsEscaped) {\n if (currentToken) {\n selectors.push(currentToken);\n }\n currentToken = char;\n } else if (char === '\\\\') {\n currentToken += char;\n lastIsEscaped = true;\n } else if (lastIsEscaped) {\n currentToken += char;\n lastIsEscaped = false;\n } else {\n currentToken += char;\n }\n }\n\n if (currentToken) {\n selectors.push(currentToken);\n }\n\n return selectors;\n }\n}\n","import {Caption, Word} from '../common/caption';\nimport {CssProcessor} from './css-processor';\n\nexport class CaptionRenderer {\n public constructor(private readonly cssProcessor: CssProcessor) {\n }\n\n public renderCaption(caption: Caption): HTMLDivElement {\n const captionDiv = document.createElement('div');\n captionDiv.setAttribute('id', `caption_${caption.index}`);\n captionDiv.setAttribute('class', 'caption');\n\n caption.words\n .map(word => this.renderWord(word, caption))\n .forEach(spanElem => captionDiv.appendChild(spanElem));\n\n const captionWords = caption.words.map(word => word.rawWord);\n return this.cssProcessor.applyDynamicClasses(captionDiv, caption.index, caption.startTimeMs, captionWords);\n }\n\n private renderWord(word: Word, caption: Caption): HTMLSpanElement {\n const wordSpan = document.createElement('span');\n wordSpan.textContent = word.rawWord;\n\n let cssClass = 'word'\n if (word.isHighlighted) {\n cssClass += ' ' + (word.highlightClass || 'highlighted');\n } if (word.isBeforeHighlighted) {\n cssClass += ' before-highlighted';\n } if (word.isAfterHighlighted) {\n cssClass += ' after-highlighted';\n }\n\n wordSpan.setAttribute('class', cssClass);\n\n return this.cssProcessor.applyDynamicClasses(wordSpan, caption.index, caption.startTimeMs, [ word.rawWord ]);\n }\n}","import {createApp} from 'vue';\nimport {Player} from './player';\nimport PlayerComponent from './components/player.component.vue';\nimport {CssProcessor} from './css-processor';\nimport {CaptionRenderer} from './caption-renderer';\n\nwindow.ready = new Promise((resolve, reject) => {\n window.onload = () => {\n const videoElem = document.getElementById('video');\n const cssProcessor = new CssProcessor();\n const renderer = new CaptionRenderer(cssProcessor);\n window.Player = new Player(videoElem!, window.captions, cssProcessor, renderer);\n\n if (window.playerArgs.isPreview) {\n createApp({})\n .component('player', PlayerComponent)\n .mount('#player-controller');\n }\n\n resolve();\n };\n});\n"],"names":["reactive","createApp","PlayerComponent"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"index.js","sources":["../../src/web/player.ts","../../src/web/components/player.component.vue","../../src/common/timecodes.ts","../../src/script/dynamic-css-rules.ts","../../src/web/css-processor.ts","../../src/web/caption-renderer.ts","../../src/web/index.ts"],"sourcesContent":["import {Caption} from '../common/captions';\nimport {CaptionRenderer} from './caption-renderer';\nimport {CssProcessor} from './css-processor';\n\nexport class Player {\n public onStop = () => {};\n\n private readonly captionsContainer: HTMLDivElement;\n private readonly rendered: HTMLDivElement[] = [];\n private timeoutIds: NodeJS.Timeout[] = [];\n private displayedCaptionId = 0;\n\n constructor(private readonly videoElem: HTMLElement,\n private readonly captions: Caption[],\n private readonly cssProcessor: CssProcessor,\n renderer: CaptionRenderer) {\n this.captionsContainer = this.videoElem.querySelector('.captions')!;\n\n for (const caption of captions) {\n this.rendered[caption.index] = renderer.renderCaption(caption);\n }\n }\n\n public play() {\n this.rendered.forEach(captionElem => captionElem.remove());\n\n if (this.captions.length === 0) {\n return;\n }\n\n for (let i = 0; i < this.captions.length; i++) {\n const caption = this.captions[i];\n const displayTimeoutId = setTimeout(() => {\n this.displayCaption(caption.index);\n }, caption.startTimeMs);\n\n let hideTimeoutId: NodeJS.Timeout;\n if (i < this.captions.length - 1) {\n hideTimeoutId = setTimeout(() => {\n this.hideCaption(caption.index);\n }, caption.endTimeMs);\n } else {\n hideTimeoutId = setTimeout(() => {\n this.hideCaption(caption.index);\n this.stop();\n }, caption.endTimeMs);\n }\n\n this.timeoutIds.push(displayTimeoutId, hideTimeoutId);\n }\n }\n\n public stop() {\n if (this.displayedCaptionId) {\n this.rendered[this.displayedCaptionId].remove();\n this.displayedCaptionId = 0;\n }\n\n while (this.timeoutIds.length) {\n clearTimeout(this.timeoutIds.pop());\n }\n\n this.onStop();\n }\n\n public prec() {\n if (!this.isBeginning) {\n let precId = this.displayedCaptionId - 1;\n if (precId) {\n this.displayCaption(precId);\n } else {\n this.hideCaption(this.displayedCaptionId)\n }\n }\n }\n\n public next() {\n if (!this.isEnd) {\n this.displayCaption(this.displayedCaptionId + 1);\n }\n }\n\n public get isBeginning(): boolean {\n return this.displayedCaptionId === 0;\n }\n\n public get isEnd(): boolean {\n return this.displayedCaptionId === this.captions.length\n }\n\n private displayCaption(index: number) {\n if (this.displayedCaptionId === index) {\n return; // Displayed already, do nothing\n }\n\n if (this.displayedCaptionId) {\n this.rendered[this.displayedCaptionId].remove();\n }\n\n this.dynamicallyStyleContainers(index);\n\n this.captionsContainer.appendChild(this.rendered[index]);\n this.displayedCaptionId = index;\n }\n\n private dynamicallyStyleContainers(index: number) {\n this.videoElem.setAttribute('class', '');\n this.captionsContainer.setAttribute('class', 'captions');\n\n const caption = this.captions[index - 1];\n const captionWords = caption.words.map(word => word.rawWord);\n this.cssProcessor.applyDynamicClasses(this.videoElem, index, caption.startTimeMs, captionWords);\n this.cssProcessor.applyDynamicClasses(this.captionsContainer, index, caption.startTimeMs, captionWords);\n }\n\n private hideCaption(index: number) {\n if (this.displayedCaptionId != index) {\n return; // Removed already, do nothing\n }\n\n this.rendered[index].remove();\n this.displayedCaptionId = 0;\n }\n}","\n\n\n \n \n
\n \n \n \n \n Prec \n \n
\n
\n \n \n \n \n \n Play \n \n
\n
\n \n \n \n \n Next \n \n
\n
\n \n ","export class Timecode {\n public readonly hours: number;\n public readonly minutes: number;\n public readonly seconds: number;\n public readonly millis: number;\n\n constructor(millis: number) {\n this.millis = millis % 1000;\n\n this.hours = Math.floor(millis / 3_600_000);\n const remainingMillisAfterHours = millis % 3_600_000;\n this.minutes = Math.floor(remainingMillisAfterHours / 60_000);\n const remainingMillisAfterMinutes = remainingMillisAfterHours % 60_000;\n this.seconds = Math.floor(remainingMillisAfterMinutes / 1000);\n }\n\n public get hh(): string {\n return String(this.hours).padStart(2, '0');\n }\n\n public get mm(): string {\n return String(this.minutes).padStart(2, '0');\n }\n\n public get ss(): string {\n return String(this.seconds).padStart(2, '0');\n }\n public get SSS(): string {\n return String(this.millis).padStart(3, '0');\n }\n\n public get asString(): string {\n return `${this.hh}:${this.mm}:${this.ss},${this.SSS}`;\n }\n}\n\nexport function toMillis(timecodes: string): number {\n const parts = timecodes.split(/[:,]/).map(Number);\n\n const hours = parts[0];\n const minutes = parts[1];\n const seconds = parts[2];\n const milliseconds = parts[3];\n\n return hours * 3_600_000 // hours to millis\n + minutes * 60_000 // minutes to millis\n + seconds * 1000 // second to millis\n + milliseconds;\n}","import {toMillis} from '../common/timecodes';\n\nexport type FilterType = 'indexes' | 'timecodes' | 'words';\n\nexport interface Filter {\n cssClass: string;\n type: FilterType;\n args: string[];\n}\n\nexport function normalizeTimecode(timecode: string): string {\n const [ hh, mm, ss, ms ] = timecode.split(/[^\\d]+/);\n return `${hh}:${mm}:${ss}.${ms}`;\n}\n\nexport abstract class AbstractDynamicCssRule {\n protected constructor(protected readonly targetSelectors: string[],\n public readonly appliedCssClass: string) {\n }\n\n public isApplied(target: HTMLElement, captionIndex: number, timeMs: number, words: string[]): boolean {\n let targetClasses = target.getAttribute('class')?.split(' ') || [];\n\n for (const targetSelector of this.targetSelectors) {\n if (targetSelector.startsWith('#')) {\n const idSelector = targetSelector.slice(1);\n if (target.getAttribute('id') != idSelector) {\n return false;\n }\n } else if (targetSelector.startsWith('.')) {\n const classSelector = targetSelector.slice(1);\n if (!targetClasses.includes(classSelector)) {\n return false;\n }\n } else {\n throw new Error(`Unsupported target selector: '${targetSelector}'`);\n }\n }\n\n return true;\n }\n}\n\nexport class IndexesDynamicCssRule extends AbstractDynamicCssRule {\n constructor(targetSelectors: string[],\n appliedCssClass: string,\n private readonly startIndexInclusive: number,\n private readonly endIndexInclusive?: number) {\n super(targetSelectors, appliedCssClass);\n }\n\n isApplied(target: HTMLElement, captionIndex: number, timeMs: number, words: string[]): boolean {\n return super.isApplied(target, captionIndex, timeMs, words)\n && this.startIndexInclusive <= captionIndex\n && (this.endIndexInclusive ? this.endIndexInclusive >= captionIndex : true);\n }\n}\n\nexport class TimecodesDynamicCssRule extends AbstractDynamicCssRule {\n constructor(targetSelectors: string[],\n appliedCssClass: string,\n private readonly startTimeMsInclusive: number,\n private readonly endTimeMsInclusive?: number) {\n super(targetSelectors, appliedCssClass);\n }\n\n isApplied(target: HTMLElement, captionIndex: number, timeMs: number, words: string[]): boolean {\n return super.isApplied(target, captionIndex, timeMs, words)\n && this.startTimeMsInclusive <= timeMs\n && (this.endTimeMsInclusive ? this.endTimeMsInclusive >= timeMs : true);\n }\n}\n\nexport function createDynamicCssRule(targetSelectors: string[], filter: Filter): AbstractDynamicCssRule {\n switch (filter.type) {\n case 'indexes':\n const [ startIndex, endIndex ] = filter.args.map(arg => Number(arg));\n return new IndexesDynamicCssRule(targetSelectors, filter.cssClass, startIndex, endIndex);\n case 'timecodes':\n const [ startMs, endMs ] = filter.args.map(normalizeTimecode).map(toMillis);\n return new TimecodesDynamicCssRule(targetSelectors, filter.cssClass, startMs, endMs);\n default:\n throw new Error(`Unknown filter type '${filter.type}'!`);\n }\n}","import {AbstractDynamicCssRule, createDynamicCssRule, Filter, FilterType} from '../script/dynamic-css-rules';\n\nconst dynamicCssClassPattern = /^\\.pup-(\\w+)((?:-[^-]+)+)$/;\n\nexport class CssProcessor {\n private readonly dynamicCssRules: AbstractDynamicCssRule[] = [];\n\n constructor() {\n for (const styleSheet of document.styleSheets) {\n for (const styleRule of styleSheet.cssRules) {\n const selectorText = (styleRule as CSSStyleRule).selectorText || '';\n\n if (selectorText.includes('.pup-')) {\n const selectors = CssProcessor.parseSelectors(selectorText);\n const targetSelectors: string[] = [];\n let filter: Filter | null = null;\n\n for (const selector of selectors) {\n if (selector.match(dynamicCssClassPattern)) {\n if (filter) {\n throw new Error(\n `Only one dynamic CSS class is allowed per style rule. \n Two dynamic classes were found: .${filter.cssClass} and ${selector}`);\n }\n\n filter = CssProcessor.parseFilter(selector);\n } else {\n targetSelectors.push(selector);\n }\n }\n\n const rule = createDynamicCssRule(targetSelectors, filter!);\n this.dynamicCssRules.push(rule);\n }\n }\n }\n }\n\n public applyDynamicClasses(target: T, captionIndex: number, timeMs: number, words: string[]): T {\n const dynamicCssClasses = this.dynamicCssClasses(target, captionIndex, timeMs, words);\n let cssClass = target.getAttribute('class') || '';\n dynamicCssClasses.forEach(dynamicClass => cssClass += ' ' + dynamicClass);\n target.setAttribute('class', cssClass);\n return target;\n }\n\n private dynamicCssClasses(target: HTMLElement, captionIndex: number, timeMs: number, words: string[]): string[] {\n return this.dynamicCssRules\n .filter(rule => rule.isApplied(target, captionIndex, timeMs, words))\n .map(rule => rule.appliedCssClass);\n }\n\n static parseFilter(dynamicCssClass: string): Filter {\n const match = dynamicCssClass.match(dynamicCssClassPattern);\n\n if (!match) {\n throw new Error(`CSS class ${dynamicCssClass} do not match required pattern!`);\n }\n\n const cssClass = dynamicCssClass.slice(1);\n const filterType = match[1] as FilterType;\n const filterArgs = match[2].split('-').slice(1);\n\n return {\n cssClass,\n type: filterType,\n args: filterArgs,\n }\n }\n\n static parseSelectors(selectorText: string): string[] {\n const selectors: string[] = [];\n let currentToken = '';\n let lastIsEscaped = false;\n\n for (let i = 0; i < selectorText.length; i++) {\n const char = selectorText[i];\n if ((char === '.' || char === '#') && !lastIsEscaped) {\n if (currentToken) {\n selectors.push(currentToken);\n }\n currentToken = char;\n } else if (char === '\\\\') {\n currentToken += char;\n lastIsEscaped = true;\n } else if (lastIsEscaped) {\n currentToken += char;\n lastIsEscaped = false;\n } else {\n currentToken += char;\n }\n }\n\n if (currentToken) {\n selectors.push(currentToken);\n }\n\n return selectors;\n }\n}\n","import {Caption, Word} from '../common/captions';\nimport {CssProcessor} from './css-processor';\n\nexport class CaptionRenderer {\n public constructor(private readonly cssProcessor: CssProcessor) {\n }\n\n public renderCaption(caption: Caption): HTMLDivElement {\n const captionDiv = document.createElement('div');\n captionDiv.setAttribute('id', `caption_${caption.index}`);\n captionDiv.setAttribute('class', 'caption');\n\n caption.words\n .map(word => this.renderWord(word, caption))\n .forEach(spanElem => captionDiv.appendChild(spanElem));\n\n const captionWords = caption.words.map(word => word.rawWord);\n return this.cssProcessor.applyDynamicClasses(captionDiv, caption.index, caption.startTimeMs, captionWords);\n }\n\n private renderWord(word: Word, caption: Caption): HTMLSpanElement {\n const wordSpan = document.createElement('span');\n wordSpan.textContent = word.rawWord;\n\n let cssClass = 'word'\n if (word.isHighlighted) {\n cssClass += ' ' + (word.highlightClass || 'highlighted');\n } if (word.isBeforeHighlighted) {\n cssClass += ' before-highlighted';\n } if (word.isAfterHighlighted) {\n cssClass += ' after-highlighted';\n }\n\n wordSpan.setAttribute('class', cssClass);\n\n return this.cssProcessor.applyDynamicClasses(wordSpan, caption.index, caption.startTimeMs, [ word.rawWord ]);\n }\n}","import {createApp} from 'vue';\nimport {Player} from './player';\nimport PlayerComponent from './components/player.component.vue';\nimport {CssProcessor} from './css-processor';\nimport {CaptionRenderer} from './caption-renderer';\n\nwindow.ready = new Promise((resolve, reject) => {\n window.onload = () => {\n const videoElem = document.getElementById('video');\n const cssProcessor = new CssProcessor();\n const renderer = new CaptionRenderer(cssProcessor);\n window.Player = new Player(videoElem!, window.captions, cssProcessor, renderer);\n\n if (window.playerArgs.isPreview) {\n createApp({})\n .component('player', PlayerComponent)\n .mount('#player-controller');\n }\n\n resolve();\n };\n});\n"],"names":["reactive","createApp","PlayerComponent"],"mappings":";;;UAIa,MAAM,CAAA;IAQc,IAAA,SAAA;IACA,IAAA,QAAA;IACA,IAAA,YAAA;IATtB,IAAA,MAAM,GAAG,MAAK,GAAG;IAEP,IAAA,iBAAiB;QACjB,QAAQ,GAAqB,EAAE;QACxC,UAAU,GAAqB,EAAE;QACjC,kBAAkB,GAAG,CAAC;IAE9B,IAAA,WAAA,CAA6B,SAAsB,EACtB,QAAmB,EACnB,YAA0B,EAC3C,QAAyB,EAAA;YAHR,IAAS,CAAA,SAAA,GAAT,SAAS;YACT,IAAQ,CAAA,QAAA,GAAR,QAAQ;YACR,IAAY,CAAA,YAAA,GAAZ,YAAY;YAErC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,WAAW,CAAE;IAEnE,QAAA,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE;IAC5B,YAAA,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC;;;QAI/D,IAAI,GAAA;IACP,QAAA,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;YAE1D,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE;gBAC5B;;IAGJ,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;gBAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChC,YAAA,MAAM,gBAAgB,GAAG,UAAU,CAAC,MAAK;IACrC,gBAAA,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC;IACtC,aAAC,EAAE,OAAO,CAAC,WAAW,CAAC;IAEvB,YAAA,IAAI,aAA6B;gBACjC,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE;IAC9B,gBAAA,aAAa,GAAG,UAAU,CAAC,MAAK;IAC5B,oBAAA,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;IACnC,iBAAC,EAAE,OAAO,CAAC,SAAS,CAAC;;qBAClB;IACH,gBAAA,aAAa,GAAG,UAAU,CAAC,MAAK;IAC5B,oBAAA,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;wBAC/B,IAAI,CAAC,IAAI,EAAE;IACf,iBAAC,EAAE,OAAO,CAAC,SAAS,CAAC;;gBAGzB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,gBAAgB,EAAE,aAAa,CAAC;;;QAItD,IAAI,GAAA;IACP,QAAA,IAAI,IAAI,CAAC,kBAAkB,EAAE;gBACzB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,MAAM,EAAE;IAC/C,YAAA,IAAI,CAAC,kBAAkB,GAAG,CAAC;;IAG/B,QAAA,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE;gBAC3B,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC;;YAGvC,IAAI,CAAC,MAAM,EAAE;;QAGV,IAAI,GAAA;IACP,QAAA,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;IACnB,YAAA,IAAI,MAAM,GAAG,IAAI,CAAC,kBAAkB,GAAG,CAAC;gBACxC,IAAI,MAAM,EAAE;IACR,gBAAA,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC;;qBACxB;IACH,gBAAA,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,kBAAkB,CAAC;;;;QAK9C,IAAI,GAAA;IACP,QAAA,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;gBACb,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,kBAAkB,GAAG,CAAC,CAAC;;;IAIxD,IAAA,IAAW,WAAW,GAAA;IAClB,QAAA,OAAO,IAAI,CAAC,kBAAkB,KAAK,CAAC;;IAGxC,IAAA,IAAW,KAAK,GAAA;YACZ,OAAO,IAAI,CAAC,kBAAkB,KAAK,IAAI,CAAC,QAAQ,CAAC,MAAM;;IAGnD,IAAA,cAAc,CAAC,KAAa,EAAA;IAChC,QAAA,IAAI,IAAI,CAAC,kBAAkB,KAAK,KAAK,EAAE;IACnC,YAAA,OAAO;;IAGX,QAAA,IAAI,IAAI,CAAC,kBAAkB,EAAE;gBACzB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,MAAM,EAAE;;IAGnD,QAAA,IAAI,CAAC,0BAA0B,CAAC,KAAK,CAAC;IAEtC,QAAA,IAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxD,QAAA,IAAI,CAAC,kBAAkB,GAAG,KAAK;;IAG3B,IAAA,0BAA0B,CAAC,KAAa,EAAA;YAC5C,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC;YACxC,IAAI,CAAC,iBAAiB,CAAC,YAAY,CAAC,OAAO,EAAE,UAAU,CAAC;YAExD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC;IACxC,QAAA,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC;IAC5D,QAAA,IAAI,CAAC,YAAY,CAAC,mBAAmB,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,YAAY,CAAC;IAC/F,QAAA,IAAI,CAAC,YAAY,CAAC,mBAAmB,CAAC,IAAI,CAAC,iBAAiB,EAAE,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,YAAY,CAAC;;IAGnG,IAAA,WAAW,CAAC,KAAa,EAAA;IAC7B,QAAA,IAAI,IAAI,CAAC,kBAAkB,IAAI,KAAK,EAAE;IAClC,YAAA,OAAO;;YAGX,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE;IAC7B,QAAA,IAAI,CAAC,kBAAkB,GAAG,CAAC;;IAElC;;;;;;;;;;;;;;;;YCxHD,MAAM,WAAW,GAAGA,YAAQ,CAAC;IAC3B,YAAA,SAAS,EAAE,KAAK;IAChB,YAAA,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW;IACtC,YAAA,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK;IAC3B,SAAA,CAAC;IAEF,QAAA,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,MAAK;IAC1B,YAAA,WAAW,CAAC,SAAS,GAAG,KAAK;IAC/B,SAAC;IAED,QAAA,SAAS,IAAI,GAAA;IACX,YAAA,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;IACpB,YAAA,WAAW,EAAE;;IAGf,QAAA,SAAS,IAAI,GAAA;IACX,YAAA,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;IACpB,YAAA,WAAW,EAAE;;IAGf,QAAA,SAAS,UAAU,GAAA;IACjB,YAAA,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE;IAC1B,gBAAA,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;IACpB,gBAAA,WAAW,CAAC,SAAS,GAAG,IAAI;;qBACvB;IACL,gBAAA,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;;;IAIxB,QAAA,SAAS,WAAW,GAAA;gBAClB,WAAW,CAAC,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW;gBACnD,WAAW,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ICEnC,SAAU,QAAQ,CAAC,SAAiB,EAAA;IACtC,IAAA,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;IAEjD,IAAA,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;IACtB,IAAA,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC;IACxB,IAAA,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC;IACxB,IAAA,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC;IAE7B,IAAA,OAAO,KAAK,GAAG,SAAS;cAClB,OAAO,GAAG,MAAM;cAChB,OAAO,GAAG,IAAI;IACd,UAAA,YAAY;IACtB;;ICtCM,SAAU,iBAAiB,CAAC,QAAgB,EAAA;IAC9C,IAAA,MAAM,CAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC;QACnD,OAAO,CAAA,EAAG,EAAE,CAAI,CAAA,EAAA,EAAE,IAAI,EAAE,CAAA,CAAA,EAAI,EAAE,CAAA,CAAE;IACpC;UAEsB,sBAAsB,CAAA;IACC,IAAA,eAAA;IACH,IAAA,eAAA;QADtC,WAAyC,CAAA,eAAyB,EAC5B,eAAuB,EAAA;YADpB,IAAe,CAAA,eAAA,GAAf,eAAe;YAClB,IAAe,CAAA,eAAA,GAAf,eAAe;;IAG9C,IAAA,SAAS,CAAC,MAAmB,EAAE,YAAoB,EAAE,MAAc,EAAE,KAAe,EAAA;IACvF,QAAA,IAAI,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE;IAElE,QAAA,KAAK,MAAM,cAAc,IAAI,IAAI,CAAC,eAAe,EAAE;IAC/C,YAAA,IAAI,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;oBAChC,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;oBAC1C,IAAI,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,UAAU,EAAE;IACzC,oBAAA,OAAO,KAAK;;;IAEb,iBAAA,IAAI,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;oBACvC,MAAM,aAAa,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;oBAC7C,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE;IACxC,oBAAA,OAAO,KAAK;;;qBAEb;IACH,gBAAA,MAAM,IAAI,KAAK,CAAC,iCAAiC,cAAc,CAAA,CAAA,CAAG,CAAC;;;IAI3E,QAAA,OAAO,IAAI;;IAElB;IAEK,MAAO,qBAAsB,SAAQ,sBAAsB,CAAA;IAGhC,IAAA,mBAAA;IACA,IAAA,iBAAA;IAH7B,IAAA,WAAA,CAAY,eAAyB,EACzB,eAAuB,EACN,mBAA2B,EAC3B,iBAA0B,EAAA;IACnD,QAAA,KAAK,CAAC,eAAe,EAAE,eAAe,CAAC;YAFd,IAAmB,CAAA,mBAAA,GAAnB,mBAAmB;YACnB,IAAiB,CAAA,iBAAA,GAAjB,iBAAiB;;IAI9C,IAAA,SAAS,CAAC,MAAmB,EAAE,YAAoB,EAAE,MAAc,EAAE,KAAe,EAAA;YAChF,OAAO,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK;mBACnD,IAAI,CAAC,mBAAmB,IAAI;IAC5B,gBAAC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,IAAI,YAAY,GAAG,IAAI,CAAC;;IAEtF;IAEK,MAAO,uBAAwB,SAAQ,sBAAsB,CAAA;IAGlC,IAAA,oBAAA;IACA,IAAA,kBAAA;IAH7B,IAAA,WAAA,CAAY,eAAyB,EACzB,eAAuB,EACN,oBAA4B,EAC5B,kBAA2B,EAAA;IACpD,QAAA,KAAK,CAAC,eAAe,EAAE,eAAe,CAAC;YAFd,IAAoB,CAAA,oBAAA,GAApB,oBAAoB;YACpB,IAAkB,CAAA,kBAAA,GAAlB,kBAAkB;;IAI/C,IAAA,SAAS,CAAC,MAAmB,EAAE,YAAoB,EAAE,MAAc,EAAE,KAAe,EAAA;YAChF,OAAO,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK;mBACnD,IAAI,CAAC,oBAAoB,IAAI;IAC7B,gBAAC,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,kBAAkB,IAAI,MAAM,GAAG,IAAI,CAAC;;IAElF;IAEe,SAAA,oBAAoB,CAAC,eAAyB,EAAE,MAAc,EAAA;IAC1E,IAAA,QAAQ,MAAM,CAAC,IAAI;IACf,QAAA,KAAK,SAAS;gBACV,MAAM,CAAE,UAAU,EAAE,QAAQ,CAAE,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC;IACpE,YAAA,OAAO,IAAI,qBAAqB,CAAC,eAAe,EAAE,MAAM,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC;IAC5F,QAAA,KAAK,WAAW;IACZ,YAAA,MAAM,CAAE,OAAO,EAAE,KAAK,CAAE,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC3E,YAAA,OAAO,IAAI,uBAAuB,CAAC,eAAe,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC;IACxF,QAAA;gBACI,MAAM,IAAI,KAAK,CAAC,CAAA,qBAAA,EAAwB,MAAM,CAAC,IAAI,CAAI,EAAA,CAAA,CAAC;;IAEpE;;IClFA,MAAM,sBAAsB,GAAG,4BAA4B;UAE9C,YAAY,CAAA;QACJ,eAAe,GAA6B,EAAE;IAE/D,IAAA,WAAA,GAAA;IACI,QAAA,KAAK,MAAM,UAAU,IAAI,QAAQ,CAAC,WAAW,EAAE;IAC3C,YAAA,KAAK,MAAM,SAAS,IAAI,UAAU,CAAC,QAAQ,EAAE;IACzC,gBAAA,MAAM,YAAY,GAAI,SAA0B,CAAC,YAAY,IAAI,EAAE;IAEnE,gBAAA,IAAI,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;wBAChC,MAAM,SAAS,GAAG,YAAY,CAAC,cAAc,CAAC,YAAY,CAAC;wBAC3D,MAAM,eAAe,GAAa,EAAE;wBACpC,IAAI,MAAM,GAAkB,IAAI;IAEhC,oBAAA,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE;IAC9B,wBAAA,IAAI,QAAQ,CAAC,KAAK,CAAC,sBAAsB,CAAC,EAAE;gCACxC,IAAI,MAAM,EAAE;oCACR,MAAM,IAAI,KAAK,CACX,CAAA;AACmC,qEAAA,EAAA,MAAM,CAAC,QAAQ,CAAA,KAAA,EAAQ,QAAQ,CAAA,CAAE,CAAC;;IAG7E,4BAAA,MAAM,GAAG,YAAY,CAAC,WAAW,CAAC,QAAQ,CAAC;;iCACxC;IACH,4BAAA,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC;;;wBAItC,MAAM,IAAI,GAAG,oBAAoB,CAAC,eAAe,EAAE,MAAO,CAAC;IAC3D,oBAAA,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;IAMxC,IAAA,mBAAmB,CAAwB,MAAS,EAAE,YAAoB,EAAE,MAAc,EAAE,KAAe,EAAA;IAC9G,QAAA,MAAM,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC;YACrF,IAAI,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE;IACjD,QAAA,iBAAiB,CAAC,OAAO,CAAC,YAAY,IAAI,QAAQ,IAAI,GAAG,GAAG,YAAY,CAAC;IACzE,QAAA,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC;IACtC,QAAA,OAAO,MAAM;;IAGT,IAAA,iBAAiB,CAAC,MAAmB,EAAE,YAAoB,EAAE,MAAc,EAAE,KAAe,EAAA;YAChG,OAAO,IAAI,CAAC;IACP,aAAA,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC;iBAClE,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,eAAe,CAAC;;QAG1C,OAAO,WAAW,CAAC,eAAuB,EAAA;YACtC,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,sBAAsB,CAAC;YAE3D,IAAI,CAAC,KAAK,EAAE;IACR,YAAA,MAAM,IAAI,KAAK,CAAC,aAAa,eAAe,CAAA,+BAAA,CAAiC,CAAC;;YAGlF,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;IACzC,QAAA,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAe;IACzC,QAAA,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;YAE/C,OAAO;gBACH,QAAQ;IACR,YAAA,IAAI,EAAE,UAAU;IAChB,YAAA,IAAI,EAAE,UAAU;aACnB;;QAGL,OAAO,cAAc,CAAC,YAAoB,EAAA;YACtC,MAAM,SAAS,GAAa,EAAE;YAC9B,IAAI,YAAY,GAAG,EAAE;YACrB,IAAI,aAAa,GAAG,KAAK;IAEzB,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;IAC1C,YAAA,MAAM,IAAI,GAAG,YAAY,CAAC,CAAC,CAAC;IAC5B,YAAA,IAAI,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,KAAK,CAAC,aAAa,EAAE;oBAClD,IAAI,YAAY,EAAE;IACd,oBAAA,SAAS,CAAC,IAAI,CAAC,YAAY,CAAC;;oBAEhC,YAAY,GAAG,IAAI;;IAChB,iBAAA,IAAI,IAAI,KAAK,IAAI,EAAE;oBACtB,YAAY,IAAI,IAAI;oBACpB,aAAa,GAAG,IAAI;;qBACjB,IAAI,aAAa,EAAE;oBACtB,YAAY,IAAI,IAAI;oBACpB,aAAa,GAAG,KAAK;;qBAClB;oBACH,YAAY,IAAI,IAAI;;;YAI5B,IAAI,YAAY,EAAE;IACd,YAAA,SAAS,CAAC,IAAI,CAAC,YAAY,CAAC;;IAGhC,QAAA,OAAO,SAAS;;IAEvB;;UChGY,eAAe,CAAA;IACY,IAAA,YAAA;IAApC,IAAA,WAAA,CAAoC,YAA0B,EAAA;YAA1B,IAAY,CAAA,YAAA,GAAZ,YAAY;;IAGzC,IAAA,aAAa,CAAC,OAAgB,EAAA;YACjC,MAAM,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC;YAChD,UAAU,CAAC,YAAY,CAAC,IAAI,EAAE,CAAW,QAAA,EAAA,OAAO,CAAC,KAAK,CAAE,CAAA,CAAC;IACzD,QAAA,UAAU,CAAC,YAAY,CAAC,OAAO,EAAE,SAAS,CAAC;IAE3C,QAAA,OAAO,CAAC;IACH,aAAA,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC;IAC1C,aAAA,OAAO,CAAC,QAAQ,IAAI,UAAU,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAE1D,QAAA,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC;IAC5D,QAAA,OAAO,IAAI,CAAC,YAAY,CAAC,mBAAmB,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,YAAY,CAAC;;QAGtG,UAAU,CAAC,IAAU,EAAE,OAAgB,EAAA;YAC3C,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC;IAC/C,QAAA,QAAQ,CAAC,WAAW,GAAG,IAAI,CAAC,OAAO;YAEnC,IAAI,QAAQ,GAAG,MAAM;IACrB,QAAA,IAAI,IAAI,CAAC,aAAa,EAAE;gBACpB,QAAQ,IAAI,GAAG,IAAI,IAAI,CAAC,cAAc,IAAI,aAAa,CAAC;;IAC1D,QAAA,IAAI,IAAI,CAAC,mBAAmB,EAAE;gBAC5B,QAAQ,IAAI,qBAAqB;;IACnC,QAAA,IAAI,IAAI,CAAC,kBAAkB,EAAE;gBAC3B,QAAQ,IAAI,oBAAoB;;IAGpC,QAAA,QAAQ,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC;YAExC,OAAO,IAAI,CAAC,YAAY,CAAC,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,CAAE,IAAI,CAAC,OAAO,CAAE,CAAC;;IAEnH;;IC/BD,MAAM,CAAC,KAAK,GAAG,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,KAAI;IAC3C,IAAA,MAAM,CAAC,MAAM,GAAG,MAAK;YACjB,MAAM,SAAS,GAAG,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC;IAClD,QAAA,MAAM,YAAY,GAAG,IAAI,YAAY,EAAE;IACvC,QAAA,MAAM,QAAQ,GAAG,IAAI,eAAe,CAAC,YAAY,CAAC;IAClD,QAAA,MAAM,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC,SAAU,EAAE,MAAM,CAAC,QAAQ,EAAE,YAAY,EAAE,QAAQ,CAAC;IAE/E,QAAA,IAAI,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE;gBAC7BC,aAAS,CAAC,EAAE;IACP,iBAAA,SAAS,CAAC,QAAQ,EAAEC,MAAe;qBACnC,KAAK,CAAC,oBAAoB,CAAC;;IAGpC,QAAA,OAAO,EAAE;IACb,KAAC;IACL,CAAC,CAAC;;;;;;"}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 2c239f9..d7dc284 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"bulma": "^1.0.2",
"cli-progress": "^3.12.0",
"commander": "^12.1.0",
+ "file-saver": "^2.0.5",
"fluent-ffmpeg": "^2.1.3",
"get-port": "^7.1.0",
"http-server": "^14.1.1",
@@ -32,6 +33,7 @@
"@rollup/plugin-node-resolve": "^15.3.0",
"@types/chai": "^5.0.1",
"@types/cli-progress": "^3.11.6",
+ "@types/file-saver": "^2.0.7",
"@types/fluent-ffmpeg": "^2.1.27",
"@types/http-server": "^0.12.4",
"@types/mocha": "^10.0.10",
@@ -42,6 +44,7 @@
"chai": "^5.1.2",
"mocha": "^10.8.2",
"rollup": "^4.27.3",
+ "rollup-plugin-copy": "^3.5.0",
"rollup-plugin-typescript2": "^0.36.0",
"rollup-plugin-vue": "^6.0.0",
"tsx": "^4.19.2",
@@ -671,6 +674,44 @@
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"license": "MIT"
},
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/@puppeteer/browsers": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.1.tgz",
@@ -1205,6 +1246,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/file-saver": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz",
+ "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/fluent-ffmpeg": {
"version": "2.1.27",
"resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.27.tgz",
@@ -1215,6 +1263,27 @@
"@types/node": "*"
}
},
+ "node_modules/@types/fs-extra": {
+ "version": "8.1.5",
+ "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz",
+ "integrity": "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/minimatch": "*",
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/http-server": {
"version": "0.12.4",
"resolved": "https://registry.npmjs.org/@types/http-server/-/http-server-0.12.4.tgz",
@@ -1225,6 +1294,13 @@
"@types/connect": "*"
}
},
+ "node_modules/@types/minimatch": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
+ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/mocha": {
"version": "10.0.10",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz",
@@ -1511,6 +1587,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
@@ -2008,6 +2094,13 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
+ "node_modules/colorette": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
+ "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -2037,6 +2130,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@@ -2290,6 +2390,19 @@
"node": ">=0.3.1"
}
},
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/domexception": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
@@ -2561,6 +2674,23 @@
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
"license": "MIT"
},
+ "node_modules/fast-glob": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -2575,6 +2705,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fastq": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+ "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
@@ -2584,6 +2724,12 @@
"pend": "~1.2.0"
}
},
+ "node_modules/file-saver": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+ "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
+ "license": "MIT"
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -2869,6 +3015,72 @@
"node": ">= 6"
}
},
+ "node_modules/globby": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz",
+ "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/glob": "^7.1.1",
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.0.3",
+ "glob": "^7.1.3",
+ "ignore": "^5.1.1",
+ "merge2": "^1.2.3",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/globby/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/globby/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/globby/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/gopd": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.1.0.tgz",
@@ -3118,6 +3330,16 @@
],
"license": "BSD-3-Clause"
},
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -3293,6 +3515,16 @@
"node": ">=8"
}
},
+ "node_modules/is-plain-object": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz",
+ "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-reference": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
@@ -3658,6 +3890,30 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -4119,6 +4375,16 @@
"node": ">=8"
}
},
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -4126,6 +4392,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/pathval": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
@@ -4386,6 +4662,27 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/queue-tick": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
@@ -4545,6 +4842,17 @@
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/rollup": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.3.tgz",
@@ -4583,6 +4891,58 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/rollup-plugin-copy": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.5.0.tgz",
+ "integrity": "sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/fs-extra": "^8.0.1",
+ "colorette": "^1.1.0",
+ "fs-extra": "^8.1.0",
+ "globby": "10.0.1",
+ "is-plain-object": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8.3"
+ }
+ },
+ "node_modules/rollup-plugin-copy/node_modules/fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/rollup-plugin-copy/node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "license": "MIT",
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/rollup-plugin-copy/node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
"node_modules/rollup-plugin-typescript2": {
"version": "0.36.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.36.0.tgz",
@@ -4660,6 +5020,30 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -4763,6 +5147,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
diff --git a/package.json b/package.json
index 62fb92c..e20d8a1 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"bulma": "^1.0.2",
"cli-progress": "^3.12.0",
"commander": "^12.1.0",
+ "file-saver": "^2.0.5",
"fluent-ffmpeg": "^2.1.3",
"get-port": "^7.1.0",
"http-server": "^14.1.1",
@@ -46,6 +47,7 @@
"@rollup/plugin-node-resolve": "^15.3.0",
"@types/chai": "^5.0.1",
"@types/cli-progress": "^3.11.6",
+ "@types/file-saver": "^2.0.7",
"@types/fluent-ffmpeg": "^2.1.27",
"@types/http-server": "^0.12.4",
"@types/mocha": "^10.0.10",
@@ -56,6 +58,7 @@
"chai": "^5.1.2",
"mocha": "^10.8.2",
"rollup": "^4.27.3",
+ "rollup-plugin-copy": "^3.5.0",
"rollup-plugin-typescript2": "^0.36.0",
"rollup-plugin-vue": "^6.0.0",
"tsx": "^4.19.2",
diff --git a/rollup.config.mjs b/rollup.config.mjs
index d4dcd62..2b09b58 100644
--- a/rollup.config.mjs
+++ b/rollup.config.mjs
@@ -3,6 +3,7 @@ import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import vue from 'rollup-plugin-vue';
+import copy from 'rollup-plugin-copy';
export default [{
input: 'src/web/index.ts',
@@ -19,10 +20,36 @@ export default [{
tsconfig: './src/web/tsconfig.json',
}),
vue(),
+ resolve({
+ extensions: ['.ts', '.vue'],
+ }),
+ ],
+ external: ['vue'],
+}, {
+ input: 'src/editor/index.ts',
+ output: {
+ file: 'dist/editor/index.js',
+ format: 'iife',
+ sourcemap: true,
+ globals: {
+ vue: 'Vue',
+ },
+ },
+ plugins: [
+ commonjs(),
+ typescript({
+ tsconfig: './src/editor/tsconfig.json',
+ }),
+ vue(),
resolve({
extensions: [ '.ts', '.vue' ],
}),
- ],
+ copy({
+ targets: [
+ { src: 'src/editor/index.html', dest: 'dist/editor' }
+ ]
+ }),
+ ],
external: ['vue'],
}, {
input: 'src/script/index.ts',
@@ -46,4 +73,23 @@ export default [{
'commander', 'tmp', 'fluent-ffmpeg', 'pngjs', 'puppeteer', 'cli-progress', 'open', 'http-server', 'get-port',
'@ffmpeg-installer/ffmpeg',
],
+}, {
+ input: 'src/script/srt-editor.ts',
+ output: {
+ file: 'dist/script/srt-editor.js',
+ format: 'cjs',
+ sourcemap: true,
+ },
+ plugins: [
+ resolve({
+ extensions: ['.ts'],
+ }),
+ typescript({
+ tsconfig: './src/script/tsconfig.json',
+ }),
+ ],
+ external: [
+ 'fs', 'path', 'os', 'stream', 'util', // Node.js built-in modules
+ 'open', 'http-server', 'get-port',
+ ],
}];
diff --git a/src/common/caption.ts b/src/common/caption.ts
deleted file mode 100644
index db7b42c..0000000
--- a/src/common/caption.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-export interface Word {
- rawWord: string;
- isHighlighted: boolean;
- isBeforeHighlighted: boolean;
- isAfterHighlighted: boolean;
- highlightClass?: string;
-}
-
-export interface Caption {
- index: number;
- startTimeMs: number;
- endTimeMs: number;
- words: Word[];
-}
\ No newline at end of file
diff --git a/src/script/srt-captions-reader.ts b/src/common/captions.ts
similarity index 63%
rename from src/script/srt-captions-reader.ts
rename to src/common/captions.ts
index d1630b1..63b58bb 100644
--- a/src/script/srt-captions-reader.ts
+++ b/src/common/captions.ts
@@ -1,17 +1,39 @@
-import {readFileSync} from 'fs';
-import {Caption, Word} from '../common/caption';
-import {toMillis} from '../common/timecodes';
+import {toMillis} from './timecodes';
const indexLinePattern = /^\d+$/;
const timecodesLinePattern = /^(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})$/;
const highlightedWordPattern = /^\[(.+)](?:\((\w+)\))?$/;
-export function parseCaptions(srtCaptionsFile: string): Caption[] {
- const captionsSrc = readFileSync(srtCaptionsFile, 'utf-8');
- return readCaptions(captionsSrc);
+export interface Word {
+ rawWord: string;
+ isHighlighted: boolean;
+ isBeforeHighlighted: boolean;
+ isAfterHighlighted: boolean;
+ highlightClass?: string;
}
-function readCaptions(srtContent: string): Caption[] {
+export interface Caption {
+ index: number;
+ startTimeMs: number;
+ endTimeMs: number;
+ words: Word[];
+}
+
+export function haveSameWords(caption1: Caption, caption2: Caption): boolean {
+ if (caption1.words.length != caption2.words.length) {
+ return false;
+ }
+
+ for (let i =0; i < caption1.words.length; i++) {
+ if (caption1.words[i].rawWord != caption2.words[i].rawWord) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+export function readCaptions(srtContent: string): Caption[] {
const lines = srtContent.split('\n');
const captions: Caption[] = [];
@@ -77,7 +99,7 @@ export function readWords(text: string): Word[] {
return res;
}
-function splitText(text: string): string[] {
+export function splitText(text: string): string[] {
const words: string[] = [];
let currentWord = '';
@@ -86,18 +108,28 @@ function splitText(text: string): string[] {
for (let i = 0; i < text.length; i++) {
const char = text[i];
const isWhitespace = /^\s$/.test(char);
+ const isPunctuation = /[,.!?]/.test(char);
if (!isWhitespace) {
- currentWord += char;
- switch (char) {
- case '[':
- case '(':
- isCurrentHighlighted = true;
- break;
- case ']':
- case ')':
- isCurrentHighlighted = false;
- break;
+ if (!isPunctuation) {
+ currentWord += char;
+ switch (char) {
+ case '[':
+ case '(':
+ isCurrentHighlighted = true;
+ break;
+ case ']':
+ case ')':
+ isCurrentHighlighted = false;
+ break;
+ }
+ } else {
+ if (currentWord) {
+ currentWord += char;
+ } else {
+ // Attach punctuation mark to the previous word
+ words[words.length - 1] += ' ' + char;
+ }
}
} else {
// char is a whitespace
diff --git a/src/common/timecodes.ts b/src/common/timecodes.ts
index c992079..748f564 100644
--- a/src/common/timecodes.ts
+++ b/src/common/timecodes.ts
@@ -1,3 +1,39 @@
+export class Timecode {
+ public readonly hours: number;
+ public readonly minutes: number;
+ public readonly seconds: number;
+ public readonly millis: number;
+
+ constructor(millis: number) {
+ this.millis = millis % 1000;
+
+ this.hours = Math.floor(millis / 3_600_000);
+ const remainingMillisAfterHours = millis % 3_600_000;
+ this.minutes = Math.floor(remainingMillisAfterHours / 60_000);
+ const remainingMillisAfterMinutes = remainingMillisAfterHours % 60_000;
+ this.seconds = Math.floor(remainingMillisAfterMinutes / 1000);
+ }
+
+ public get hh(): string {
+ return String(this.hours).padStart(2, '0');
+ }
+
+ public get mm(): string {
+ return String(this.minutes).padStart(2, '0');
+ }
+
+ public get ss(): string {
+ return String(this.seconds).padStart(2, '0');
+ }
+ public get SSS(): string {
+ return String(this.millis).padStart(3, '0');
+ }
+
+ public get asString(): string {
+ return `${this.hh}:${this.mm}:${this.ss},${this.SSS}`;
+ }
+}
+
export function toMillis(timecodes: string): number {
const parts = timecodes.split(/[:,]/).map(Number);
diff --git a/src/script/preview-server.ts b/src/common/web-server.ts
similarity index 70%
rename from src/script/preview-server.ts
rename to src/common/web-server.ts
index 1183ae5..6dc3a2b 100644
--- a/src/script/preview-server.ts
+++ b/src/common/web-server.ts
@@ -1,19 +1,18 @@
import {createServer} from 'http-server';
-import {WorkDir} from './work-dir';
-export class PreviewServer {
- constructor(private readonly wordDir: WorkDir) {
+export class WebServer {
+ constructor(private readonly rootDir: string) {
}
- public async start() {
+ public async start(relativePath = '') {
return new Promise(async (resolve, reject) => {
try {
- const server = createServer({ root: this.wordDir.rootDir });
- const port = await PreviewServer.getFreePort();
+ const server = createServer({ root: this.rootDir });
+ const port = await WebServer.getFreePort();
server.listen(port, async () => {
try {
- const childProcess = await PreviewServer.openUrl(`http://127.0.0.1:${port}`);
+ const childProcess = await WebServer.openUrl(`http://127.0.0.1:${port}${relativePath}`);
childProcess.on('close', () => {
server.close(() => {
diff --git a/src/editor/captions.service.ts b/src/editor/captions.service.ts
new file mode 100644
index 0000000..b61f42b
--- /dev/null
+++ b/src/editor/captions.service.ts
@@ -0,0 +1,161 @@
+import {Caption, haveSameWords} from '../common/captions';
+import {Timecode} from '../common/timecodes';
+
+export interface KaraokeWord {
+ rawWord: string;
+ startTimeMs: number;
+ endTimeMs: number;
+}
+
+export class KaraokeGroup {
+ constructor(public indexStart: number,
+ public indexEnd: number,
+ public readonly words: KaraokeWord[]) {
+ }
+
+ static fromCaptions(captions: Caption[]): KaraokeGroup {
+ const indexStart = captions[0].index;
+ const indexEnd = captions[captions.length - 1].index;
+ const words: KaraokeWord[] = captions
+ .map(caption => {
+ const highlightedWord = caption.words.filter(word => word.isHighlighted)[0].rawWord;
+
+ return {
+ rawWord: highlightedWord,
+ startTimeMs: caption.startTimeMs,
+ endTimeMs: caption.endTimeMs,
+ };
+ })
+
+ return new KaraokeGroup(indexStart, indexEnd, words);
+ }
+
+ public addAtBeginning(word: KaraokeWord) {
+ this.indexStart--;
+ this.words.unshift(word);
+ }
+
+ public removeFromBeginning(): KaraokeWord {
+ this.indexStart++;
+ return this.words.splice(0, 1)[0];
+ }
+
+ public addAtEnd(word: KaraokeWord) {
+ this.indexEnd++;
+ this.words.push(word);
+ }
+
+ public removeFromEnd(): KaraokeWord {
+ this.indexEnd--;
+ return this.words.pop()!;
+ }
+
+ public get id() {
+ return `${this.indexStart}-${this.indexEnd}`;
+ }
+
+ public get startTimeMs(): number {
+ return this.words[0].startTimeMs;
+ }
+
+ public get endTimeMs(): number {
+ return this.words[this.words.length - 1].endTimeMs;
+ }
+
+ public get isEmpty(): boolean {
+ return this.words.length === 0;
+ }
+}
+
+export class CaptionsService {
+ private groups: KaraokeGroup[] = [];
+
+ public readCaptions(captions: Caption[]) {
+ this.groups = [];
+
+ let lastCaption: Caption | null = null;
+ let lastGroup = [];
+
+ for (const caption of captions) {
+ if (lastCaption && !haveSameWords(caption, lastCaption!)) {
+ const karaokeGroup = KaraokeGroup.fromCaptions(lastGroup);
+ this.groups.push(karaokeGroup);
+ lastGroup = [];
+ }
+
+ lastGroup.push(caption);
+ lastCaption = caption;
+ }
+
+ if (lastGroup.length) {
+ const karaokeGroup = KaraokeGroup.fromCaptions(lastGroup);
+ this.groups.push(karaokeGroup);
+ }
+
+ console.dir(this.groups, {depth: null});
+ }
+
+ public moveFirstWordToPrecedentGroup(groupId: number) {
+ const karaokeGroup = this.groups[groupId];
+ const firstWord = karaokeGroup.removeFromBeginning();
+
+ if (groupId > 0) {
+ this.groups[groupId - 1].addAtEnd(firstWord);
+ } else {
+ const index = karaokeGroup.indexStart - 1;
+ const newKaraokeGroup = new KaraokeGroup(index, index, [ firstWord ]);
+ this.groups.unshift(newKaraokeGroup);
+ }
+
+ if (karaokeGroup.isEmpty) {
+ this.groups.splice(groupId, 1);
+ }
+ }
+
+ public moveLastWordToNextGroup(groupId: number) {
+ const karaokeGroup = this.groups[groupId];
+ const lastWord = karaokeGroup.removeFromEnd();
+
+ if (groupId < this.groups.length - 1) {
+ this.groups[groupId + 1].addAtBeginning(lastWord);
+ } else {
+ const index = karaokeGroup.indexEnd + 1;
+ const newKaraokeGroup = new KaraokeGroup(index, index, [ lastWord ]);
+ this.groups.push(newKaraokeGroup);
+ }
+
+ if (karaokeGroup.isEmpty) {
+ this.groups.splice(groupId, 1);
+ }
+ }
+
+ public get karaokeGroups(): KaraokeGroup[] {
+ return [...this.groups];
+ }
+
+ public get asSrt(): string {
+ let srtText = '';
+
+ for (const group of this.groups) {
+ for (let i = 0; i < group.words.length; i++) {
+ const captionIndex = group.indexStart + i;
+ const highlightedWord = group.words[i];
+ const startTimecode = new Timecode(highlightedWord.startTimeMs).asString;
+ const endTimecode = new Timecode(highlightedWord.endTimeMs).asString;
+
+ let captionWords = '';
+
+ for (let j = 0; j < group.words.length; j++) {
+ const word = group.words[j];
+ captionWords += j === i
+ ? `[${word.rawWord}] `
+ : `${word.rawWord} `;
+ }
+
+ srtText += `${captionIndex}\n${startTimecode} --> ${endTimecode}\n${captionWords}\n\n`;
+ }
+ }
+
+ return srtText;
+ }
+}
\ No newline at end of file
diff --git a/src/editor/components/application.component.vue b/src/editor/components/application.component.vue
new file mode 100644
index 0000000..e45b521
--- /dev/null
+++ b/src/editor/components/application.component.vue
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/editor/components/cue.component.vue b/src/editor/components/cue.component.vue
new file mode 100644
index 0000000..f0f3dee
--- /dev/null
+++ b/src/editor/components/cue.component.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/editor/components/indexes.component.vue b/src/editor/components/indexes.component.vue
new file mode 100644
index 0000000..22c726b
--- /dev/null
+++ b/src/editor/components/indexes.component.vue
@@ -0,0 +1,18 @@
+
+
+
+ {{ start }}
+
+
+
+ {{ end }}
+
+
\ No newline at end of file
diff --git a/src/editor/components/srt-file-picker.component.vue b/src/editor/components/srt-file-picker.component.vue
new file mode 100644
index 0000000..6ff907f
--- /dev/null
+++ b/src/editor/components/srt-file-picker.component.vue
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+ Pick subs file
+
+ {{ selectedFile?.name }}
+
+
+
\ No newline at end of file
diff --git a/src/editor/components/subtitles-table.component.vue b/src/editor/components/subtitles-table.component.vue
new file mode 100644
index 0000000..dfdfe9a
--- /dev/null
+++ b/src/editor/components/subtitles-table.component.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+ Indexes
+ Start
+ End
+ Caption
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/editor/components/timecode.component.vue b/src/editor/components/timecode.component.vue
new file mode 100644
index 0000000..e77419a
--- /dev/null
+++ b/src/editor/components/timecode.component.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+ {{ (timecode as Timecode).hh }}
+
+ :
+
+ {{ (timecode as Timecode).mm }}
+
+ :
+
+ {{ (timecode as Timecode).ss }}
+
+ ,
+
+ {{ (timecode as Timecode).SSS }}
+
+
+
\ No newline at end of file
diff --git a/src/editor/components/words.component.vue b/src/editor/components/words.component.vue
new file mode 100644
index 0000000..4dc3426
--- /dev/null
+++ b/src/editor/components/words.component.vue
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+ {{ word.rawWord }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/editor/file-loader.ts b/src/editor/file-loader.ts
new file mode 100644
index 0000000..528e3b3
--- /dev/null
+++ b/src/editor/file-loader.ts
@@ -0,0 +1,15 @@
+export async function loadFile(file: File): Promise {
+ const reader = new FileReader();
+
+ return new Promise((resolve, reject) => {
+ reader.onload = (loadEvent) => {
+ resolve(loadEvent.target?.result as string);
+ };
+
+ reader.onerror = (err) => {
+ reject(err);
+ };
+
+ reader.readAsText(file);
+ });
+}
\ No newline at end of file
diff --git a/src/editor/index.html b/src/editor/index.html
new file mode 100644
index 0000000..86a8f19
--- /dev/null
+++ b/src/editor/index.html
@@ -0,0 +1,20 @@
+
+
+
+ PupCaps! - Subtitles Editor
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/editor/index.ts b/src/editor/index.ts
new file mode 100644
index 0000000..b6867fd
--- /dev/null
+++ b/src/editor/index.ts
@@ -0,0 +1,6 @@
+import {createApp} from 'vue';
+import Application from './components/application.component.vue';
+
+createApp({})
+ .component('application', Application)
+ .mount('#app');
\ No newline at end of file
diff --git a/src/editor/tsconfig.json b/src/editor/tsconfig.json
new file mode 100644
index 0000000..1fb6263
--- /dev/null
+++ b/src/editor/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "module": "esnext"
+ },
+ "include": [
+ "./**/*.ts",
+ "./**/*.d.ts",
+ "./**/*.vue",
+ "../types/**/*.d.ts"
+ ],
+ "exclude": [
+ ]
+}
\ No newline at end of file
diff --git a/src/script/index.ts b/src/script/index.ts
index 640f948..9fa717b 100644
--- a/src/script/index.ts
+++ b/src/script/index.ts
@@ -1,13 +1,18 @@
+import {readFileSync} from 'fs';
import {Args, createProgressBar, parseArgs, printArgs} from './cli';
-import {parseCaptions} from './srt-captions-reader';
import {WorkDir} from './work-dir';
import {StepRenderer} from './step-renderer';
-import {PreviewServer} from './preview-server';
import {RealTimeRecorder} from './real-time-recorder';
import {RealTimeRenderer} from './real-time-renderer';
import {StepRecorder} from './step-recorder';
import {AbstractRecorder} from './abstract-recorder';
-import {Caption} from '../common/caption';
+import {Caption, readCaptions} from '../common/captions';
+import {WebServer} from '../common/web-server';
+
+function parseCaptions(srtCaptionsFile: string): Caption[] {
+ const captionsSrc = readFileSync(srtCaptionsFile, 'utf-8');
+ return readCaptions(captionsSrc);
+}
function createRecorder(args: Args, captions: Caption[], workDir: WorkDir): AbstractRecorder {
if (args.css3Animations) {
@@ -34,7 +39,7 @@ const workDir = new WorkDir(captions, cliArgs);
await recorder.recordCaptionsVideo(indexHtml);
} else {
console.log('Launching preview server...');
- const previewServer = new PreviewServer(workDir);
+ const previewServer = new WebServer(workDir.rootDir);
await previewServer.start();
}
console.log('Done!');
diff --git a/src/script/srt-editor.ts b/src/script/srt-editor.ts
new file mode 100644
index 0000000..962963a
--- /dev/null
+++ b/src/script/srt-editor.ts
@@ -0,0 +1,9 @@
+import * as path from 'path';
+import {WebServer} from '../common/web-server';
+
+const rootDir = path.join(__dirname, '..', '..');
+const webServer = new WebServer(rootDir);
+
+(async () => {
+ await webServer.start('/dist/editor/index.html');
+})();
\ No newline at end of file
diff --git a/src/script/step-recorder.ts b/src/script/step-recorder.ts
index f574b1e..432e2fb 100644
--- a/src/script/step-recorder.ts
+++ b/src/script/step-recorder.ts
@@ -1,7 +1,7 @@
import * as puppeteer from 'puppeteer';
import * as cliProgress from 'cli-progress';
import {PNG, PNGWithMetadata} from 'pngjs';
-import {Caption} from '../common/caption';
+import {Caption} from '../common/captions';
import {StepRenderer} from './step-renderer';
import {Args} from './cli';
import {AbstractRecorder} from './abstract-recorder';
diff --git a/src/script/step-renderer.ts b/src/script/step-renderer.ts
index e3af2dd..0ca4224 100644
--- a/src/script/step-renderer.ts
+++ b/src/script/step-renderer.ts
@@ -3,7 +3,7 @@ import {PNG, PNGWithMetadata} from 'pngjs';
import * as path from 'path';
import {appendFileSync, writeFileSync} from 'fs';
import {WorkDir} from './work-dir';
-import {Caption} from '../common/caption';
+import {Caption} from '../common/captions';
import {StatsPrinter} from './stats-printer';
import {AbstractRenderer} from './abstract-renderer';
diff --git a/src/script/work-dir.ts b/src/script/work-dir.ts
index 4cfd433..183c1ca 100644
--- a/src/script/work-dir.ts
+++ b/src/script/work-dir.ts
@@ -1,7 +1,7 @@
import * as tmp from 'tmp';
import * as path from 'path';
import {writeFileSync, symlinkSync, rmSync, mkdirSync} from 'fs';
-import {Caption} from '../common/caption';
+import {Caption} from '../common/captions';
import {Args} from './cli';
import {indexHtml, indexJs, nodeModules} from './assets';
import {PlayerArgs} from '../common/player-args';
diff --git a/src/web/shims-vue.d.ts b/src/types/shims-vue.d.ts
similarity index 100%
rename from src/web/shims-vue.d.ts
rename to src/types/shims-vue.d.ts
diff --git a/src/types/window.d.ts b/src/types/window.d.ts
index 191245b..b36255b 100644
--- a/src/types/window.d.ts
+++ b/src/types/window.d.ts
@@ -1,4 +1,4 @@
-import {Caption} from '../common/caption';
+import {Caption} from '../common/captions';
import {Player} from '../web/player';
import {PlayerArgs} from '../common/player-args';
diff --git a/src/web/caption-renderer.ts b/src/web/caption-renderer.ts
index 4da2050..b1ee4c4 100644
--- a/src/web/caption-renderer.ts
+++ b/src/web/caption-renderer.ts
@@ -1,4 +1,4 @@
-import {Caption, Word} from '../common/caption';
+import {Caption, Word} from '../common/captions';
import {CssProcessor} from './css-processor';
export class CaptionRenderer {
diff --git a/src/web/player.ts b/src/web/player.ts
index 668e89e..9061009 100644
--- a/src/web/player.ts
+++ b/src/web/player.ts
@@ -1,4 +1,4 @@
-import {Caption} from '../common/caption';
+import {Caption} from '../common/captions';
import {CaptionRenderer} from './caption-renderer';
import {CssProcessor} from './css-processor';
diff --git a/srt-editor b/srt-editor
new file mode 100755
index 0000000..d002d33
--- /dev/null
+++ b/srt-editor
@@ -0,0 +1,6 @@
+#!/usr/bin/env node
+
+const path = require('path');
+const main = path.join(__dirname, 'dist', 'script', 'srt-editor.js');
+
+require(main);
\ No newline at end of file
diff --git a/test/script/srt-captions-reader.test.ts b/test/common/captions.test.ts
similarity index 77%
rename from test/script/srt-captions-reader.test.ts
rename to test/common/captions.test.ts
index a793c10..b97fb7a 100644
--- a/test/script/srt-captions-reader.test.ts
+++ b/test/common/captions.test.ts
@@ -1,5 +1,61 @@
import {expect} from 'chai';
-import {readWords} from '../../src/script/srt-captions-reader';
+import {Caption, haveSameWords, readWords, splitText} from '../../src/common/captions';
+
+it('haveSameWords', () => {
+ // Given
+ const cue1 = 'vous [avez] lancé en septembre';
+ const cue2 = 'vous avez lancé [en] septembre';
+ const cue3 = 'que [vous] recrutez';
+
+ const caption1: Caption = {
+ index: 1,
+ startTimeMs: 0,
+ endTimeMs: 0,
+ words: readWords(cue1),
+ };
+ const caption2: Caption = {
+ index: 2,
+ startTimeMs: 0,
+ endTimeMs: 0,
+ words: readWords(cue2),
+ };
+ const caption3: Caption = {
+ index: 2,
+ startTimeMs: 0,
+ endTimeMs: 0,
+ words: readWords(cue3),
+ };
+
+ // When
+ const sameWords1 = haveSameWords(caption1, caption2);
+ const sameWords2 = haveSameWords(caption1, caption3);
+
+ // Then
+ expect(sameWords1).to.be.true;
+ expect(sameWords2).to.be.false;
+});
+
+it('splitText', () => {
+ // Given
+ const text1 = 'votre [projet] Basalt !';
+ const text2 = 'votre projet [Basalt !]';
+
+ // When
+ const tokens1 = splitText(text1);
+ const tokens2 = splitText(text2);
+
+ // Then
+ expect(tokens1).to.be.deep.equals([
+ 'votre',
+ '[projet]',
+ 'Basalt !',
+ ]);
+ expect(tokens2).to.be.deep.equals([
+ 'votre',
+ 'projet',
+ '[Basalt !]',
+ ]);
+});
describe('readCaptions', () => {
it('when no highlighted', () => {
diff --git a/test/common/timecodes.test.ts b/test/common/timecodes.test.ts
index f16cad4..d9a3cc2 100644
--- a/test/common/timecodes.test.ts
+++ b/test/common/timecodes.test.ts
@@ -1,5 +1,21 @@
import {expect} from 'chai';
-import {toMillis} from '../../src/common/timecodes';
+import {Timecode, toMillis} from '../../src/common/timecodes';
+
+it('create Timecode', () => {
+ const timecode = new Timecode(6158678);
+
+ expect(timecode.hours).to.equals(1);
+ expect(timecode.minutes).to.equals(42);
+ expect(timecode.seconds).to.equals(38);
+ expect(timecode.millis).to.equals(678);
+
+ expect(timecode.hh).to.equals('01');
+ expect(timecode.mm).to.equals('42');
+ expect(timecode.ss).to.equals('38');
+ expect(timecode.SSS).to.equals('678');
+
+ expect(timecode.asString).to.equals('01:42:38,678');
+});
it('should parse timecodes to millis', () => {
const timecode = '01:42:38,678';
diff --git a/test/editor/captions.service.test.ts b/test/editor/captions.service.test.ts
new file mode 100644
index 0000000..5f13cb2
--- /dev/null
+++ b/test/editor/captions.service.test.ts
@@ -0,0 +1,96 @@
+import {expect} from 'chai';
+import {KaraokeGroup, KaraokeWord} from '../../src/editor/captions.service';
+
+describe('KaraokeGroup', () => {
+ let karaokeGroup: KaraokeGroup;
+
+ beforeEach(() => {
+ karaokeGroup = new KaraokeGroup(20, 22, [
+ { rawWord: 'que', startTimeMs: 7520, endTimeMs: 7680 },
+ { rawWord: 'vous', startTimeMs: 7680, endTimeMs: 7839 },
+ { rawWord: 'recrutez', startTimeMs: 7839, endTimeMs: 8340 },
+ ]);
+ });
+
+ it('initial state', () => {
+ expect(karaokeGroup.id).to.be.equals('20-22');
+ expect(karaokeGroup.indexStart).to.be.equals(20);
+ expect(karaokeGroup.indexEnd).to.be.equals(22);
+ expect(karaokeGroup.startTimeMs).to.be.equals(7520);
+ expect(karaokeGroup.endTimeMs).to.be.equals(8340);
+ expect(karaokeGroup.isEmpty).to.be.equals(false);
+ expect(karaokeGroup.words).to.have.length(3);
+ });
+
+ it('after addAtBeginning', () => {
+ // Given
+ const insertedWord: KaraokeWord = {
+ rawWord: 'toto',
+ startTimeMs: 7000,
+ endTimeMs: 7520,
+ };
+
+ // When
+ karaokeGroup.addAtBeginning(insertedWord);
+
+ // Then
+ expect(karaokeGroup.id).to.be.equals('19-22');
+ expect(karaokeGroup.indexStart).to.be.equals(19);
+ expect(karaokeGroup.indexEnd).to.be.equals(22);
+ expect(karaokeGroup.startTimeMs).to.be.equals(7000);
+ expect(karaokeGroup.endTimeMs).to.be.equals(8340);
+ expect(karaokeGroup.isEmpty).to.be.equals(false);
+ expect(karaokeGroup.words).to.have.length(4);
+ });
+
+ it('after removeFromBeginning', () => {
+ // When
+ const removedWord = karaokeGroup.removeFromBeginning();
+
+ // Then
+ expect(removedWord).to.deep.equals({ rawWord: 'que', startTimeMs: 7520, endTimeMs: 7680 });
+
+ expect(karaokeGroup.id).to.be.equals('21-22');
+ expect(karaokeGroup.indexStart).to.be.equals(21);
+ expect(karaokeGroup.indexEnd).to.be.equals(22);
+ expect(karaokeGroup.startTimeMs).to.be.equals(7680);
+ expect(karaokeGroup.endTimeMs).to.be.equals(8340);
+ expect(karaokeGroup.isEmpty).to.be.equals(false);
+ expect(karaokeGroup.words).to.have.length(2);
+ });
+
+ it('after addAtEnd', () => {
+ // Given
+ const insertedWord: KaraokeWord = {
+ rawWord: 'toto',
+ startTimeMs: 8340,
+ endTimeMs: 8500,
+ };
+
+ // When
+ karaokeGroup.addAtEnd(insertedWord);
+
+ // Then
+ expect(karaokeGroup.id).to.be.equals('20-23');
+ expect(karaokeGroup.indexStart).to.be.equals(20);
+ expect(karaokeGroup.indexEnd).to.be.equals(23);
+ expect(karaokeGroup.startTimeMs).to.be.equals(7520);
+ expect(karaokeGroup.endTimeMs).to.be.equals(8500);
+ expect(karaokeGroup.isEmpty).to.be.equals(false);
+ expect(karaokeGroup.words).to.have.length(4);
+ });
+
+ it('after removeFromEnd', () => {
+ // When
+ const removedWord = karaokeGroup.removeFromEnd();
+
+ // Then
+ expect(karaokeGroup.id).to.be.equals('20-21');
+ expect(karaokeGroup.indexStart).to.be.equals(20);
+ expect(karaokeGroup.indexEnd).to.be.equals(21);
+ expect(karaokeGroup.startTimeMs).to.be.equals(7520);
+ expect(karaokeGroup.endTimeMs).to.be.equals(7839);
+ expect(karaokeGroup.isEmpty).to.be.equals(false);
+ expect(karaokeGroup.words).to.have.length(2);
+ });
+});
\ No newline at end of file