dsh-vidi 0.7.0 → 0.7.1
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 +50 -21
- package/lib/index.mjs +71 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -34066,10 +34066,21 @@ const Player = (0, react.forwardRef)(function Player(props, ref) {
|
|
|
34066
34066
|
]
|
|
34067
34067
|
}),
|
|
34068
34068
|
openMenu && (() => {
|
|
34069
|
+
const phone = narrow && window.innerWidth < 700 && !full && !pageFull;
|
|
34070
|
+
const floating = narrow && !phone && !full && !pageFull;
|
|
34071
|
+
const r = floating ? stage.current?.getBoundingClientRect() : void 0;
|
|
34072
|
+
const place = r ? {
|
|
34073
|
+
position: "fixed",
|
|
34074
|
+
right: Math.max(8, window.innerWidth - r.right + 8),
|
|
34075
|
+
bottom: Math.max(8, window.innerHeight - r.bottom + 56),
|
|
34076
|
+
width: Math.min(360, window.innerWidth - 16),
|
|
34077
|
+
maxHeight: Math.max(240, r.bottom - 56 - 12)
|
|
34078
|
+
} : void 0;
|
|
34069
34079
|
const panel = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34070
|
-
className: `dshl-menu ${
|
|
34080
|
+
className: `dshl-menu ${phone ? "sheet" : ""}`,
|
|
34071
34081
|
role: "dialog",
|
|
34072
34082
|
"aria-label": openMenu.title,
|
|
34083
|
+
style: place,
|
|
34073
34084
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34074
34085
|
className: "head",
|
|
34075
34086
|
children: [
|
|
@@ -34094,13 +34105,13 @@ const Player = (0, react.forwardRef)(function Player(props, ref) {
|
|
|
34094
34105
|
children: openMenu.content
|
|
34095
34106
|
})]
|
|
34096
34107
|
});
|
|
34097
|
-
return
|
|
34108
|
+
return phone ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
34098
34109
|
className: "dshl-sheetwrap",
|
|
34099
34110
|
onMouseDown: (e) => {
|
|
34100
34111
|
if (e.target === e.currentTarget) setMenu(null);
|
|
34101
34112
|
},
|
|
34102
34113
|
children: panel
|
|
34103
|
-
}), document.body) : panel;
|
|
34114
|
+
}), document.body) : floating ? (0, react_dom.createPortal)(panel, document.body) : panel;
|
|
34104
34115
|
})(),
|
|
34105
34116
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34106
34117
|
className: "dshl-controls",
|
|
@@ -34628,38 +34639,49 @@ function RestreamPanel(props) {
|
|
|
34628
34639
|
]
|
|
34629
34640
|
});
|
|
34630
34641
|
}
|
|
34631
|
-
/**
|
|
34642
|
+
/** Subtitle pages: the overlay for OBS's Browser Source, and the big screen of subtitles only. */
|
|
34632
34643
|
function OverlayLink({ t }) {
|
|
34633
|
-
const [
|
|
34634
|
-
const [copied, setCopied] = (0, react.useState)(
|
|
34644
|
+
const [urls, setUrls] = (0, react.useState)({});
|
|
34645
|
+
const [copied, setCopied] = (0, react.useState)("");
|
|
34635
34646
|
(0, react.useEffect)(() => {
|
|
34636
|
-
fetch("/live/api/overlay").then((r) => r.json()).then(
|
|
34647
|
+
fetch("/live/api/overlay").then((r) => r.json()).then(setUrls).catch(() => {});
|
|
34637
34648
|
}, []);
|
|
34638
|
-
if (!url) return null;
|
|
34639
|
-
|
|
34649
|
+
if (!urls.url) return null;
|
|
34650
|
+
const row = (label, url, note, open) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34640
34651
|
className: "rs-overlay",
|
|
34641
34652
|
children: [
|
|
34642
34653
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34643
34654
|
className: "rs-sub",
|
|
34644
|
-
children:
|
|
34655
|
+
children: label
|
|
34645
34656
|
}),
|
|
34646
34657
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
34647
34658
|
className: "rs-url",
|
|
34648
|
-
children: [
|
|
34649
|
-
|
|
34650
|
-
|
|
34651
|
-
|
|
34652
|
-
|
|
34659
|
+
children: [
|
|
34660
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: url }),
|
|
34661
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
34662
|
+
className: "dshl-link",
|
|
34663
|
+
onClick: () => void navigator.clipboard.writeText(url).then(() => {
|
|
34664
|
+
setCopied(url);
|
|
34665
|
+
setTimeout(() => setCopied(""), 1500);
|
|
34666
|
+
}),
|
|
34667
|
+
children: copied === url ? t("tr.copied") : t("rs.copy")
|
|
34653
34668
|
}),
|
|
34654
|
-
|
|
34655
|
-
|
|
34669
|
+
open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
34670
|
+
className: "dshl-link",
|
|
34671
|
+
href: url,
|
|
34672
|
+
target: "_blank",
|
|
34673
|
+
rel: "noreferrer",
|
|
34674
|
+
children: t("rs.openPage")
|
|
34675
|
+
})
|
|
34676
|
+
]
|
|
34656
34677
|
}),
|
|
34657
34678
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
34658
34679
|
className: "rs-hint",
|
|
34659
|
-
children:
|
|
34680
|
+
children: note
|
|
34660
34681
|
})
|
|
34661
34682
|
]
|
|
34662
34683
|
});
|
|
34684
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [row(t("rs.overlay"), urls.url, t("rs.overlay.d")), urls.screen && row(t("rs.screen"), urls.screenLan ?? urls.screen, t("rs.screen.d").replace("{local}", urls.screen), true)] });
|
|
34663
34685
|
}
|
|
34664
34686
|
|
|
34665
34687
|
// src/client/Settings.tsx
|
|
@@ -36299,7 +36321,7 @@ function SubsStarter(props) {
|
|
|
36299
36321
|
const box = (0, react.useRef)(null);
|
|
36300
36322
|
const pop = (0, react.useRef)(null);
|
|
36301
36323
|
const toggle = () => {
|
|
36302
|
-
setSheet(
|
|
36324
|
+
setSheet(window.innerWidth < 700 && !document.fullscreenElement);
|
|
36303
36325
|
setOpen((v) => !v);
|
|
36304
36326
|
};
|
|
36305
36327
|
(0, react.useEffect)(() => {
|
|
@@ -36801,6 +36823,9 @@ const en = {
|
|
|
36801
36823
|
"rs.overlay": "Subtitles for OBS (Browser Source)",
|
|
36802
36824
|
"rs.overlay.d": "Add a Browser Source in OBS with this address (1920×1080): the newest subtitles on a transparent background, over your camera or any picture. Add ?lines=zh,src&size=4.5 to choose lines and size.",
|
|
36803
36825
|
"rs.copy": "Copy",
|
|
36826
|
+
"rs.openPage": "Open",
|
|
36827
|
+
"rs.screen": "Subtitle screen (16:9)",
|
|
36828
|
+
"rs.screen.d": "Only the subtitles, large, for a big screen or projector: open it in any browser on this network and go full screen (on this computer: {local}). Add ?theme=light for a light screen; it can also be an OBS source of its own.",
|
|
36804
36829
|
"rs.content": "Sends",
|
|
36805
36830
|
"rs.c.burn": "Picture + subtitles",
|
|
36806
36831
|
"rs.c.clean": "Picture only",
|
|
@@ -37179,6 +37204,9 @@ const zh = {
|
|
|
37179
37204
|
"rs.overlay": "OBS 字幕叠加(浏览器源)",
|
|
37180
37205
|
"rs.overlay.d": "在 OBS 里加一个「浏览器源」,填这个地址(1920×1080):透明背景上只有最新的字幕,叠在摄像头或任何画面上。地址后加 ?lines=zh,src&size=4.5 可以选行和字号。",
|
|
37181
37206
|
"rs.copy": "复制",
|
|
37207
|
+
"rs.openPage": "打开",
|
|
37208
|
+
"rs.screen": "字幕大屏(16:9)",
|
|
37209
|
+
"rs.screen.d": "只有字幕、字很大,适合大屏和投影:在同一网络里任何浏览器打开、全屏即可(本机用 {local})。地址后加 ?theme=light 换浅色;也可以作为 OBS 的一个独立画面来源。",
|
|
37182
37210
|
"rs.content": "内容",
|
|
37183
37211
|
"rs.c.burn": "画面 + 字幕",
|
|
37184
37212
|
"rs.c.clean": "只要画面",
|
|
@@ -37966,8 +37994,9 @@ html[data-platform='darwin'] .dshl.page .dshl-top :is(button, input, label, a, .
|
|
|
37966
37994
|
.dshl-dl .dshl-chips .chip { background: transparent; }
|
|
37967
37995
|
|
|
37968
37996
|
/* Menus as a bottom sheet on small players (phones) */
|
|
37969
|
-
.dshl-sheetwrap { position: fixed; inset: 0; z-index: 2147483002; display: flex; align-items: flex-end; background: rgba(0,0,0,.4); }
|
|
37970
|
-
.dshl-menu
|
|
37997
|
+
.dshl-sheetwrap { position: fixed; inset: 0; z-index: 2147483002; display: flex; align-items: flex-end; justify-content: center; background: rgba(0,0,0,.4); }
|
|
37998
|
+
body > .dshl-menu { z-index: 2147483002; }
|
|
37999
|
+
.dshl-menu.sheet { position: relative; right: auto; bottom: auto; width: 100%; max-width: 560px; max-height: 78vh; padding-bottom: env(safe-area-inset-bottom); border: 0; border-radius: 16px 16px 0 0;
|
|
37971
38000
|
font-family: system-ui, -apple-system, "PingFang SC", sans-serif; font-size: 15px; }
|
|
37972
38001
|
.dshl-menu.sheet .head { padding: 14px 12px 8px 18px; font-size: 16px; }
|
|
37973
38002
|
.dshl-menu.sheet .body { padding: 4px 14px 16px; }
|
package/lib/index.mjs
CHANGED
|
@@ -5046,11 +5046,19 @@ function startOverlay(port, current, onJob, log) {
|
|
|
5046
5046
|
res.end(PAGE);
|
|
5047
5047
|
return;
|
|
5048
5048
|
}
|
|
5049
|
+
if (url.pathname === "/screen" || url.pathname === "/screen/") {
|
|
5050
|
+
res.writeHead(200, {
|
|
5051
|
+
"content-type": "text/html; charset=utf-8",
|
|
5052
|
+
"cache-control": "no-store"
|
|
5053
|
+
});
|
|
5054
|
+
res.end(SCREEN);
|
|
5055
|
+
return;
|
|
5056
|
+
}
|
|
5049
5057
|
res.writeHead(404);
|
|
5050
5058
|
res.end();
|
|
5051
5059
|
});
|
|
5052
5060
|
server.on("error", (err) => log(`overlay: port ${port} unavailable (${err.message})`));
|
|
5053
|
-
server.listen(port,
|
|
5061
|
+
server.listen(port, () => log(`overlay: http://127.0.0.1:${port}/overlay, /screen`));
|
|
5054
5062
|
return () => {
|
|
5055
5063
|
offJob();
|
|
5056
5064
|
detach?.();
|
|
@@ -5098,6 +5106,63 @@ function connect() {
|
|
|
5098
5106
|
}
|
|
5099
5107
|
connect()
|
|
5100
5108
|
<\/script></body></html>`;
|
|
5109
|
+
/**
|
|
5110
|
+
* Subtitles as the whole picture, for a big screen (a hall, a projector, a
|
|
5111
|
+
* second monitor, or OBS as a source of its own): 16:9, the current line large
|
|
5112
|
+
* with the original under it, the two before it smaller and dimmed above.
|
|
5113
|
+
* …/screen?lines=zh,src&theme=dark|light
|
|
5114
|
+
*/
|
|
5115
|
+
const SCREEN = `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Vidi · subtitles</title>
|
|
5116
|
+
<style>
|
|
5117
|
+
html,body{margin:0;height:100%;background:#000;overflow:hidden;cursor:none}
|
|
5118
|
+
body.light{background:#f4f3ef}
|
|
5119
|
+
#stage{position:absolute;inset:0;margin:auto;width:min(100vw,177.78vh);height:min(56.25vw,100vh);display:flex;flex-direction:column;justify-content:flex-end;box-sizing:border-box;padding:4% 6% 7%;
|
|
5120
|
+
font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC",system-ui,sans-serif;color:#fff;container-type:inline-size}
|
|
5121
|
+
body.light #stage{color:#151513}
|
|
5122
|
+
.cue{text-align:center;text-wrap:balance;line-break:strict;transition:opacity .4s,font-size .3s}
|
|
5123
|
+
.cue+.cue{margin-top:3.2cqw}
|
|
5124
|
+
.cue .main{display:block;font-weight:600;line-height:1.35}
|
|
5125
|
+
.cue .sub{display:block;margin-top:.8cqw;opacity:.62;line-height:1.4}
|
|
5126
|
+
.cue.now .main{font-size:5.4cqw}.cue.now .sub{font-size:2.9cqw}
|
|
5127
|
+
.cue.old .main{font-size:3cqw}.cue.old .sub{font-size:1.9cqw}
|
|
5128
|
+
.cue.old{opacity:.38}.cue.old2{opacity:.2}
|
|
5129
|
+
.cue.draft .main{opacity:.75}
|
|
5130
|
+
</style></head><body><div id="stage"></div>
|
|
5131
|
+
<script>
|
|
5132
|
+
const q = new URLSearchParams(location.search)
|
|
5133
|
+
const lines = (q.get('lines') || 'zh,src').split(',')
|
|
5134
|
+
if (q.get('theme') === 'light') document.body.classList.add('light')
|
|
5135
|
+
const stage = document.getElementById('stage')
|
|
5136
|
+
let done = [], draft = null
|
|
5137
|
+
function texts(c) {
|
|
5138
|
+
const seen = new Set()
|
|
5139
|
+
return lines.map(k => (k === 'src' ? c.src : (c.tr || {})[k]) || '').map(s => s.trim()).filter(s => s && !seen.has(s) && seen.add(s))
|
|
5140
|
+
}
|
|
5141
|
+
function render() {
|
|
5142
|
+
const list = [...done.slice(-3), ...(draft ? [draft] : [])].slice(-3)
|
|
5143
|
+
stage.innerHTML = ''
|
|
5144
|
+
list.forEach((c, i) => {
|
|
5145
|
+
const t = texts(c); if (!t.length) return
|
|
5146
|
+
const d = document.createElement('div')
|
|
5147
|
+
const age = list.length - 1 - i
|
|
5148
|
+
d.className = 'cue ' + (age === 0 ? 'now' : age === 1 ? 'old' : 'old old2') + (c.final ? '' : ' draft')
|
|
5149
|
+
const m = document.createElement('span'); m.className = 'main'; m.textContent = t[0]; d.appendChild(m)
|
|
5150
|
+
if (t[1]) { const s = document.createElement('span'); s.className = 'sub'; s.textContent = t[1]; d.appendChild(s) }
|
|
5151
|
+
stage.appendChild(d)
|
|
5152
|
+
})
|
|
5153
|
+
}
|
|
5154
|
+
function connect() {
|
|
5155
|
+
const es = new EventSource('/overlay/events')
|
|
5156
|
+
es.onmessage = e => {
|
|
5157
|
+
let c; try { c = JSON.parse(e.data) } catch { return }
|
|
5158
|
+
if (!c) return
|
|
5159
|
+
if (c.final) { draft = null; if (!done.length || done.at(-1).src !== c.src) done.push(c); done = done.slice(-6) } else draft = c
|
|
5160
|
+
render()
|
|
5161
|
+
}
|
|
5162
|
+
es.onerror = () => { es.close(); setTimeout(connect, 2000) }
|
|
5163
|
+
}
|
|
5164
|
+
connect()
|
|
5165
|
+
<\/script></body></html>`;
|
|
5101
5166
|
// src/tools.ts
|
|
5102
5167
|
const CLOCK = (s) => {
|
|
5103
5168
|
const h = Math.floor(s / 3600), m = Math.floor(s / 60) % 60, ss = Math.floor(s % 60);
|
|
@@ -5865,7 +5930,11 @@ function apply(ctx, config) {
|
|
|
5865
5930
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
5866
5931
|
const route = url.pathname.replace(/^\/live\/api\/?/, "");
|
|
5867
5932
|
try {
|
|
5868
|
-
if (route === "overlay") return send(res, 200, {
|
|
5933
|
+
if (route === "overlay") return send(res, 200, {
|
|
5934
|
+
url: `http://127.0.0.1:${config.overlayPort}/overlay`,
|
|
5935
|
+
screen: `http://127.0.0.1:${config.overlayPort}/screen`,
|
|
5936
|
+
screenLan: `http://${lanAddress()}:${config.overlayPort}/screen`
|
|
5937
|
+
});
|
|
5869
5938
|
if (route === "deps") {
|
|
5870
5939
|
const [deps] = await Promise.all([checkDeps(config.whisperModel), keys.refresh()]);
|
|
5871
5940
|
localReady = deps.filter((d) => [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vidi",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
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",
|