make it a pure static website
This commit is contained in:
129
index.html
129
index.html
@@ -2,107 +2,46 @@
|
|||||||
<title>Fast and Dirty Captioner</title>
|
<title>Fast and Dirty Captioner</title>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<script src="p5.min.js"></script>
|
|
||||||
<script src="p5.sound.min.js"></script>
|
|
||||||
<script src="sketch.js"></script>
|
|
||||||
<script>
|
|
||||||
(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) {
|
<style>
|
||||||
return "undefined" == typeof b ? b = {
|
.video {
|
||||||
autoBom: !1
|
width: 960px;
|
||||||
} : "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
|
|
||||||
}
|
}
|
||||||
|
</style>
|
||||||
function c(b, c, d) {
|
|
||||||
var e = new XMLHttpRequest;
|
|
||||||
e.open("GET", b), e.responseType = "blob", e.onload = function() {
|
|
||||||
a(e.response, c, d)
|
|
||||||
}, e.onerror = function() {
|
|
||||||
console.error("could not download file")
|
|
||||||
}, e.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.saveAs || ("object" != typeof window || window !== f ? function() {} : "download" in HTMLAnchorElement.prototype ? 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(a, b, d, e) {
|
|
||||||
if (e = e || open("", "_blank"), e && (e.document.title = e.document.body.innerText = "downloading..."), "string" == typeof a) return c(a, b, d);
|
|
||||||
var g = "application/octet-stream" === a.type,
|
|
||||||
h = /constructor/i.test(f.HTMLElement) || f.safari,
|
|
||||||
i = /CriOS\/[\d]+/.test(navigator.userAgent);
|
|
||||||
if ((i || g && h) && "undefined" != typeof FileReader) {
|
|
||||||
var j = new FileReader;
|
|
||||||
j.onloadend = function() {
|
|
||||||
var a = j.result;
|
|
||||||
a = i ? a : a.replace(/^data:[^;]*;/, "data:attachment/file;"), e ? e.location.href = a : location = a, e = null
|
|
||||||
}, j.readAsDataURL(a)
|
|
||||||
} else {
|
|
||||||
var k = f.URL || f.webkitURL,
|
|
||||||
l = k.createObjectURL(a);
|
|
||||||
e ? e.location = l : location.href = l, e = null, setTimeout(function() {
|
|
||||||
k.revokeObjectURL(l)
|
|
||||||
}, 4E4)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
f.saveAs = a.saveAs = a, "undefined" != typeof module && (module.exports = a)
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<p>用 Node 開 HTTP Server,影片檔名取 video.mp4,字幕文字檔 subs.txt(每句分行),放在同一資料夾。</p>
|
<p>選取字幕檔以及影片檔案開始製作 SRT!</p>
|
||||||
<p>K: 下一行開始 | L: 這一行提前結束 | I: 前捲一行 | O: 後捲一行 | U: 倒帶 3 秒 | P: 前進 3 秒 | Q: 製作 SRT 檔</p>
|
<label>
|
||||||
<p>K: Next Line | L: This Line Ends Early | I: Scroll Back | O: Scroll Forward | U: Rewind | P: Forward | Q: Make SRT File</p>
|
<span>選擇字幕檔案:</span>
|
||||||
|
<input
|
||||||
|
id="srtFile"
|
||||||
|
type="file"
|
||||||
|
name="srtFile"
|
||||||
|
placeholder="點擊上傳字幕檔"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>選擇影片檔案:</span>
|
||||||
|
<input
|
||||||
|
id="videoFile"
|
||||||
|
type="file"
|
||||||
|
name="videoFile"
|
||||||
|
placeholder="點擊上傳影片檔"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<p>
|
||||||
|
K: 下一行開始 | L: 這一行提前結束 | I: 前捲一行 | O: 後捲一行 | U: 倒帶 3
|
||||||
|
秒 | P: 前進 3 秒 | Q: 製作 SRT 檔
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
K: Next Line | L: This Line Ends Early | I: Scroll Back | O: Scroll
|
||||||
|
Forward | U: Rewind | P: Forward | Q: Make SRT File
|
||||||
|
</p>
|
||||||
<p id="status">Test Text.</p>
|
<p id="status">Test Text.</p>
|
||||||
<textarea id="textArea" rows="10" cols="80">預設的字。</textarea>
|
<textarea id="textArea" rows="10" cols="80">預設的字。</textarea>
|
||||||
|
<video class="video" id="video" controls></video>
|
||||||
</body>
|
</body>
|
||||||
|
<script src="main.js"></script>
|
||||||
</html>
|
</html>
|
||||||
|
151
main.js
Normal file
151
main.js
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
const SRT_ID = 'srtFile';
|
||||||
|
const VIDEO_ID = 'videoFile';
|
||||||
|
|
||||||
|
const srtInput = document.querySelector('#srtFile');
|
||||||
|
const videoInput = document.querySelector('#videoFile');
|
||||||
|
const video = document.querySelector('#video');
|
||||||
|
const textArea = document.querySelector('#textArea');
|
||||||
|
const status = document.querySelector('#status');
|
||||||
|
const reactTime = 0.4;
|
||||||
|
let subTexts = [];
|
||||||
|
let currentStamping = 0;
|
||||||
|
let lines = [];
|
||||||
|
|
||||||
|
function clamp(num) {
|
||||||
|
return Math.max(num, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyMap = {
|
||||||
|
/* K */ '75': video => {
|
||||||
|
if (currentStamping >= lines.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines[currentStamping + 1][0] = clamp(video.currentTime - reactTime);
|
||||||
|
lines[currentStamping][1] =
|
||||||
|
lines[currentStamping][1] > video.currentTime - reactTime ||
|
||||||
|
lines[currentStamping][1] === null
|
||||||
|
? clamp(video.currentTime - 0.03 - reactTime)
|
||||||
|
: null;
|
||||||
|
currentStamping += 1;
|
||||||
|
},
|
||||||
|
/* L */ '76': video => {
|
||||||
|
lines[currentStamping] = [
|
||||||
|
lines[currentStamping][0],
|
||||||
|
video.currentTime - reactTime
|
||||||
|
];
|
||||||
|
},
|
||||||
|
/* I */ '73': () => {
|
||||||
|
currentStamping -= 1;
|
||||||
|
},
|
||||||
|
/* O*/ '79': () => {
|
||||||
|
currentStamping += 1;
|
||||||
|
},
|
||||||
|
/* U*/ '85': () => (video.currentTime -= 3),
|
||||||
|
/* P */ '80': () => (video.currentTime += 3),
|
||||||
|
/* Q */ '81': () => makeSRT()
|
||||||
|
};
|
||||||
|
|
||||||
|
function getCurrentStatus() {
|
||||||
|
return `Stamping Line ${currentStamping} | Playhead: ${video.currentTime}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function execHotkey(keyMap) {
|
||||||
|
document.addEventListener('keypress', function(e) {
|
||||||
|
const execFn = keyMap[e.keyCode];
|
||||||
|
if (typeof execFn === 'function') {
|
||||||
|
execFn(video);
|
||||||
|
updateContent();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateContent() {
|
||||||
|
const head = '** 目前 ---> ';
|
||||||
|
|
||||||
|
const content = subTexts
|
||||||
|
.slice(currentStamping, currentStamping + 5)
|
||||||
|
.map((text, i) => {
|
||||||
|
const [timeStart, timeEnd] = lines[currentStamping + i];
|
||||||
|
return `${i === 0 ? head : ''}${text} | ${timeStart} --> ${timeEnd}`;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
textArea.value = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFileUpload(e) {
|
||||||
|
if (e.target.files !== null) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
const file = e.target.files[0];
|
||||||
|
|
||||||
|
/*
|
||||||
|
if it's srt file, fill text area with srt content
|
||||||
|
if it's video, load it into video tag
|
||||||
|
*/
|
||||||
|
reader.onload = function() {
|
||||||
|
if (e.target.id === SRT_ID) {
|
||||||
|
subTexts = reader.result.split('\n');
|
||||||
|
subTexts.forEach((_, i) => (lines[i] = [null, null]));
|
||||||
|
lines[0][0] = 0;
|
||||||
|
|
||||||
|
updateContent();
|
||||||
|
|
||||||
|
execHotkey(keyMap);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onerror = function() {
|
||||||
|
alert('無法讀取檔案!');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (e.target.id === SRT_ID) {
|
||||||
|
reader.readAsText(file);
|
||||||
|
} else {
|
||||||
|
video.src = URL.createObjectURL(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
videoInput.addEventListener('change', handleFileUpload);
|
||||||
|
srtInput.addEventListener('change', handleFileUpload);
|
||||||
|
|
||||||
|
video.addEventListener('timeupdate', function(e) {
|
||||||
|
status.textContent = getCurrentStatus();
|
||||||
|
});
|
||||||
|
|
||||||
|
function makeSRT() {
|
||||||
|
srt = '';
|
||||||
|
for (let i = 0; i < subTexts.length; i++) {
|
||||||
|
// line number
|
||||||
|
srt += i + 1 + '\n';
|
||||||
|
// line time
|
||||||
|
let sh, sm, ss, sms;
|
||||||
|
let eh, em, es, ems;
|
||||||
|
const [timeStart, timeEnd] = lines[i];
|
||||||
|
const leftPad = str => `${str}`.padStart(2, '0');
|
||||||
|
sh = leftPad(Math.floor(timeStart / 3600));
|
||||||
|
sm = leftPad(Math.floor((timeStart % 3600) / 60));
|
||||||
|
ss = leftPad(Math.floor(timeStart % 60));
|
||||||
|
sms = leftPad(Math.floor((timeStart * 1000) % 1000));
|
||||||
|
eh = leftPad(Math.floor(timeEnd / 3600));
|
||||||
|
em = leftPad(Math.floor((timeEnd % 3600) / 60));
|
||||||
|
es = leftPad(Math.floor(timeEnd % 60));
|
||||||
|
ems = leftPad(Math.floor((timeEnd * 1000) % 1000));
|
||||||
|
|
||||||
|
srt += `${sh}:${sm}:${ss},${sms} --> ${eh}:${em}:${es},${ems}\n`;
|
||||||
|
srt += subTexts[i];
|
||||||
|
srt += '\n\n';
|
||||||
|
}
|
||||||
|
console.log(srt);
|
||||||
|
let blob = new Blob([srt], {
|
||||||
|
type: 'text/plain;charset=utf-8'
|
||||||
|
});
|
||||||
|
const a = document.createElement('a');
|
||||||
|
const file = new Blob([srt], { type: 'text/plain;charset=utf-8' });
|
||||||
|
a.href = URL.createObjectURL(file);
|
||||||
|
a.download = 'srt.txt';
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(a.href);
|
||||||
|
a.remove();
|
||||||
|
}
|
111
sketch.js
111
sketch.js
@@ -1,111 +0,0 @@
|
|||||||
let curr; // current time
|
|
||||||
let sta; // status text
|
|
||||||
let status; // = select("#status");
|
|
||||||
let tAreaText; //
|
|
||||||
let tArea;
|
|
||||||
let vidFile = "./video.mp4";
|
|
||||||
let subFile = "./subs.txt";
|
|
||||||
let subText; // load from text file
|
|
||||||
let lineStartTime = [];
|
|
||||||
let lineEndTime = [];
|
|
||||||
let reactTime = 0.4;
|
|
||||||
|
|
||||||
let currentStamping = 0;
|
|
||||||
let srt = "";
|
|
||||||
|
|
||||||
function preload() {
|
|
||||||
vid = createVideo(vidFile);
|
|
||||||
vid.size(640, 320);
|
|
||||||
subText = loadStrings(subFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setup() {
|
|
||||||
noCanvas();
|
|
||||||
status = select("#status");
|
|
||||||
tArea = select("#textArea");
|
|
||||||
|
|
||||||
for (let i = 0; i < subText.length; i++) {
|
|
||||||
lineStartTime[i] = null;
|
|
||||||
lineEndTime[i] = null;
|
|
||||||
|
|
||||||
//init all timestamps to 0
|
|
||||||
}
|
|
||||||
lineStartTime[0] = 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function draw() {
|
|
||||||
curr = vid.elt.currentTime;
|
|
||||||
sta = `Stamping Line ${currentStamping} | Playhead: ${curr}`;
|
|
||||||
tAreaText = "";
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
if (i == 0) {
|
|
||||||
tAreaText += "** 目前 ---> "
|
|
||||||
}
|
|
||||||
tAreaText += `${subText[currentStamping+i]} | ${lineStartTime[currentStamping+i]} --> ${lineEndTime[currentStamping+i]}` + String.fromCharCode(13, 10);
|
|
||||||
}
|
|
||||||
status.html(sta);
|
|
||||||
tArea.html(tAreaText);
|
|
||||||
}
|
|
||||||
|
|
||||||
function keyPressed() {
|
|
||||||
if (keyCode === 75) { // K
|
|
||||||
// 按左鍵
|
|
||||||
// set line start time to current time
|
|
||||||
lineStartTime[currentStamping + 1] = vid.elt.currentTime - reactTime;
|
|
||||||
if (lineStartTime[currentStamping + 1] < 0){
|
|
||||||
lineStartTime[currentStamping + 1] = 0;
|
|
||||||
}
|
|
||||||
// set prev line's end time, if prev end time > currentTime;
|
|
||||||
if (lineEndTime[currentStamping] > vid.elt.currentTime - reactTime || lineEndTime[currentStamping] == null) {
|
|
||||||
lineEndTime[currentStamping] = vid.elt.currentTime - 0.03 - reactTime;
|
|
||||||
if (lineEndTime[currentStamping] < 0){
|
|
||||||
lineEndTime[currentStamping] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentStamping++;
|
|
||||||
} else if (keyCode === 76) { // L
|
|
||||||
lineEndTime[currentStamping] = vid.elt.currentTime - reactTime;
|
|
||||||
} else if (keyCode === 73) { // I
|
|
||||||
currentStamping--;
|
|
||||||
} else if (keyCode === 79) { // O
|
|
||||||
currentStamping++;
|
|
||||||
} else if (keyCode === 81) {
|
|
||||||
// Q : Make SRT
|
|
||||||
makeSRT();
|
|
||||||
} else if (keyCode === 85) { // U
|
|
||||||
vid.elt.currentTime -= 3;
|
|
||||||
|
|
||||||
} else if (keyCode === 80) { // P
|
|
||||||
vid.elt.currentTime += 3;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeSRT() {
|
|
||||||
srt = "";
|
|
||||||
for (let i = 0; i < subText.length; i++) {
|
|
||||||
// line number
|
|
||||||
srt += (i + 1) + "\n";
|
|
||||||
// line time
|
|
||||||
let sh, sm, ss, sms;
|
|
||||||
let eh, em, es, ems;
|
|
||||||
sh = floor(lineStartTime[i] / 3600);
|
|
||||||
sm = floor((lineStartTime[i] % 3600) / 60);
|
|
||||||
ss = floor(lineStartTime[i] % 60);
|
|
||||||
sms = floor((lineStartTime[i] * 1000) % 1000);
|
|
||||||
eh = floor(lineEndTime[i] / 3600);
|
|
||||||
em = floor((lineEndTime[i] % 3600) / 60);
|
|
||||||
es = floor(lineEndTime[i] % 60);
|
|
||||||
ems = floor((lineEndTime[i] * 1000) % 1000);
|
|
||||||
|
|
||||||
srt += `${sh}:${sm}:${ss},${sms} --> ${eh}:${em}:${es},${ems}\n`
|
|
||||||
srt += subText[i];
|
|
||||||
srt += "\n\n"
|
|
||||||
}
|
|
||||||
console.log(srt);
|
|
||||||
let blob = new Blob([srt], {
|
|
||||||
type: "text/plain;charset=utf-8"
|
|
||||||
});
|
|
||||||
saveAs(blob, 'srt.txt');
|
|
||||||
}
|
|
Reference in New Issue
Block a user