Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,9 @@
</li>
<li><export-json></export-json></li>
<li><import-json></import-json></li>
<li><export-youtube-vtt></export-youtube-vtt></li>
<li><export-youtube-xml></export-youtube-xml></li>
<li><label for="file-import-youtube-caption-dialog">Import YouTube captions</label></li>
<li><label for="file-import-deepgram-json-dialog">Import Deepgram JSON</label></li>
<li><label for="file-import-srt-dialog">Import SRT</label></li>
<li><label for="file-import-vtt-dialog">Import VTT</label></li>
Expand Down Expand Up @@ -1095,6 +1098,7 @@ <h3 class="font-bold text-lg">Caption Regeneration </h3>
<import-deepgram-json></import-deepgram-json>
<import-srt></import-srt>
<import-vtt></import-vtt>
<import-youtube-captions></import-youtube-captions>

<!-- comment out if localstorage not required -->

Expand Down Expand Up @@ -1173,6 +1177,7 @@ <h3 class="font-bold text-lg">Load from Local Storage</h3>

</script>

<script src="./js/youtube-caption-converter.js"></script>
<script src="./js/hyperaudio-lite-editor-export.js" type="module"> </script>
<!-- end of localstorage additions -->

Expand Down
171 changes: 171 additions & 0 deletions js/hyperaudio-lite-editor-export.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,56 @@ class ImportJson extends HTMLElement {

customElements.define('import-json', ImportJson);

class ExportYoutubeVtt extends HTMLElement {

constructor() {
super();
}

exportYoutubeVtt() {
const jsonData = getHyperaudioJsonForExport();
if (!jsonData) return;

downloadText(
window.hyperaudioJsonToYoutubeVtt(jsonData),
'youtube-captions.vtt',
'text/vtt'
);
}

connectedCallback() {
this.innerHTML = `<a>Export YouTube VTT</a>`;
this.addEventListener('click', this.exportYoutubeVtt);
}
}

customElements.define('export-youtube-vtt', ExportYoutubeVtt);

class ExportYoutubeXml extends HTMLElement {

constructor() {
super();
}

exportYoutubeXml() {
const jsonData = getHyperaudioJsonForExport();
if (!jsonData) return;

downloadText(
window.hyperaudioJsonToYoutubeTimedTextXml(jsonData),
'youtube-captions.xml',
'application/xml'
);
}

connectedCallback() {
this.innerHTML = `<a>Export YouTube XML</a>`;
this.addEventListener('click', this.exportYoutubeXml);
}
}

customElements.define('export-youtube-xml', ExportYoutubeXml);

class ImportDeepgramJson extends HTMLElement {

constructor() {
Expand Down Expand Up @@ -428,6 +478,127 @@ class ImportVtt extends HTMLElement {

customElements.define('import-vtt', ImportVtt);

class ImportYoutubeCaptions extends HTMLElement {

constructor() {
super();
}

clearYoutubeCaptionMediaUrl(event) {
event.preventDefault();
document.querySelector('#youtube-caption-media').value = "";
}

clearYoutubeCaptionFilePicker(event) {
event.preventDefault();
document.querySelector('#youtube-caption-media-file').value = "";
}

confirmYoutubeCaptions() {
const player = document.querySelector("#hyperplayer");
const hypertranscript = document.getElementById('hypertranscript');
const track = document.querySelector('#hyperplayer-vtt');

if (!player || !hypertranscript || !track) {
alert("Currently you can only import YouTube captions from the transcript view.");
return;
}

if (document.querySelector('#youtube-caption-media-file').value == ""){
player.src = document.querySelector('#youtube-caption-media').value;
} else {
const mediaFile = document.querySelector('[name=youtube-caption-media-file]').files[0];
const mediaReader = new FileReader();
mediaReader.readAsArrayBuffer(mediaFile);
mediaReader.addEventListener('load', () => {
mediaFile.arrayBuffer().then((arrayBuffer) => {
const blob = new Blob([new Uint8Array(arrayBuffer)], {type: mediaFile.type });
player.src = URL.createObjectURL(blob);
});
});
}

const file = document.querySelector('[name=youtube-caption-file]').files[0];
if (!file) {
alert("Please select a YouTube XML or VTT caption file.");
return;
}

const reader = new FileReader();
reader.addEventListener('load', (event) => {
const captionData = event.target.result;
const isXml = file.name.toLowerCase().endsWith('.xml') || captionData.trim().startsWith('<');
const html = isXml
? window.youtubeTimedTextXmlToHtml(captionData)
: window.youtubeVttToHtml(captionData);

hypertranscript.innerHTML = html;

const jsonData = htmlToJson(hypertranscript);
const youtubeVtt = window.hyperaudioJsonToYoutubeVtt(jsonData);
track.src = "data:text/vtt," + encodeURIComponent(youtubeVtt);

updateCaptionsFromTranscript = false;
populateCaptionEditorFromVtt(youtubeVtt);
document.dispatchEvent(new CustomEvent('hyperaudioInit'));
});

reader.readAsText(file);
}

connectedCallback() {
this.innerHTML = `
<div class="hidden-label-holder">
<label for="file-import-youtube-caption-dialog">Import YouTube Captions Dialog</label>
</div>
<input type="checkbox" id="file-import-youtube-caption-dialog" class="modal-toggle" />
<div class="modal">
<div class="modal-box">
<div class="flex flex-col gap-4 w-full">
<label id="close-modal" for="file-import-youtube-caption-dialog" class="btn btn-sm btn-circle absolute right-2 top-2">✕</label>
<h3 class="font-bold text-lg">Import YouTube Captions Dialog</h3>
<input id="youtube-caption-media" type="text" placeholder="Link to media" class="input input-bordered w-full max-w-xs" />
<span class="label-text">or use local media file</span>
<input id="youtube-caption-media-file" name="youtube-caption-media-file" type="file" class="file-input w-full max-w-xs" />
<span class="label-text">select YouTube XML or VTT file</span>
<input id="youtube-caption-file" name="youtube-caption-file" type="file" accept=".xml,.vtt,text/xml,text/vtt" class="file-input w-full max-w-xs" />
</div>
<div class="modal-action">
<label for="file-import-youtube-caption-dialog" class="btn btn-ghost">Cancel</label>
<label id="file-import-youtube-caption" for="file-import-youtube-caption-dialog" class="btn btn-primary">Confirm</label>
</div>
</div>
</div>`;

document.querySelector('#youtube-caption-media-file').addEventListener('change',this.clearYoutubeCaptionMediaUrl);
document.querySelector('#youtube-caption-media').addEventListener('change',this.clearYoutubeCaptionFilePicker);
document.querySelector('#file-import-youtube-caption').addEventListener('click',this.confirmYoutubeCaptions);
}
}

customElements.define('import-youtube-captions', ImportYoutubeCaptions);

function getHyperaudioJsonForExport() {
const hypertranscript = document.getElementById('hypertranscript');

if (hypertranscript === null) {
alert("Currently you can only export YouTube captions from the transcript view.");
return null;
}

return htmlToJson(hypertranscript);
}

function downloadText(textData, fileName, mimeType) {
const dataStr = `data:${mimeType};charset=utf-8,` + encodeURIComponent(textData);
const downloadAnchorNode = document.createElement('a');
downloadAnchorNode.setAttribute('href', dataStr);
downloadAnchorNode.setAttribute('download', fileName);
document.body.appendChild(downloadAnchorNode);
downloadAnchorNode.click();
downloadAnchorNode.remove();
}

function downloadJson(jsonData) {
// download json file
let dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(jsonData, null, 2));
Expand Down
Loading