Merge branch 'x4base-master'

This commit is contained in:
Wiwi Kuan
2021-02-18 19:44:03 +08:00
6 changed files with 193 additions and 253 deletions

View File

@@ -1,108 +1,47 @@
<html>
<title>Fast and Dirty Captioner</title>
<title>Fast and Dirty Captioner</title>
<head>
<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
<head>
<meta charset="UTF-8" />
<style>
.video {
width: 960px;
}
})(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(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>
<body>
<p>用 Node 開 HTTP Server影片檔名取 video.mp4字幕文字檔 subs.txt每句分行放在同一資料夾。</p>
<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>
<textarea id="textArea" rows="10" cols="80">預設的字。</textarea>
</body>
</style>
</head>
<body>
<p>選取字幕檔以及影片檔案開始製作 SRT</p>
<label>
<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>
<textarea id="textArea" rows="10" cols="80">預設的字。</textarea>
<video class="video" id="video" controls></video>
</body>
<script src="main.js"></script>
</html>

151
main.js Normal file
View 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': 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': video => {
lines[currentStamping] = [
lines[currentStamping][0],
video.currentTime - reactTime
];
},
'i': () => {
currentStamping -= 1;
},
'o': () => {
currentStamping += 1;
},
'u': () => (video.currentTime -= 3),
'p': () => (video.currentTime += 3),
'q': () => makeSRT()
};
function getCurrentStatus() {
return `Stamping Line ${currentStamping} | Playhead: ${video.currentTime}`;
}
function execHotkey(keyMap) {
document.addEventListener('keypress', function(e) {
const execFn = keyMap[e.key.toLowerCase()];
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();
}

3
p5.min.js vendored

File diff suppressed because one or more lines are too long

28
p5.sound.min.js vendored

File diff suppressed because one or more lines are too long

118
sketch.js
View File

@@ -1,118 +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.05 - 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 -= 2;
} else if (keyCode === 80) { // P
vid.elt.currentTime += 2;
}
}
function pad(num, size) {
var s = "000000000" + num;
return s.substr(s.length-size);
}
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);
sms = pad(sms, 3);
eh = floor(lineEndTime[i] / 3600);
em = floor((lineEndTime[i] % 3600) / 60);
es = floor(lineEndTime[i] % 60);
ems = floor((lineEndTime[i] * 1000) % 1000);
ems = pad(ems, 3);
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');
}

View File

@@ -1 +0,0 @@
http-server -p 1234 -o