diff --git a/README.md b/README.md
index 91c1ccdd..d90429db 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# yt2press
-A tool to convert YouTube videos into press-ready markdown documents with timestamped screenshots.
+A tool to convert YouTube videos into VitePress web app with timestamped screenshots.
## How to Use
@@ -89,7 +89,7 @@ docs/ # VitePress documentation root
components/
Downloader.vue # Vue component for the downloader interface
Video.vue # Vue component to display videos and linked timestamps
- public/ # Video storage directory
+ public/ # Directory to put downloaded videos
*.md # Markdown files with content
[video-filename]/ # Screenshot folders (auto-generated)
*.png # Timestamped screenshots
diff --git a/docs/意大利为什么没有赶上现代化这趟二路汽车?.md b/docs/意大利为什么没有赶上现代化这趟二路汽车?.md
new file mode 100644
index 00000000..000d10b0
--- /dev/null
+++ b/docs/意大利为什么没有赶上现代化这趟二路汽车?.md
@@ -0,0 +1,2949 @@
+---
+interval: 2
+dedup: true
+video: '/意大利为什么没有赶上现代化这趟二路汽车? [tJNUXvNYE9A].mp4'
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0:40
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0:54
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1:04
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2:10
+
+
+
+
+
+
+
+
+
+
+
+
+2:14
+
+
+
+
+
+
+
+
+
+
+
+
+2:18
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+3:02
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+3:12
+
+
+
+
+
+
+
+
+
+
+
+
+3:16
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+4:02
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+4:28
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+4:44
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+5:02
+
+
+
+
+
+
+
+
+
+
+
+
+5:06
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+5:16
+
+
+
+
+
+
+
+
+
+
+
+
+5:20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+5:30
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+6:20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7:08
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7:16
+
+
+
+
+
+
+
+
+
+
+
+
+7:20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7:38
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+8:06
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+8:12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+8:28
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+8:34
+
+
+
+
+
+
+
+
+
+
+
+
+8:38
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+9:20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+9:30
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+9:58
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+10:26
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+12:22
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+12:36
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+12:46
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+12:56
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+13:22
+
+
+
+
+
+
+
+
+
+
+
+
+13:26
+
+
+
+
+
+
+
+
+
+
+
+
+13:30
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+13:36
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+13:44
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+13:50
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+14:16
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+14:48
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+14:54
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+15:20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+15:38
+
+
+
+
+
+
+
+
+
+
+
+
+15:42
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+16:12
+
+
+
+
+
+
+
+
+
+
+
+
+16:16
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+16:38
+
+
+
+
+
+
+
+
+
+
+
+
+16:42
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+17:36
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+18:02
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+18:36
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+18:44
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+19:24
+
+
+
+
+
+
+
+
+
+
+
+
+19:28
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+19:36
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+20:02
+
+
+
+
+
+
+
+
+
+
+
+
+20:06
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+20:14
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+20:24
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+21:50
+
+
+
+
+
+
diff --git a/server.js b/server.js
index 15779d15..6d56d6c9 100644
--- a/server.js
+++ b/server.js
@@ -6,8 +6,8 @@ import matter from "gray-matter";
import { renderFile } from "./group-similar.js";
+const ROOT_DIR = "docs";
const PORT = 8080;
-
const wss = new WebSocketServer({ port: PORT });
console.log(`WebSocket server running on ws://localhost:${PORT}`);
@@ -16,7 +16,6 @@ wss.on("connection", (ws) => {
console.log("New client connected");
let filename = "";
- const ROOT_DIR = "docs";
ws.on("message", (message, isBinary) => {
if (message instanceof Buffer && isBinary) {
@@ -33,61 +32,46 @@ wss.on("connection", (ws) => {
const { q, duration } = payload;
console.log(`Start ${q}`);
- const q_stem = q
- .replace(/\.[a-z0-9]+$/, "")
- .replace(/\[.+?\]/g, "")
- .replace(/\s+/g, "_")
- .trim();
+ const md = loadMdFile(q);
try {
- mkdirSync(joinPath(ROOT_DIR, q_stem));
+ mkdirSync(joinPath(ROOT_DIR, md.q_stem));
} catch (e) {}
let isChanged = false;
const ts = [];
- const mdFile = joinPath(ROOT_DIR, `${q_stem}.md`);
- let { data, content } = matter(readFileSync(mdFile, "utf-8"));
- data.video = "/" + q;
+ md.data.video = "/" + q;
- const { dedup, interval } = data;
+ const { interval } = md.data;
if (typeof interval === "number") {
for (let t = 0; t < duration; t += interval) {
const min = Math.floor(t / 60);
const sec = Math.floor(t - min * 60);
- content += `\n${min}:${sec.toString().padStart(2, "0")}\n`;
+ md.content += `\n${min}:${sec.toString().padStart(2, "0")}\n`;
}
}
const re = /^(?\d{1,2}):(?\d{2})\s*$/;
- const lines = content.split("\n");
- content = "";
+ const lines = md.content.split("\n");
+
+ let newContent = "";
for (const line of lines) {
const m = re.exec(line);
if (m?.groups) {
const { min, sec } = m.groups;
const t = `${min}_${sec}`;
ts.push(t);
- content += `\n\n\n`;
+ newContent += `\n\n\n`;
isChanged = true;
} else {
- content += `${line}\n`;
+ newContent += `${line}\n`;
}
}
if (isChanged) {
- writeFileSync(mdFile, matter.stringify(content, data));
-
- if (dedup) {
- renderFile(
- mdFile,
- mdFile,
- typeof dedup === "number" ? dedup : undefined,
- ).then((stats) => {
- // Don't log a thing yet.
- });
- }
+ writeFileSync(md.filename, matter.stringify(newContent, md.data));
ws.send(JSON.stringify({ name: "seek_q_ts", q, ts }));
}
@@ -106,6 +90,24 @@ wss.on("connection", (ws) => {
case "done":
{
const { q } = payload;
+ const md = loadMdFile(q);
+
+ console.log(`Done timestamp ${q}`);
+
+ const { dedup } = md.data;
+ if (dedup) {
+ console.log(`Deduping ${md.filename}`);
+
+ renderFile(
+ md.filename,
+ md.filename,
+ typeof dedup === "number" ? dedup : undefined,
+ ).then((stats) => {
+ // Don't log a thing yet.
+ console.log(`Done duduping ${md.filename}`);
+ });
+ }
+
console.log(`Done ${q}`);
}
break;
@@ -116,3 +118,23 @@ wss.on("connection", (ws) => {
console.log("Client disconnected");
});
});
+
+/**
+ *
+ * @param {string} q
+ * @returns
+ */
+function loadMdFile(q) {
+ const q_stem = q
+ .replace(/\.[a-z0-9]+$/, "")
+ .replace(/\[.+?\]/g, "")
+ .trim()
+ .replace(/\s+/g, "_");
+
+ try {
+ mkdirSync(joinPath(ROOT_DIR, q_stem));
+ } catch (e) {}
+
+ const filename = joinPath(ROOT_DIR, `${q_stem}.md`);
+ return { q_stem, filename, ...matter(readFileSync(filename, "utf-8")) };
+}
diff --git a/yt2press.code-workspace b/yt2press.code-workspace
new file mode 100644
index 00000000..71a4d2c4
--- /dev/null
+++ b/yt2press.code-workspace
@@ -0,0 +1,16 @@
+{
+ "folders": [
+ {
+ "path": "."
+ },
+ {
+ "path": "docs"
+ },
+ {
+ "path": "docs/public"
+ }
+ ],
+ "settings": {
+ "json.validate.enable": false
+ }
+}
\ No newline at end of file