dsh-vidi 0.7.2 → 0.7.3

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 CHANGED
@@ -35188,6 +35188,80 @@ function upload(file, onProgress) {
35188
35188
  });
35189
35189
  }
35190
35190
  /** A value to copy: plain text (not an input look-alike) and a text button. */
35191
+ /** The push stream key: copy it, or change it (typed, or a new random one). */
35192
+ function KeyRow({ value, t, onChanged }) {
35193
+ const [edit, setEdit] = (0, react.useState)(null);
35194
+ const [err, setErr] = (0, react.useState)("");
35195
+ const [done, setDone] = (0, react.useState)(false);
35196
+ const save = async (body) => {
35197
+ if (typeof body.key === "string" && !/^[A-Za-z0-9_-]{3,40}$/.test(body.key.trim())) {
35198
+ setErr(t("push.badKey"));
35199
+ return;
35200
+ }
35201
+ try {
35202
+ await post("pushkey", body);
35203
+ setEdit(null);
35204
+ setErr("");
35205
+ onChanged();
35206
+ } catch (e) {
35207
+ setErr(e.message);
35208
+ }
35209
+ };
35210
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("push.key") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", { children: [edit === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
35211
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
35212
+ className: "val",
35213
+ children: value
35214
+ }),
35215
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
35216
+ className: "dshl-link",
35217
+ onClick: () => {
35218
+ navigator.clipboard.writeText(value);
35219
+ setDone(true);
35220
+ setTimeout(() => setDone(false), 1500);
35221
+ },
35222
+ children: done ? t("push.copied") : t("push.copy")
35223
+ }),
35224
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
35225
+ className: "dshl-link",
35226
+ onClick: () => setEdit(value),
35227
+ children: t("push.change")
35228
+ })
35229
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
35230
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
35231
+ className: "dshl-keyedit",
35232
+ value: edit,
35233
+ spellCheck: false,
35234
+ autoFocus: true,
35235
+ onFocus: (e) => e.target.select(),
35236
+ onChange: (e) => setEdit(e.target.value),
35237
+ onKeyDown: (e) => {
35238
+ if (e.key === "Enter") save({ key: edit });
35239
+ if (e.key === "Escape") setEdit(null);
35240
+ }
35241
+ }),
35242
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
35243
+ className: "dshl-link",
35244
+ onClick: () => void save({ key: edit }),
35245
+ children: t("push.save")
35246
+ }),
35247
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
35248
+ className: "dshl-link",
35249
+ onClick: () => void save({ random: true }),
35250
+ children: t("push.random")
35251
+ }),
35252
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
35253
+ className: "dshl-link",
35254
+ onClick: () => {
35255
+ setEdit(null);
35256
+ setErr("");
35257
+ },
35258
+ children: t("player.cancel")
35259
+ })
35260
+ ] }), err && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
35261
+ className: "err",
35262
+ children: err
35263
+ })] })] });
35264
+ }
35191
35265
  function CopyRow({ label, value, copy, copied }) {
35192
35266
  const [done, setDone] = (0, react.useState)(false);
35193
35267
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -35781,11 +35855,10 @@ function LiveView(props) {
35781
35855
  value: armed.rtmp,
35782
35856
  copy: t("push.copy"),
35783
35857
  copied: t("push.copied")
35784
- }), armed.key && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyRow, {
35785
- label: t("push.key"),
35858
+ }), armed.key && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyRow, {
35786
35859
  value: armed.key,
35787
- copy: t("push.copy"),
35788
- copied: t("push.copied")
35860
+ t,
35861
+ onChanged: () => void post("arm", options()).then((a) => setArmed(a.busy ? null : a)).catch(() => {})
35789
35862
  })] })]
35790
35863
  })
35791
35864
  ]
@@ -36878,6 +36951,10 @@ const en = {
36878
36951
  "rs.overlay": "Subtitles for OBS (Browser Source)",
36879
36952
  "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.",
36880
36953
  "rs.copy": "Copy",
36954
+ "push.change": "Change",
36955
+ "push.save": "Save",
36956
+ "push.random": "New random key",
36957
+ "push.badKey": "3–40 letters, digits, - or _",
36881
36958
  "side.hide": "Hide the chat",
36882
36959
  "side.show": "Show the chat",
36883
36960
  "side.resize": "Drag to change the width",
@@ -37262,6 +37339,10 @@ const zh = {
37262
37339
  "rs.overlay": "OBS 字幕叠加(浏览器源)",
37263
37340
  "rs.overlay.d": "在 OBS 里加一个「浏览器源」,填这个地址(1920×1080):透明背景上只有最新的字幕,叠在摄像头或任何画面上。地址后加 ?lines=zh,src&size=4.5 可以选行和字号。",
37264
37341
  "rs.copy": "复制",
37342
+ "push.change": "修改",
37343
+ "push.save": "保存",
37344
+ "push.random": "换一个",
37345
+ "push.badKey": "用 3–40 位字母、数字、- 或 _",
37265
37346
  "side.hide": "收起对话",
37266
37347
  "side.show": "展开对话",
37267
37348
  "side.resize": "拖动调整宽度",
@@ -37761,6 +37842,10 @@ const CSS = `
37761
37842
  .dshl-pushinfo dl { display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 6px 16px; margin: 0; }
37762
37843
  .dshl-pushinfo dt { color: var(--faint); font-size: 12px; }
37763
37844
  .dshl-pushinfo dd { display: flex; align-items: center; gap: 14px; min-width: 0; margin: 0; }
37845
+ .dshl-keyedit { width: 140px; height: 26px; padding: 0 8px; border: 1px solid var(--line); border-radius: 6px; background: var(--bg); color: var(--ink); font: 13px ui-monospace, SFMono-Regular, Menlo, monospace; }
37846
+ .dshl-keyedit:focus { outline: none; border-color: var(--accent); }
37847
+ .dshl-pushinfo dd:has(.err) { flex-wrap: wrap; row-gap: 4px; }
37848
+ .dshl-pushinfo dd .err { flex-basis: 100%; color: #d1434b; font-size: 12px; }
37764
37849
  .dshl-pushinfo .val { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; user-select: all; }
37765
37850
  .dshl-pushinfo .dshl-link { flex: none; font-size: 12.5px; }
37766
37851
 
package/lib/index.mjs CHANGED
@@ -2289,11 +2289,28 @@ var LiveJob = class LiveJob extends EventEmitter {
2289
2289
  }
2290
2290
  }, 400);
2291
2291
  }
2292
- ff.stderr.on("data", (d) => appendFileSync(join(this.deps.dir, "ffmpeg.log"), d));
2292
+ let rejected = false;
2293
+ ff.stderr.on("data", (d) => {
2294
+ appendFileSync(join(this.deps.dir, "ffmpeg.log"), d);
2295
+ if (this.opts.source === "push" && !rejected && /Unexpected stream/.test(d.toString())) {
2296
+ rejected = true;
2297
+ ff.kill("SIGKILL");
2298
+ }
2299
+ });
2293
2300
  ff.on("exit", (code) => {
2294
2301
  if (this.ffmpeg !== ff) return;
2295
2302
  this.ffmpeg = void 0;
2296
2303
  if (this.status !== "running") return;
2304
+ if (this.opts.source === "push" && this.message && (rejected || !this.segments().length)) {
2305
+ if (rejected) rmSync(hls, {
2306
+ recursive: true,
2307
+ force: true
2308
+ });
2309
+ setTimeout(() => {
2310
+ if (this.status === "running" && !this.ffmpeg) this.spawnFfmpeg(input);
2311
+ }, 500);
2312
+ return;
2313
+ }
2297
2314
  this.finishCapture(code === 0 ? "ended" : this.ffmpegError(code), code === 0 || this.segments().length > 0);
2298
2315
  });
2299
2316
  }
@@ -5880,7 +5897,14 @@ function apply(ctx, config) {
5880
5897
  * page owns it.
5881
5898
  */
5882
5899
  let armed;
5883
- const PUSH_KEY = "dsh";
5900
+ const hadSettings = existsSync(settingsFile);
5901
+ const pushKey = () => {
5902
+ const saved = prefs().pushKey;
5903
+ if (saved) return saved;
5904
+ const key = hadSettings ? "dsh" : Math.random().toString(36).slice(2, 10);
5905
+ savePrefs({ pushKey: key });
5906
+ return key;
5907
+ };
5884
5908
  async function disarm() {
5885
5909
  const a = armed;
5886
5910
  armed = void 0;
@@ -5952,7 +5976,7 @@ function apply(ctx, config) {
5952
5976
  host: lanAddress(),
5953
5977
  rtmpPort: config.rtmpPort,
5954
5978
  srtPort: config.srtPort,
5955
- key: waiting ? PUSH_KEY : void 0
5979
+ key: waiting ? pushKey() : void 0
5956
5980
  },
5957
5981
  cookies: () => prefs().cookies
5958
5982
  };
@@ -6182,6 +6206,15 @@ function apply(ctx, config) {
6182
6206
  }
6183
6207
  return send(res, 200, job.summary());
6184
6208
  }
6209
+ if (route === "pushkey" && req.method === "POST") {
6210
+ if ([...jobs.values()].some((j) => j.opts.source === "push" && j.status === "running" && !j.message)) return send(res, 409, { error: "a stream is coming in; change the key after it ends" });
6211
+ const b = await readJson(req);
6212
+ const key = b.random ? Math.random().toString(36).slice(2, 10) : String(b.key ?? "").trim();
6213
+ if (!/^[A-Za-z0-9_-]{3,40}$/.test(key)) return send(res, 400, { error: "use 3–40 letters, digits, - or _" });
6214
+ savePrefs({ pushKey: key });
6215
+ await disarm();
6216
+ return send(res, 200, { key });
6217
+ }
6185
6218
  if (route === "arm" && req.method === "POST") {
6186
6219
  const b = await readJson(req);
6187
6220
  return send(res, 200, await arm({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-vidi",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
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",