dsh-vidi 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +91 -16
- package/lib/index.mjs +52 -5
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -33627,6 +33627,9 @@ function drawSubtitles(g, W, H, texts, style) {
|
|
|
33627
33627
|
}
|
|
33628
33628
|
|
|
33629
33629
|
// src/client/Player.tsx
|
|
33630
|
+
/** A control-bar menu: an icon button that opens a panel above the bar. */
|
|
33631
|
+
/** Seconds between two lines that still count as one stretch of speech (the line stays up across it). */
|
|
33632
|
+
const GAP_FILL = 4;
|
|
33630
33633
|
/** Subtitles carry no full stop at the end of a line (older lines may still have one). */
|
|
33631
33634
|
const noStop = (x) => /(\.\.\.|…)$/.test(x) ? x : x.replace(/[,,、;;::]+$/, "").replace(/[。.。]$|(?<=[^.\s])\.$/, "");
|
|
33632
33635
|
const qLabel = (h) => h >= 2160 ? "4K" : h >= 1440 ? "2K" : `${h}p`;
|
|
@@ -33920,7 +33923,14 @@ const Player = (0, react.forwardRef)(function Player(props, ref) {
|
|
|
33920
33923
|
const total = vod ? Math.max(duration ?? 0, edge) : edge;
|
|
33921
33924
|
const behind = Math.max(0, edge - now);
|
|
33922
33925
|
const atLive = behind < delay + 10;
|
|
33923
|
-
const current =
|
|
33926
|
+
const current = (() => {
|
|
33927
|
+
let i = -1;
|
|
33928
|
+
for (let k = 0; k < cues.length; k++) if (cues[k].start - .2 <= now) i = k;
|
|
33929
|
+
const c = cues[i], next = cues[i + 1];
|
|
33930
|
+
if (!c) return void 0;
|
|
33931
|
+
const until = next && next.start - c.end <= GAP_FILL ? next.start - .2 : c.end + 1.2;
|
|
33932
|
+
return now <= Math.max(until, c.end + .3) ? c : void 0;
|
|
33933
|
+
})() ?? (trail ? cues.filter((c) => c.start <= now && c.end > now - 8).at(-1) : void 0);
|
|
33924
33934
|
const textsOf = (cue) => {
|
|
33925
33935
|
const out = {};
|
|
33926
33936
|
if (!cue) return out;
|
|
@@ -35406,6 +35416,31 @@ function History({ t, ui, onOpen }) {
|
|
|
35406
35416
|
(0, react.useEffect)(() => {
|
|
35407
35417
|
load(0, 5);
|
|
35408
35418
|
}, []);
|
|
35419
|
+
const busy = items.some((v) => v.work);
|
|
35420
|
+
const shown = (0, react.useRef)(5);
|
|
35421
|
+
shown.current = Math.max(5, items.length);
|
|
35422
|
+
(0, react.useEffect)(() => {
|
|
35423
|
+
if (!busy) return;
|
|
35424
|
+
const timer = window.setInterval(() => void load(0, shown.current), 3e3);
|
|
35425
|
+
return () => window.clearInterval(timer);
|
|
35426
|
+
}, [busy]);
|
|
35427
|
+
const [halting, setHalting] = (0, react.useState)(null);
|
|
35428
|
+
const halt = async (v) => {
|
|
35429
|
+
setHalting(v.id);
|
|
35430
|
+
await fetch("/live/api/halt", {
|
|
35431
|
+
method: "POST",
|
|
35432
|
+
headers: { "content-type": "application/json" },
|
|
35433
|
+
body: JSON.stringify({ jobId: v.id })
|
|
35434
|
+
}).catch(() => {});
|
|
35435
|
+
await load(0, shown.current);
|
|
35436
|
+
setHalting(null);
|
|
35437
|
+
};
|
|
35438
|
+
const doing = (w) => [
|
|
35439
|
+
w.recording ? t("hist.recording") : "",
|
|
35440
|
+
w.loading ? t("hist.loading") : "",
|
|
35441
|
+
w.subs !== void 0 ? w.subs >= 0 ? t("hist.subsPct").replace("{p}", String(w.subs)) : t("hist.subs") : "",
|
|
35442
|
+
w.pushing ? t("hist.pushing") : ""
|
|
35443
|
+
].filter(Boolean);
|
|
35409
35444
|
if (!items.length) return null;
|
|
35410
35445
|
const from = (v) => v.source === "file" ? t("src.file") : v.source === "device" ? t("src.camera") : v.source === "push" ? t("src.push") : siteName(v.url ?? "") || (() => {
|
|
35411
35446
|
try {
|
|
@@ -35460,11 +35495,23 @@ function History({ t, ui, onOpen }) {
|
|
|
35460
35495
|
].filter(Boolean).join(" · ")
|
|
35461
35496
|
})]
|
|
35462
35497
|
}),
|
|
35463
|
-
v.
|
|
35498
|
+
v.work ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35499
|
+
className: "chip",
|
|
35500
|
+
children: doing(v.work).join(" · ")
|
|
35501
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35502
|
+
className: "dshl-link stop",
|
|
35503
|
+
disabled: halting === v.id,
|
|
35504
|
+
"data-tip": t("hist.stop.tip").replace("{w}", doing(v.work).join("、")),
|
|
35505
|
+
onClick: (e) => {
|
|
35506
|
+
e.stopPropagation();
|
|
35507
|
+
halt(v);
|
|
35508
|
+
},
|
|
35509
|
+
children: t(halting === v.id ? "hist.stopping" : "hist.stop")
|
|
35510
|
+
})] }) : v.running ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35464
35511
|
className: "chip",
|
|
35465
35512
|
children: t("hist.running")
|
|
35466
|
-
}),
|
|
35467
|
-
v.live && v.source === "url" && !v.running && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35513
|
+
}) : null,
|
|
35514
|
+
v.live && v.source === "url" && !v.running && !v.work && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35468
35515
|
className: "dshl-link replay",
|
|
35469
35516
|
onClick: (e) => {
|
|
35470
35517
|
e.stopPropagation();
|
|
@@ -36508,21 +36555,23 @@ function LiveView(props) {
|
|
|
36508
36555
|
c.final && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
36509
36556
|
className: "tools",
|
|
36510
36557
|
onClick: (e) => e.stopPropagation(),
|
|
36511
|
-
children: [idx < shown.length - 1 && shown[idx + 1]?.final && /* @__PURE__ */ (0, react_jsx_runtime.
|
|
36558
|
+
children: [idx < shown.length - 1 && shown[idx + 1]?.final && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
36512
36559
|
className: "ghost",
|
|
36513
|
-
|
|
36560
|
+
"data-tip": t("lines.merge"),
|
|
36561
|
+
"aria-label": t("lines.merge"),
|
|
36514
36562
|
onClick: () => void mergeLine(c.id),
|
|
36515
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon.merge, {})
|
|
36563
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon.merge, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("lines.mergeShort") })]
|
|
36516
36564
|
}), (() => {
|
|
36517
36565
|
const k = shownLines.find((x) => (lineText(c, x) ?? "").length > 1) ?? "src";
|
|
36518
|
-
return Array.from(lineText(c, k) ?? c.src).length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.
|
|
36566
|
+
return Array.from(lineText(c, k) ?? c.src).length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
36519
36567
|
className: "ghost",
|
|
36520
|
-
|
|
36568
|
+
"data-tip": t("lines.split"),
|
|
36569
|
+
"aria-label": t("lines.split"),
|
|
36521
36570
|
onClick: () => setSplitting({
|
|
36522
36571
|
id: c.id,
|
|
36523
36572
|
key: lineKey(k)
|
|
36524
36573
|
}),
|
|
36525
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon.scissors, {})
|
|
36574
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon.scissors, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("lines.splitShort") })]
|
|
36526
36575
|
});
|
|
36527
36576
|
})()]
|
|
36528
36577
|
})
|
|
@@ -37748,8 +37797,10 @@ const en = {
|
|
|
37748
37797
|
"dev.and": " and ",
|
|
37749
37798
|
"how.title": "How subtitles are made",
|
|
37750
37799
|
"how.more": "More services and keys are in Settings.",
|
|
37751
|
-
"lines.merge": "Join
|
|
37752
|
-
"lines.split": "
|
|
37800
|
+
"lines.merge": "Join this line and the next into one",
|
|
37801
|
+
"lines.split": "Break this line in two: then click where it should break",
|
|
37802
|
+
"lines.mergeShort": "Join",
|
|
37803
|
+
"lines.splitShort": "Split",
|
|
37753
37804
|
"lines.splitHint": "Click where the line should break.",
|
|
37754
37805
|
"hist.goLive": "Watch live again",
|
|
37755
37806
|
"hist.replay": "Replay",
|
|
@@ -37757,6 +37808,14 @@ const en = {
|
|
|
37757
37808
|
"hist.untitled": "Untitled video",
|
|
37758
37809
|
"hist.live": "live",
|
|
37759
37810
|
"hist.running": "Live now",
|
|
37811
|
+
"hist.recording": "Recording",
|
|
37812
|
+
"hist.loading": "Loading",
|
|
37813
|
+
"hist.subs": "Making subtitles",
|
|
37814
|
+
"hist.subsPct": "Subtitles {p}%",
|
|
37815
|
+
"hist.pushing": "Pushing out",
|
|
37816
|
+
"hist.stop": "Stop",
|
|
37817
|
+
"hist.stopping": "Stopping…",
|
|
37818
|
+
"hist.stop.tip": "Stop: {w}",
|
|
37760
37819
|
"hist.more": "More ({n})",
|
|
37761
37820
|
"set.spoken.tip": "The language people speak in this video; Auto detects it.",
|
|
37762
37821
|
"rs.mode": "Subtitle mode of the push-out",
|
|
@@ -38209,8 +38268,10 @@ const zh = {
|
|
|
38209
38268
|
"dev.and": "和",
|
|
38210
38269
|
"how.title": "字幕怎么做",
|
|
38211
38270
|
"how.more": "更多服务和 key 在设置里。",
|
|
38212
|
-
"lines.merge": "
|
|
38213
|
-
"lines.split": "
|
|
38271
|
+
"lines.merge": "把这一句和下一句合成一句",
|
|
38272
|
+
"lines.split": "把这一句拆成两句:点了以后,再点文字里要断开的位置",
|
|
38273
|
+
"lines.mergeShort": "合并",
|
|
38274
|
+
"lines.splitShort": "拆开",
|
|
38214
38275
|
"lines.splitHint": "点一下要断开的位置。",
|
|
38215
38276
|
"hist.goLive": "回到直播",
|
|
38216
38277
|
"hist.replay": "回放",
|
|
@@ -38218,6 +38279,14 @@ const zh = {
|
|
|
38218
38279
|
"hist.untitled": "未命名视频",
|
|
38219
38280
|
"hist.live": "直播",
|
|
38220
38281
|
"hist.running": "直播中",
|
|
38282
|
+
"hist.recording": "录制中",
|
|
38283
|
+
"hist.loading": "加载中",
|
|
38284
|
+
"hist.subs": "字幕制作中",
|
|
38285
|
+
"hist.subsPct": "字幕 {p}%",
|
|
38286
|
+
"hist.pushing": "推流中",
|
|
38287
|
+
"hist.stop": "停止",
|
|
38288
|
+
"hist.stopping": "停止中…",
|
|
38289
|
+
"hist.stop.tip": "停止:{w}",
|
|
38221
38290
|
"hist.more": "更多(还有 {n} 个)",
|
|
38222
38291
|
"set.spoken.tip": "视频里说的是哪种语言;自动 = 自己识别。",
|
|
38223
38292
|
"rs.mode": "推流用的字幕模式",
|
|
@@ -38902,6 +38971,9 @@ html[data-platform='darwin'] .dshl.page .dshl-top :is(button, input, label, a, .
|
|
|
38902
38971
|
.dshl-history .tx { display: grid; gap: 3px; min-width: 0; flex: 1; }
|
|
38903
38972
|
.dshl-history .tt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13.5px; font-weight: 550; }
|
|
38904
38973
|
.dshl-history .mm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--faint); font-size: 12px; }
|
|
38974
|
+
.dshl-history .stop { flex: none; padding: 4px 6px; font-size: 12.5px; color: var(--faint); }
|
|
38975
|
+
.dshl-history .stop:hover { color: var(--live); }
|
|
38976
|
+
.dshl-history .stop:disabled { opacity: .5; }
|
|
38905
38977
|
.dshl-history .chip { flex: none; padding: 2px 8px; border-radius: 999px; background: color-mix(in srgb, var(--live) 14%, transparent); color: var(--live); font-size: 11.5px; font-weight: 600; }
|
|
38906
38978
|
.dshl-history .more { margin: 8px 0 0 6px; font-size: 12.5px; }
|
|
38907
38979
|
.dshl-modemenu .mh { margin: 6px 4px 2px; color: rgba(255,255,255,.55); font-size: 11.5px; }
|
|
@@ -38911,8 +38983,11 @@ html[data-platform='darwin'] .dshl.page .dshl-top :is(button, input, label, a, .
|
|
|
38911
38983
|
.dshl-line { position: relative; }
|
|
38912
38984
|
.dshl-line .tools { position: absolute; top: 4px; right: 4px; display: none; gap: 2px; padding: 2px; border-radius: 7px; background: var(--bg); box-shadow: 0 1px 4px rgba(0,0,0,.12); }
|
|
38913
38985
|
.dshl-line:hover .tools, .dshl-line:focus-within .tools { display: flex; }
|
|
38914
|
-
.dshl-line .tools .ghost { display:
|
|
38915
|
-
.dshl-line .tools svg { width:
|
|
38986
|
+
.dshl-line .tools .ghost { position: relative; display: flex; align-items: center; gap: 4px; height: 24px; padding: 0 7px 0 5px; border: 0; border-radius: 5px; background: none; color: var(--muted); font-size: 12px; white-space: nowrap; cursor: pointer; }
|
|
38987
|
+
.dshl-line .tools svg { width: 14px; height: 14px; flex: none; }
|
|
38988
|
+
/* Says what a button does the moment the pointer is on it (the system tooltip takes a second). */
|
|
38989
|
+
.dshl-history .stop { position: relative; }
|
|
38990
|
+
.dshl-history .stop[data-tip]:hover::after, .dshl-line .tools .ghost[data-tip]:hover::after { content: attr(data-tip); position: absolute; top: calc(100% + 6px); right: 0; z-index: 5; width: max-content; max-width: 220px; padding: 5px 8px; border-radius: 6px; background: var(--ink); color: var(--bg); font-size: 12px; line-height: 1.45; white-space: normal; text-align: left; pointer-events: none; }
|
|
38916
38991
|
.dshl-line .tools .ghost:hover { background: var(--hover); color: var(--ink); }
|
|
38917
38992
|
.dshl-line.splitting { cursor: default; background: color-mix(in srgb, var(--accent) 8%, transparent); }
|
|
38918
38993
|
.dshl-line .cutline .ch { cursor: col-resize; border-left: 1px solid transparent; }
|
package/lib/index.mjs
CHANGED
|
@@ -118,8 +118,13 @@ function join$1(parts) {
|
|
|
118
118
|
*/
|
|
119
119
|
function exportCues(cues, langs, format, title = "") {
|
|
120
120
|
const rows = cues.filter((c) => c.final && lines(c, langs).length);
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
const endOf = (i) => {
|
|
122
|
+
const c = rows[i], n = rows[i + 1];
|
|
123
|
+
const until = n && n.start - c.end <= 4 ? n.start : c.end + .6;
|
|
124
|
+
return Math.max(c.start + .8, Math.min(until, n ? n.start : until));
|
|
125
|
+
};
|
|
126
|
+
if (format === "srt") return rows.map((c, i) => `${i + 1}\n${stamp(c.start, ",")} --> ${stamp(endOf(i), ",")}\n${lines(c, langs).join("\n")}\n`).join("\n");
|
|
127
|
+
if (format === "vtt") return "WEBVTT\n\n" + rows.map((c, i) => `${stamp(c.start, ".")} --> ${stamp(endOf(i), ".")}\n${lines(c, langs).join("\n")}\n`).join("\n");
|
|
123
128
|
const md = format === "md";
|
|
124
129
|
return (md && title ? `# ${title}\n\n` : title ? `${title}\n\n` : "") + paragraphs(rows).map((p) => {
|
|
125
130
|
const who = p[0].speaker;
|
|
@@ -2016,6 +2021,12 @@ function cutNear(cs, ratio) {
|
|
|
2016
2021
|
}
|
|
2017
2022
|
return best > 0 ? best : Math.max(1, Math.min(n - 1, target));
|
|
2018
2023
|
}
|
|
2024
|
+
/** When a video was first opened: its folder is named after that moment (UTC); older saves could carry a later time. */
|
|
2025
|
+
function openedAt(id, saved) {
|
|
2026
|
+
const named = /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})/.exec(id);
|
|
2027
|
+
const fromName = named ? Date.parse(`${named[1]}T${named[2]}:${named[3]}:${named[4]}Z`) : NaN;
|
|
2028
|
+
return Number.isFinite(fromName) ? fromName : typeof saved === "number" ? saved : void 0;
|
|
2029
|
+
}
|
|
2019
2030
|
var LiveJob = class LiveJob extends EventEmitter {
|
|
2020
2031
|
opts;
|
|
2021
2032
|
deps;
|
|
@@ -2028,6 +2039,7 @@ var LiveJob = class LiveJob extends EventEmitter {
|
|
|
2028
2039
|
status = "resolving";
|
|
2029
2040
|
message;
|
|
2030
2041
|
title;
|
|
2042
|
+
/** When it was first opened (kept across restores, so the history keeps its place). */
|
|
2031
2043
|
startedAt = Date.now();
|
|
2032
2044
|
cues = [];
|
|
2033
2045
|
ffmpeg;
|
|
@@ -2170,6 +2182,7 @@ var LiveJob = class LiveJob extends EventEmitter {
|
|
|
2170
2182
|
const j = JSON.parse(readFileSync(join(dir, "job.json"), "utf8"));
|
|
2171
2183
|
const job = new LiveJob(j.id, j.opts, deps);
|
|
2172
2184
|
job.title = j.title;
|
|
2185
|
+
job.startedAt = openedAt(j.id, j.startedAt) ?? job.startedAt;
|
|
2173
2186
|
job.vod = !!j.vod;
|
|
2174
2187
|
job.duration = j.duration;
|
|
2175
2188
|
job.captureDone = true;
|
|
@@ -3087,7 +3100,12 @@ var LiveJob = class LiveJob extends EventEmitter {
|
|
|
3087
3100
|
}
|
|
3088
3101
|
/** What the player would show at media time t, line by line (the same rules: no repeats, line-end marks dropped). */
|
|
3089
3102
|
rowsAt(t, style) {
|
|
3090
|
-
const
|
|
3103
|
+
const finals = this.cues.filter((c) => c.final);
|
|
3104
|
+
let i = -1;
|
|
3105
|
+
for (let k = 0; k < finals.length; k++) if (finals[k].start <= t) i = k;
|
|
3106
|
+
const cand = finals[i], next = finals[i + 1];
|
|
3107
|
+
const until = cand ? next && next.start - cand.end <= 4 ? next.start : Math.max(cand.end, cand.start + 1.2) : 0;
|
|
3108
|
+
const cue = cand && t < until ? cand : void 0;
|
|
3091
3109
|
const text = (key) => {
|
|
3092
3110
|
if (key.startsWith("site:")) return this.siteCues.get(key)?.find((c) => c.start <= t && t < c.end)?.text;
|
|
3093
3111
|
if (!cue) return void 0;
|
|
@@ -6601,7 +6619,7 @@ function apply(ctx, config) {
|
|
|
6601
6619
|
title: j.title || device || (j.opts.source === "url" ? j.opts.url : ""),
|
|
6602
6620
|
source: j.opts.source,
|
|
6603
6621
|
url: j.opts.source === "url" ? j.opts.url : void 0,
|
|
6604
|
-
startedAt: j.startedAt,
|
|
6622
|
+
startedAt: openedAt(j.id, j.startedAt),
|
|
6605
6623
|
duration,
|
|
6606
6624
|
live: !j.vod,
|
|
6607
6625
|
running: false,
|
|
@@ -6617,7 +6635,26 @@ function apply(ctx, config) {
|
|
|
6617
6635
|
}
|
|
6618
6636
|
if (hit.item) out.push(hit.item);
|
|
6619
6637
|
}
|
|
6620
|
-
|
|
6638
|
+
for (const item of out) {
|
|
6639
|
+
const j = jobs.get(String(item.id));
|
|
6640
|
+
if (!j) continue;
|
|
6641
|
+
const w = workOf(j);
|
|
6642
|
+
if (w) item.work = w;
|
|
6643
|
+
}
|
|
6644
|
+
return [...out.filter((i) => i.work), ...out.filter((i) => !i.work)];
|
|
6645
|
+
}
|
|
6646
|
+
/** Still working: recording a live stream, loading a video, making subtitles, pushing out. */
|
|
6647
|
+
function workOf(j) {
|
|
6648
|
+
const s = j.summary();
|
|
6649
|
+
const ok = !s.message;
|
|
6650
|
+
const total = s.vod ? s.duration : s.status === "running" ? void 0 : s.recorded || s.duration;
|
|
6651
|
+
const w = {
|
|
6652
|
+
recording: s.status === "running" && !s.vod && ok,
|
|
6653
|
+
loading: (s.status === "running" || s.status === "resolving") && !!s.vod && ok,
|
|
6654
|
+
subs: s.subs?.state === "running" ? total ? Math.min(99, Math.round(s.subs.at / total * 100)) : -1 : void 0,
|
|
6655
|
+
pushing: !!s.restream?.running
|
|
6656
|
+
};
|
|
6657
|
+
return w.recording || w.loading || w.subs !== void 0 || w.pushing ? w : void 0;
|
|
6621
6658
|
}
|
|
6622
6659
|
/** A small picture of a video for its history row (made once, kept in its folder). */
|
|
6623
6660
|
async function thumbnail(id) {
|
|
@@ -6800,6 +6837,16 @@ function apply(ctx, config) {
|
|
|
6800
6837
|
} catch {}
|
|
6801
6838
|
return send(res, 200, { ok: true });
|
|
6802
6839
|
}
|
|
6840
|
+
if (route === "halt" && req.method === "POST") {
|
|
6841
|
+
const b = await readJson(req);
|
|
6842
|
+
const job = jobs.get(String(b.jobId));
|
|
6843
|
+
if (!job) return send(res, 404, { error: "no such job" });
|
|
6844
|
+
const s = job.summary();
|
|
6845
|
+
if (s.subs?.state === "running") await job.pauseSubs();
|
|
6846
|
+
if (s.restream?.running) await job.stopRestream();
|
|
6847
|
+
if ((s.status === "running" || s.status === "resolving") && !s.vod) await job.stop();
|
|
6848
|
+
return send(res, 200, { ok: true });
|
|
6849
|
+
}
|
|
6803
6850
|
if (route === "stop" && req.method === "POST") {
|
|
6804
6851
|
const b = await readJson(req);
|
|
6805
6852
|
const job = jobs.get(String(b.jobId));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vidi",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"description": "A video tool for DeepSeek Harness: watch live streams, videos and podcasts with real-time subtitles in any language, then clip and download",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|