UNPKG

pupcaps

Version:

PupCaps! : A script to add stylish captions to your videos.

669 lines (640 loc) 30.2 kB
(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 { // char 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(); } shiftIndices(shift) { this.indexStart += shift; this.indexEnd += shift; } 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); } } deleteKaraokeGroup(karaokeGroupId) { const groups = []; let shift = 0; for (const group of this.groups) { if (group.id === karaokeGroupId) { shift = group.indexStart - group.indexEnd - 1; } else { if (~shift) { group.shiftIndices(shift); } groups.push(group); } } this.groups = groups; } 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", "delete"], 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.createElementVNode("button", { class: "button is-small is-danger", onClick: _cache[0] || (_cache[0] = ($event) => { _ctx.$emit('delete', _ctx.karaokeGroup.id); }) }, _cache[3] || (_cache[3] = [ vue.createElementVNode("span", { class: "icon is-small" }, [ vue.createElementVNode("i", { class: "fas fa-times" }) ], -1 /* HOISTED */) ])) ]), 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[1] || (_cache[1] = ($event) => (_ctx.$emit('move-word-to-prec', $event))), onMoveWordToNext: _cache[2] || (_cache[2] = ($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; } function deleteKaraokeGroup(karaokeGroupId) { captionService.deleteKaraokeGroup(karaokeGroupId); 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", { style: { "width": "3%" } }), 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)), onDelete: ($event) => (deleteKaraokeGroup(karaokeGroup.id)) }, null, 8 /* PROPS */, ["karaoke-group", "onMoveWordToPrec", "onMoveWordToNext", "onDelete"])); }), 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