dsh-vidi 0.9.4 → 0.9.5

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
@@ -35466,7 +35466,7 @@ function History({ t, ui, onOpen }) {
35466
35466
  onKeyDown: (e) => {
35467
35467
  if (e.key === "Enter") onOpen(v);
35468
35468
  },
35469
- title: v.live && v.source === "url" && !v.running ? t("hist.goLive") : void 0,
35469
+ title: v.live && v.source === "url" && !v.running && !v.work ? t("hist.goLive") : void 0,
35470
35470
  children: [
35471
35471
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
35472
35472
  className: "th",
@@ -36293,7 +36293,7 @@ function LiveView(props) {
36293
36293
  };
36294
36294
  const openPast = async (v, replay = false) => {
36295
36295
  setError("");
36296
- if (!replay && v.live && v.source === "url" && v.url && !v.running) {
36296
+ if (!replay && v.live && v.source === "url" && v.url && !v.running && !v.work) {
36297
36297
  if (v.conversationId !== sessionId && props.onOpenVideo && await props.onOpenVideo(v.conversationId, v.id, v.url)) return;
36298
36298
  start(v.url, "url");
36299
36299
  return;
@@ -37099,6 +37099,10 @@ function SubsButton(props) {
37099
37099
  style: { ["--p"]: `${pct ?? 0}%` },
37100
37100
  children: [
37101
37101
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { className: "dot" }),
37102
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
37103
+ className: "pz",
37104
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon.pause, {})
37105
+ }),
37102
37106
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
37103
37107
  className: "now",
37104
37108
  children: pct !== void 0 ? t("subs.progress").replace("{p}", String(pct)) : t("subs.progressLive").replace("{t}", CLOCK(at))
@@ -37117,7 +37121,14 @@ function SubsButton(props) {
37117
37121
  className: "dshl-subsbtn",
37118
37122
  onClick: onResume,
37119
37123
  title: t("subs.resume.tip").replace("{t}", CLOCK(at)),
37120
- children: [t("subs.resume"), pct !== void 0 ? ` · ${pct}%` : ""]
37124
+ children: [
37125
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
37126
+ className: "pz on",
37127
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon.play, {})
37128
+ }),
37129
+ t("subs.resume"),
37130
+ pct !== void 0 ? ` · ${pct}%` : ""
37131
+ ]
37121
37132
  });
37122
37133
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
37123
37134
  className: "dshl-subsbtn",
@@ -38918,7 +38929,12 @@ html[data-platform='darwin'] .dshl.page .dshl-top :is(button, input, label, a, .
38918
38929
  @container (max-width: 640px) { .dshl-subsbtn .now { display: none; } .dshl-subsbtn.running:not(:hover) .short { display: inline; } }
38919
38930
  .dshl-subsbtn.running:hover .now { display: none; }
38920
38931
  .dshl-subsbtn.running:hover .act { display: inline; }
38921
- .dshl-subsbtn.running:hover .dot { width: 8px; height: 8px; border-radius: 1px; background: #fff; animation: none; }
38932
+ .dshl-subsbtn .pz { display: none; margin: 0 -2px 0 -4px; }
38933
+ .dshl-subsbtn .pz svg { display: block; width: 15px; height: 15px; }
38934
+ .dshl-subsbtn .pz.on { display: inline-flex; }
38935
+ /* Hover on a running one: the pause sign stands where the pulsing dot was. */
38936
+ .dshl-subsbtn.running:hover .dot { display: none; }
38937
+ .dshl-subsbtn.running:hover .pz { display: inline-flex; }
38922
38938
  @container (max-width: 520px) { .dshl-subsbtn { margin: 0 2px; padding: 0 9px; } }
38923
38939
  .dshl-starter { position: relative; display: inline-flex; }
38924
38940
  .dshl-startpop { position: absolute; left: 50%; bottom: calc(100% + 26px); z-index: 6; display: grid; gap: 10px; width: 290px; padding: 12px 12px 14px; transform: translateX(-50%);
package/lib/index.mjs CHANGED
@@ -2021,6 +2021,22 @@ function cutNear(cs, ratio) {
2021
2021
  }
2022
2022
  return best > 0 ? best : Math.max(1, Math.min(n - 1, target));
2023
2023
  }
2024
+ /**
2025
+ * A line meant to be in `lang` that is written in another script: live interpretation now and then
2026
+ * passes the speaker's words through untranslated (Chinese in a Japanese line). Latin words (names,
2027
+ * brands) never count; a Japanese line has kana, a Chinese one none, a Korean one hangul.
2028
+ */
2029
+ function wrongScript(text, lang) {
2030
+ const n = (re) => (text.match(re) ?? []).length;
2031
+ const han = n(/[\u3400-\u9fff]/g), kana = n(/[\u3040-\u30ff]/g), hangul = n(/[\uac00-\ud7af\u1100-\u11ff]/g);
2032
+ const letters = n(/\p{L}/gu);
2033
+ const base = lang.split("-")[0];
2034
+ if (base === "ja") return kana === 0 && (han >= 4 || /[是吧嗯么们这那的呢吗啊呃对没还就说几]/.test(text)) || hangul >= 2;
2035
+ if (base === "zh") return kana >= 2 || hangul >= 2;
2036
+ if (base === "ko") return hangul === 0 && han + kana >= 3;
2037
+ const cjk = han + kana + hangul;
2038
+ return cjk >= 3 && cjk > letters * .3;
2039
+ }
2024
2040
  /** When a video was first opened: its folder is named after that moment (UTC); older saves could carry a later time. */
2025
2041
  function openedAt(id, saved) {
2026
2042
  const named = /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})/.exec(id);
@@ -2221,6 +2237,7 @@ var LiveJob = class LiveJob extends EventEmitter {
2221
2237
  state: j.subs?.state === "done" ? "done" : at > 0 ? "paused" : "off",
2222
2238
  at
2223
2239
  };
2240
+ setTimeout(() => job.repairEchoes(), 3e3);
2224
2241
  if (!existsSync(join(dir, "recording.mp4"))) job.remux();
2225
2242
  return job;
2226
2243
  } catch {
@@ -2857,20 +2874,49 @@ var LiveJob = class LiveJob extends EventEmitter {
2857
2874
  this.emit("cue", cue);
2858
2875
  if (cue.final) {
2859
2876
  this.deps.glossary.notice(cue.src);
2860
- if (this.opts.mode !== "polished" && !this.provider?.translates && cue.tr === void 0 && this.targets().length) this.translate(cue);
2877
+ const echoed = this.echoOf(cue);
2878
+ if (echoed) this.translate(cue, echoed);
2879
+ else if (this.opts.mode !== "polished" && !this.provider?.translates && cue.tr === void 0 && this.targets().length) this.translate(cue);
2861
2880
  else {
2862
2881
  this.record(cue);
2863
2882
  this.polisher.add(cue);
2864
2883
  }
2865
2884
  }
2866
2885
  }
2867
- translate(cue) {
2886
+ /**
2887
+ * Translations of a line that are in the wrong language: taken out (the line shows without them
2888
+ * until the proper one comes), and the text to translate from returned (the line's source, or the
2889
+ * echoed text itself when the line has none of its own: its source sits with the next line).
2890
+ */
2891
+ echoOf(cue) {
2892
+ let from;
2893
+ for (const t of this.targets()) {
2894
+ const x = cue.tr?.[t];
2895
+ if (!x || !wrongScript(x, t)) continue;
2896
+ from ??= cue.src.trim() || x;
2897
+ delete cue.tr[t];
2898
+ }
2899
+ if (from && cue.tr && !Object.keys(cue.tr).length) cue.tr = void 0;
2900
+ return from;
2901
+ }
2902
+ /** Lines saved with a translation in the wrong language (before this check existed): translate them again. */
2903
+ repairEchoes() {
2904
+ for (const cue of this.cues) {
2905
+ if (!cue.final || cue.edited) continue;
2906
+ const from = this.echoOf(cue);
2907
+ if (from) {
2908
+ this.emit("cue", cue);
2909
+ this.translate(cue, from);
2910
+ }
2911
+ }
2912
+ }
2913
+ translate(cue, text = cue.src) {
2868
2914
  const context = this.cues.filter((c) => c.final && c.id < cue.id).slice(-6);
2869
2915
  this.pendingTranslations++;
2870
2916
  this.translating = this.translating.then(async () => {
2871
2917
  try {
2872
2918
  const ref = this.siteText(cue.start, cue.end).map((x) => `[${x.lang}${x.auto ? ", auto" : ""}] ${x.text}`).join("\n");
2873
- const tr = await this.deps.translator.translate(this.targets(), cue.src, context, this.deps.glossary.forPrompt(), void 0, ref);
2919
+ const tr = await this.deps.translator.translate(this.targets(), text, context, this.deps.glossary.forPrompt(), void 0, ref);
2874
2920
  if (tr) {
2875
2921
  cue.tr = {
2876
2922
  ...cue.tr,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-vidi",
3
- "version": "0.9.4",
3
+ "version": "0.9.5",
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",