@hypit/hypit 0.1.14 → 0.2.0
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/README.md +2 -2
- package/dist/public/browser-capture.d.ts +9 -3
- package/dist/public/caption.d.ts +2 -0
- package/dist/public/narrative.d.ts +2 -0
- package/dist/public/runtime-kit.d.ts +2 -2
- package/dist/public/speech.d.ts +2 -0
- package/examples/semantic-composition/packages/responsive-explainer/README.md +1 -1
- package/package.json +1 -1
- package/packages/browser-capture/README.md +9 -2
- package/packages/browser-capture/package.json +5 -0
- package/packages/browser-capture/src/browser.ts +22 -8
- package/packages/browser-capture/src/index.ts +8 -2
- package/packages/caption/README.md +1 -1
- package/packages/caption/src/display.ts +1 -0
- package/packages/caption-fine/README.md +19 -5
- package/packages/caption-fine/package.json +1 -0
- package/packages/caption-fine/src/joined-box.ts +114 -0
- package/packages/caption-fine/src/manifest.ts +1 -1
- package/packages/caption-fine/src/render.ts +99 -92
- package/packages/caption-fine/src/spacing.ts +8 -61
- package/packages/caption-fine-studio/src/index.ts +2 -4
- package/packages/cli/README.md +3 -2
- package/packages/cli/src/arguments.ts +2 -2
- package/packages/cli/src/command.ts +1 -1
- package/packages/cli/src/commands/environment.ts +9 -2
- package/packages/cli/src/machine-view.ts +1 -1
- package/packages/cli/src/output.ts +1 -0
- package/packages/credential-store-os/src/store.ts +5 -1
- package/packages/narrative/README.md +3 -1
- package/packages/narrative/src/schema.ts +1 -0
- package/packages/narrative/src/types.ts +2 -0
- package/packages/provider-whisperx-local/README.md +26 -10
- package/packages/provider-whisperx-local/src/activation.ts +10 -1
- package/packages/provider-whisperx-local/src/program.ts +8 -4
- package/packages/runtime-host-node/src/index.ts +1 -0
- package/packages/runtime-kit/README.md +6 -2
- package/packages/runtime-kit/src/index.ts +2 -2
- package/packages/runtime-local/README.md +6 -1
- package/packages/runtime-local/src/host.ts +2 -0
- package/packages/runtime-local/src/index.ts +1 -0
- package/packages/runtime-local/src/programs.ts +53 -17
- package/packages/script/README.md +81 -34
- package/packages/script/bin/migrate-0.2.mjs +90 -0
- package/packages/script/src/edit.ts +28 -60
- package/packages/script/src/lexical.ts +59 -130
- package/packages/script/src/manifest.ts +11 -11
- package/packages/script/src/narrative.ts +4 -2
- package/packages/script/src/parser.ts +140 -193
- package/packages/script/src/types.ts +2 -3
- package/packages/studio/src/ui/code.ts +1 -1
- package/packages/studio/src/ui/main.ts +2 -2
- package/packages/studio/src/ui/markers.ts +3 -3
- package/packages/studio/src/ui/selection.ts +1 -1
- package/packages/studio/src/ui/syntax.ts +42 -21
- package/packages/temporal-markup/README.md +1 -1
- package/packages/video-cli/README.md +9 -5
- package/packages/video-cli/src/capture.ts +17 -2
- package/packages/video-cli/src/creation.ts +4 -6
- package/packages/video-cli/src/media.ts +10 -3
- package/packages/whisperx/README.md +56 -3
- package/packages/whisperx/src/component.ts +2 -5
- package/packages/whisperx/src/evidence.ts +4 -3
- package/packages/whisperx/src/index.ts +1 -0
- package/packages/whisperx/src/manifest.ts +2 -3
- package/packages/whisperx/src/surface.ts +3 -6
- package/packages/whisperx/src/types.ts +9 -1
- package/packages/yt-dlp/README.md +9 -3
- package/packages/yt-dlp/package.json +2 -1
- package/packages/yt-dlp/src/download.ts +10 -24
- package/packages/yt-dlp/src/environment.ts +36 -0
- package/packages/yt-dlp/src/index.ts +1 -0
- package/services/whisperx/README.md +45 -12
- package/services/whisperx/src/hypit_whisperx_service/application.py +3 -0
- package/services/whisperx/src/hypit_whisperx_service/check.py +7 -0
- package/services/whisperx/src/hypit_whisperx_service/config.py +7 -0
- package/services/whisperx/src/hypit_whisperx_service/engine.py +21 -5
- package/services/whisperx/src/hypit_whisperx_service/models.py +131 -0
- package/services/whisperx/src/hypit_whisperx_service/prepare.py +21 -3
- package/services/whisperx/src/hypit_whisperx_service/resources.py +19 -3
- package/services/yt-dlp/README.md +14 -15
- package/services/yt-dlp/pyproject.toml +1 -1
- package/services/yt-dlp/uv.lock +513 -2
|
@@ -8,19 +8,22 @@ import type {
|
|
|
8
8
|
VisualColorPaint,
|
|
9
9
|
VisualElement,
|
|
10
10
|
VisualKeyframe,
|
|
11
|
+
VisualProgramElement,
|
|
11
12
|
VisualStyleDeclaration,
|
|
12
13
|
VisualTextElement,
|
|
13
14
|
VisualTextPaintLayer,
|
|
14
15
|
VisualTrack,
|
|
15
16
|
} from "@hypit/composition";
|
|
16
|
-
import type { CaptionAlignmentUnit, CaptionDocument } from "@hypit/narrative";
|
|
17
|
+
import type { CaptionAlignmentUnit, CaptionDocument, CaptionDisplayWord } from "@hypit/narrative";
|
|
17
18
|
import { assertProgramSpaceIdentity, programSpaceFrameCount } from "@hypit/program-space";
|
|
18
19
|
import { assertSpatialRegionTimeline } from "@hypit/spatial";
|
|
19
20
|
import type { CanvasSpace, SpatialFrame, SpatialRegionTimeline } from "@hypit/spatial";
|
|
20
21
|
|
|
21
22
|
import { assertFineCaptionParameters, FINE_CAPTION_FAMILY } from "./style.js";
|
|
22
23
|
import { assertFineCaptionSchedule } from "./schedule.js";
|
|
23
|
-
import {
|
|
24
|
+
import { uniformGap, wordGaps } from "./spacing.js";
|
|
25
|
+
import { browserProgram } from "@hypit/hyperframes";
|
|
26
|
+
import { joinedBoxSetup } from "./joined-box.js";
|
|
24
27
|
import type {
|
|
25
28
|
FineCaptionActiveUnderline,
|
|
26
29
|
FineCaptionGlyphPaint,
|
|
@@ -621,20 +624,17 @@ function trackedPlacementAnimation(
|
|
|
621
624
|
return { keyframes };
|
|
622
625
|
}
|
|
623
626
|
|
|
624
|
-
function
|
|
625
|
-
|
|
626
|
-
maxWordsPerLine: number,
|
|
627
|
-
): number {
|
|
628
|
-
let rows = atoms.length === 0 ? 0 : 1;
|
|
627
|
+
function structuralRowStarts(atoms: readonly CaptionAlignmentUnit[], maxWordsPerLine?: number): ReadonlySet<number> {
|
|
628
|
+
const starts = new Set<number>();
|
|
629
629
|
let wordsOnRow = 0;
|
|
630
|
-
for (const atom of atoms) {
|
|
631
|
-
if (wordsOnRow > 0 && wordsOnRow + atom.wordIds.length > maxWordsPerLine) {
|
|
632
|
-
|
|
630
|
+
for (const [index, atom] of atoms.entries()) {
|
|
631
|
+
if (index === 0 || (maxWordsPerLine !== undefined && wordsOnRow > 0 && wordsOnRow + atom.wordIds.length > maxWordsPerLine)) {
|
|
632
|
+
starts.add(index);
|
|
633
633
|
wordsOnRow = 0;
|
|
634
634
|
}
|
|
635
635
|
wordsOnRow += atom.wordIds.length;
|
|
636
636
|
}
|
|
637
|
-
return
|
|
637
|
+
return starts;
|
|
638
638
|
}
|
|
639
639
|
|
|
640
640
|
/**
|
|
@@ -665,7 +665,7 @@ function cueElements(
|
|
|
665
665
|
atoms: readonly CaptionAlignmentUnit[],
|
|
666
666
|
atomFrames: ReadonlyMap<string, { readonly start: number; readonly end: number }>,
|
|
667
667
|
parameters: FineCaptionParameters,
|
|
668
|
-
wordText: ReadonlyMap<string,
|
|
668
|
+
wordText: ReadonlyMap<string, CaptionDisplayWord>,
|
|
669
669
|
durationFrames: number,
|
|
670
670
|
styleId: string,
|
|
671
671
|
trackedPlacement?: {
|
|
@@ -673,7 +673,7 @@ function cueElements(
|
|
|
673
673
|
readonly canvas: CanvasSpace;
|
|
674
674
|
},
|
|
675
675
|
): VisualElement[] {
|
|
676
|
-
type UnorderedVisualElement = Omit<VisualBoxElement, "order"> | Omit<VisualTextElement, "order">;
|
|
676
|
+
type UnorderedVisualElement = Omit<VisualBoxElement, "order"> | Omit<VisualTextElement, "order"> | Omit<VisualProgramElement, "order">;
|
|
677
677
|
const elements: VisualElement[] = [];
|
|
678
678
|
let order = 0;
|
|
679
679
|
const push = (element: UnorderedVisualElement): void => {
|
|
@@ -694,21 +694,29 @@ function cueElements(
|
|
|
694
694
|
const cueLoop = parameters.motion.loopTarget === "cue" ? loopAnimation(parameters, durationFrames) : undefined;
|
|
695
695
|
const activationFrames = exclusiveActivationFrames(atoms, atomFrames);
|
|
696
696
|
const gapPx = `${compactNumber(parameters.layout.wordGapPx)}px`;
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const cueGap = uniformGap(atomGaps);
|
|
697
|
+
const atomWords = atoms.map(atom => atom.wordIds.map(wordId => {
|
|
698
|
+
const word = wordText.get(wordId);
|
|
699
|
+
if (!word) throw new Error(`Fine Caption Atom references unknown word ${wordId}`);
|
|
700
|
+
return word;
|
|
701
|
+
}));
|
|
702
|
+
const atomSurfaces = atomWords.map(words => words.map(word => word.text));
|
|
703
|
+
const atomGaps = atomWords.map((words, index) => index > 0 && words[0]!.separatorBefore === " ");
|
|
705
704
|
// The Visual IR carries physical margins, so the leading edge follows the Cue's own direction.
|
|
706
705
|
const marginStart = parameters.layout.direction === "rtl" ? "margin-right" : "margin-left";
|
|
707
706
|
const spacedStyle = (spaced: boolean): readonly VisualStyleDeclaration[] =>
|
|
708
707
|
spaced ? [{ name: marginStart, value: gapPx }] : [];
|
|
708
|
+
const atomFlow = (words: readonly CaptionDisplayWord[]): VisualStyleDeclaration[] => [
|
|
709
|
+
...(uniformGap(wordGaps(words)) === true ? [{ name: "column-gap", value: gapPx }] : []),
|
|
710
|
+
{ name: "display", value: "inline-flex" },
|
|
711
|
+
{ name: "min-width", value: "0" },
|
|
712
|
+
{ name: "overflow-wrap", value: "anywhere" },
|
|
713
|
+
{ name: "white-space", value: "normal" },
|
|
714
|
+
{ name: "word-break", value: parameters.layout.wrap === "grapheme" ? "break-all" : "normal" },
|
|
715
|
+
];
|
|
709
716
|
const fonts = parameters.typography.exactFonts;
|
|
717
|
+
const rowStarts = structuralRowStarts(atoms, parameters.layout.maxWordsPerLine);
|
|
710
718
|
if (parameters.layout.maxLines !== undefined) {
|
|
711
|
-
const rows =
|
|
719
|
+
const rows = rowStarts.size;
|
|
712
720
|
if (rows > parameters.layout.maxLines) {
|
|
713
721
|
throw new Error(`Fine Caption Style ${styleId} constructs ${rows} rows for a Cue whose maximum is ${parameters.layout.maxLines}`);
|
|
714
722
|
}
|
|
@@ -766,10 +774,10 @@ function cueElements(
|
|
|
766
774
|
{ name: "border-radius", value: `${compactNumber(parameters.cueBox.radiusPx)}px` },
|
|
767
775
|
{ name: "border-style", value: "solid" },
|
|
768
776
|
{ name: "border-width", value: `${compactNumber(parameters.cueBox.borderWidthPx)}px` },
|
|
769
|
-
...(cueGap === true ? [{ name: "column-gap", value: gapPx }] as const : []),
|
|
770
777
|
{ name: "direction", value: parameters.layout.direction },
|
|
771
|
-
{ name: "display", value: "
|
|
772
|
-
{ name: "
|
|
778
|
+
{ name: "display", value: "block" },
|
|
779
|
+
{ name: "font-size", value: "0px" },
|
|
780
|
+
{ name: "line-height", value: "0" },
|
|
773
781
|
{ name: "justify-content", value: parameters.layout.textAlign === "left" ? "flex-start"
|
|
774
782
|
: parameters.layout.textAlign === "right" ? "flex-end" : "center" },
|
|
775
783
|
{ name: "max-width", value: "100%" },
|
|
@@ -781,64 +789,73 @@ function cueElements(
|
|
|
781
789
|
],
|
|
782
790
|
});
|
|
783
791
|
|
|
792
|
+
const pushSeparator = (id: string, parent: string): void => push({
|
|
793
|
+
id, parent, kind: "text", text: " ", fonts,
|
|
794
|
+
style: [
|
|
795
|
+
{ name: "display", value: "inline" },
|
|
796
|
+
{ name: "font-size", value: "0px" },
|
|
797
|
+
{ name: "letter-spacing", value: gapPx },
|
|
798
|
+
{ name: "line-height", value: "0" },
|
|
799
|
+
{ name: "white-space", value: "normal" },
|
|
800
|
+
],
|
|
801
|
+
});
|
|
802
|
+
|
|
784
803
|
if (parameters.activeBox.mode === "trail" && parameters.activeBox.continuity === "joined") {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
804
|
+
// Fine owns this drawing program. The renderer only executes its existing browser-program
|
|
805
|
+
// format; it learns nothing about captions, activation units or their layout rules.
|
|
806
|
+
push({
|
|
807
|
+
id: "joined-boxes", parent: "cue", kind: "program",
|
|
808
|
+
style: [
|
|
809
|
+
{ name: "position", value: "absolute" }, { name: "inset", value: "0" },
|
|
810
|
+
{ name: "padding", value: `${compactNumber(parameters.cueBox.paddingYPx)}px ${compactNumber(parameters.cueBox.paddingXPx)}px` },
|
|
811
|
+
],
|
|
812
|
+
program: browserProgram({
|
|
813
|
+
html: "{{joined-layout}}" + atoms.map((_, index) => `{{joined-box-${index + 1}}}`).join(""),
|
|
814
|
+
setup: joinedBoxSetup,
|
|
815
|
+
css: ":scope { pointer-events: none; }",
|
|
816
|
+
data: {
|
|
817
|
+
wordCounts: atomWords.map(words => words.length),
|
|
818
|
+
background: parameters.activeBox.background, borderColor: parameters.activeBox.borderColor,
|
|
819
|
+
borderWidth: parameters.activeBox.borderWidthPx, radius: parameters.activeBox.radiusPx,
|
|
820
|
+
paddingX: parameters.activeBox.paddingXPx, paddingY: parameters.activeBox.paddingYPx,
|
|
821
|
+
},
|
|
822
|
+
}),
|
|
823
|
+
});
|
|
824
|
+
push({ id: "joined-layout", parent: "joined-boxes", kind: "box",
|
|
825
|
+
style: [{ name: "visibility", value: "hidden" }],
|
|
826
|
+
attributes: [{ name: "data-fine-box-layout", value: "" }],
|
|
827
|
+
});
|
|
828
|
+
// The complete stationary Cue supplies layout, including the inactive suffix. Its actual
|
|
829
|
+
// word fragments determine the background; decoration contributes no width to text flow.
|
|
830
|
+
for (const [index, words] of atomWords.entries()) {
|
|
831
|
+
if (index > 0 && rowStarts.has(index)) push({ id: `joined-break-${index}`, parent: "joined-layout", kind: "box",
|
|
832
|
+
style: [{ name: "display", value: "block" }, { name: "height", value: "0" }],
|
|
833
|
+
});
|
|
834
|
+
if (!rowStarts.has(index) && words[0]!.separatorBefore === " ") pushSeparator(`joined-gap-${index}`, "joined-layout");
|
|
835
|
+
const groupId = `joined-group-${index}`;
|
|
836
|
+
push({ id: groupId, parent: "joined-layout", kind: "box", style: [...atomFlow(words), { name: "max-width", value: "100%" }] });
|
|
837
|
+
const gaps = wordGaps(words);
|
|
838
|
+
for (const [wordIndex, word] of words.entries()) {
|
|
839
|
+
push({ id: `${groupId}-word-${wordIndex}`, parent: groupId, kind: "text", text: word.text, fonts,
|
|
840
|
+
style: [...transparentGlyphStyle(parameters),
|
|
841
|
+
...(uniformGap(gaps) === undefined ? spacedStyle(gaps[wordIndex] ?? false) : [])],
|
|
842
|
+
attributes: [{ name: "data-fine-box-word", value: "" }],
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
for (const [index, atom] of atoms.entries()) {
|
|
847
|
+
const timing = atomFrames.get(atom.id)!;
|
|
848
|
+
const next = atoms[index + 1];
|
|
789
849
|
const nextStart = next === undefined ? durationFrames : atomFrames.get(next.id)?.start;
|
|
790
850
|
if (nextStart === undefined) throw new Error("Fine Caption is missing timing for the next Atom");
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
const prefixText = joinSurfaces(
|
|
794
|
-
atoms.slice(0, prefixIndex + 1).map((prefixAtom, index) => {
|
|
795
|
-
for (const wordId of prefixAtom.wordIds) {
|
|
796
|
-
if (!wordText.has(wordId)) throw new Error(`Fine Caption Atom references unknown word ${wordId}`);
|
|
797
|
-
}
|
|
798
|
-
return joinSurfaces(atomSurfaces[index] ?? [], "\u00A0");
|
|
799
|
-
}),
|
|
800
|
-
" ",
|
|
801
|
-
);
|
|
802
|
-
const layerId = `joined-box-${prefixIndex + 1}`;
|
|
803
|
-
push({
|
|
804
|
-
id: layerId,
|
|
805
|
-
parent: "cue",
|
|
806
|
-
kind: "box",
|
|
807
|
-
style: [
|
|
808
|
-
{ name: "box-sizing", value: "border-box" },
|
|
809
|
-
{ name: "direction", value: parameters.layout.direction },
|
|
810
|
-
{ name: "inset", value: "0" },
|
|
811
|
-
{ name: "padding", value: `${compactNumber(parameters.cueBox.paddingYPx)}px ${compactNumber(parameters.cueBox.paddingXPx)}px` },
|
|
812
|
-
{ name: "position", value: "absolute" },
|
|
813
|
-
{ name: "text-align", value: parameters.layout.textAlign },
|
|
814
|
-
],
|
|
851
|
+
push({ id: `joined-box-${index + 1}`, parent: "joined-boxes", kind: "box",
|
|
852
|
+
style: [{ name: "position", value: "absolute" }, { name: "inset", value: "0" }],
|
|
815
853
|
animation: activeBoxAnimation(parameters, timing.start, nextStart, durationFrames, "current"),
|
|
816
854
|
attributes: [{ name: "data-caption-active-box", value: "joined" }],
|
|
817
855
|
});
|
|
818
|
-
push({
|
|
819
|
-
id: `${layerId}-text`,
|
|
820
|
-
parent: layerId,
|
|
821
|
-
kind: "text",
|
|
822
|
-
text: prefixText,
|
|
823
|
-
style: [
|
|
824
|
-
...transparentGlyphStyle(parameters).filter(({ name }) => name !== "white-space"),
|
|
825
|
-
{ name: "-webkit-box-decoration-break", value: "clone" },
|
|
826
|
-
{ name: "background", value: parameters.activeBox.background },
|
|
827
|
-
{ name: "border-color", value: parameters.activeBox.borderColor },
|
|
828
|
-
{ name: "border-radius", value: `${compactNumber(parameters.activeBox.radiusPx)}px` },
|
|
829
|
-
{ name: "border-style", value: "solid" },
|
|
830
|
-
{ name: "border-width", value: `${compactNumber(parameters.activeBox.borderWidthPx)}px` },
|
|
831
|
-
{ name: "box-decoration-break", value: "clone" },
|
|
832
|
-
{ name: "display", value: "inline" },
|
|
833
|
-
{ name: "padding", value: `${compactNumber(parameters.activeBox.paddingYPx)}px ${compactNumber(parameters.activeBox.paddingXPx)}px` },
|
|
834
|
-
{ name: "white-space", value: "normal" },
|
|
835
|
-
],
|
|
836
|
-
fonts,
|
|
837
|
-
});
|
|
838
856
|
}
|
|
839
857
|
}
|
|
840
858
|
|
|
841
|
-
let wordsOnLine = 0;
|
|
842
859
|
for (const [atomIndex, atom] of atoms.entries()) {
|
|
843
860
|
const timing = atomFrames.get(atom.id);
|
|
844
861
|
if (timing === undefined) throw new Error(`Fine Caption is missing timing for Atom ${atom.id}`);
|
|
@@ -848,22 +865,20 @@ function cueElements(
|
|
|
848
865
|
const surfaces = atomSurfaces[atomIndex] ?? [];
|
|
849
866
|
// The base glyphs and the activated copy stacked over them are laid out from this one list, so
|
|
850
867
|
// the karaoke wipe keeps sitting on the letterforms it reveals.
|
|
851
|
-
const gaps = wordGaps(
|
|
868
|
+
const gaps = wordGaps(atomWords[atomIndex]!);
|
|
852
869
|
const wordGap = uniformGap(gaps);
|
|
853
870
|
const entryId = `${atomId}-entry`;
|
|
854
871
|
const loopId = `${atomId}-loop`;
|
|
855
872
|
const responseId = `${atomId}-response`;
|
|
856
|
-
if (
|
|
857
|
-
&& wordsOnLine > 0
|
|
858
|
-
&& wordsOnLine + atom.wordIds.length > parameters.layout.maxWordsPerLine) {
|
|
873
|
+
if (atomIndex > 0 && rowStarts.has(atomIndex)) {
|
|
859
874
|
push({
|
|
860
875
|
id: `line-break-${atomIndex}`,
|
|
861
876
|
parent: "cue",
|
|
862
877
|
kind: "box",
|
|
863
|
-
style: [{ name: "
|
|
878
|
+
style: [{ name: "display", value: "block" }, { name: "height", value: "0" }],
|
|
864
879
|
});
|
|
865
|
-
wordsOnLine = 0;
|
|
866
880
|
}
|
|
881
|
+
if (!rowStarts.has(atomIndex) && atomGaps[atomIndex]) pushSeparator(`gap-${atomIndex}`, "cue");
|
|
867
882
|
const entryAnimation = atomLifecycleAnimation(parameters, timing.start, timing.end, durationFrames);
|
|
868
883
|
const atomLoop = parameters.motion.loopTarget === "active-atom"
|
|
869
884
|
? loopAnimation(parameters, durationFrames, activationTiming) : undefined;
|
|
@@ -880,8 +895,6 @@ function cueElements(
|
|
|
880
895
|
style: [
|
|
881
896
|
{ name: "display", value: "inline-flex" },
|
|
882
897
|
{ name: "min-width", value: "0" },
|
|
883
|
-
// A row that wraps opens at its own edge, so an atom that starts one carries no margin.
|
|
884
|
-
...(cueGap === undefined && wordsOnLine > 0 ? spacedStyle(atomGaps[atomIndex] ?? false) : []),
|
|
885
898
|
{ name: "transform-origin", value: "center center" },
|
|
886
899
|
],
|
|
887
900
|
...(entryAnimation === undefined ? {} : { animation: entryAnimation }),
|
|
@@ -913,13 +926,8 @@ function cueElements(
|
|
|
913
926
|
parent: responseId,
|
|
914
927
|
kind: "box",
|
|
915
928
|
style: [
|
|
916
|
-
...(
|
|
917
|
-
{ name: "display", value: "inline-flex" },
|
|
918
|
-
{ name: "min-width", value: "0" },
|
|
919
|
-
{ name: "overflow-wrap", value: "anywhere" },
|
|
929
|
+
...atomFlow(atomWords[atomIndex]!),
|
|
920
930
|
{ name: "position", value: "relative" },
|
|
921
|
-
{ name: "white-space", value: "normal" },
|
|
922
|
-
{ name: "word-break", value: parameters.layout.wrap === "grapheme" ? "break-all" : "normal" },
|
|
923
931
|
],
|
|
924
932
|
attributes: [{ name: "data-caption-atom", value: atom.id }],
|
|
925
933
|
});
|
|
@@ -996,7 +1004,7 @@ function cueElements(
|
|
|
996
1004
|
}
|
|
997
1005
|
};
|
|
998
1006
|
for (const [wordIndex, wordId] of atom.wordIds.entries()) {
|
|
999
|
-
const text = wordText.get(wordId);
|
|
1007
|
+
const text = wordText.get(wordId)?.text;
|
|
1000
1008
|
if (text === undefined) throw new Error(`Fine Caption Atom references unknown word ${wordId}`);
|
|
1001
1009
|
pushText({
|
|
1002
1010
|
id: `${atomId}-base-${wordIndex + 1}`,
|
|
@@ -1037,7 +1045,7 @@ function cueElements(
|
|
|
1037
1045
|
attributes: [{ name: kind === "glyph" ? "data-caption-karaoke" : "data-caption-active-underline", value: mode }],
|
|
1038
1046
|
});
|
|
1039
1047
|
for (const [wordIndex, wordId] of atom.wordIds.entries()) {
|
|
1040
|
-
const text = wordText.get(wordId)
|
|
1048
|
+
const text = wordText.get(wordId)!.text;
|
|
1041
1049
|
pushText({
|
|
1042
1050
|
id: `${activeId}-${wordIndex + 1}`,
|
|
1043
1051
|
parent: activeId,
|
|
@@ -1060,7 +1068,6 @@ function cueElements(
|
|
|
1060
1068
|
if (parameters.activeUnderline.mode !== "off") {
|
|
1061
1069
|
addActivatedTextLayer("underline", parameters.activeUnderline.mode, "underline");
|
|
1062
1070
|
}
|
|
1063
|
-
wordsOnLine += atom.wordIds.length;
|
|
1064
1071
|
}
|
|
1065
1072
|
return elements;
|
|
1066
1073
|
}
|
|
@@ -1081,7 +1088,7 @@ export function renderFineCaption(
|
|
|
1081
1088
|
throw new Error("Fine Caption inputs belong to different Timelines or Narratives");
|
|
1082
1089
|
}
|
|
1083
1090
|
const styles = new Map(program.styles.map((style) => [style.id, style]));
|
|
1084
|
-
const wordText = new Map(document.words.map((word) => [word.id, word
|
|
1091
|
+
const wordText = new Map(document.words.map((word) => [word.id, word]));
|
|
1085
1092
|
const atomById = new Map(document.units.map((atom) => [atom.id, atom]));
|
|
1086
1093
|
for (const style of styles.values()) {
|
|
1087
1094
|
if (style.rendering === null) continue;
|
|
@@ -1,69 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
* Where a word gap belongs between two adjacent display surfaces.
|
|
3
|
-
*
|
|
4
|
-
* A Display Word is one lexical unit, and in Han, Hiragana and Katakana that unit is a single
|
|
5
|
-
* character. A gap applied to every boundary therefore sets Chinese and Japanese as though every
|
|
6
|
-
* character were a word, so each boundary decides its own gap from the two characters that meet
|
|
7
|
-
* across it.
|
|
8
|
-
*
|
|
9
|
-
* The classes follow pangu.js: a CJK character beside a half-width letter, digit, bracket or
|
|
10
|
-
* operator takes one space. The gap pangu inserts is an ordinary space, so a boundary either
|
|
11
|
-
* carries the Style's `word-gap` or carries nothing.
|
|
12
|
-
*/
|
|
1
|
+
import type { CaptionDisplayWord } from "@hypit/narrative";
|
|
13
2
|
|
|
14
|
-
|
|
15
|
-
const CJK = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/u;
|
|
3
|
+
type Surface = Pick<CaptionDisplayWord, "text" | "separatorBefore">;
|
|
16
4
|
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* These glyphs are drawn inside a full-width em box with their own side bearing, so a gap beside
|
|
21
|
-
* one paints a second space over the one the font already carries. The ranges cover CJK
|
|
22
|
-
* punctuation, the full-width ASCII forms, the curly quotes and the ellipsis, and stop short of
|
|
23
|
-
* the full-width digits and letters at `0-9`, `A-Z` and `a-z`, which
|
|
24
|
-
* are ordinary word characters.
|
|
25
|
-
*/
|
|
26
|
-
const FULL_WIDTH_PUNCTUATION =
|
|
27
|
-
/[、-〃〈-】〔-〟!-/:-@[-`{-・‘’“”—…]/u;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Half-width brackets and quotation marks take their space on the outside of the pair, which falls
|
|
31
|
-
* out of reading the boundary characters: `displayWordSurfaces` attaches an opener to the surface
|
|
32
|
-
* that follows it and a closer to the surface before it, so `中文(注)中文` meets `(` on the right
|
|
33
|
-
* of one boundary and `)` on the left of the next, and both take a gap.
|
|
34
|
-
*
|
|
35
|
-
* A boundary with no CJK character on either side keeps the gap it has today. Latin spacing is not
|
|
36
|
-
* what this decides.
|
|
37
|
-
*/
|
|
38
|
-
export function wordGapBetween(previous: string, next: string): boolean {
|
|
39
|
-
const left = [...previous].at(-1);
|
|
40
|
-
const right = [...next].at(0);
|
|
41
|
-
if (left === undefined || right === undefined) return false;
|
|
42
|
-
if (FULL_WIDTH_PUNCTUATION.test(left) || FULL_WIDTH_PUNCTUATION.test(right)) return false;
|
|
43
|
-
return !(CJK.test(left) && CJK.test(right));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Every boundary in order. The first surface opens the run, so its entry is always `false`. */
|
|
47
|
-
export function wordGaps(surfaces: readonly string[]): readonly boolean[] {
|
|
48
|
-
return surfaces.map((surface, index) => {
|
|
49
|
-
const previous = surfaces[index - 1];
|
|
50
|
-
return previous === undefined ? false : wordGapBetween(previous, surface);
|
|
51
|
-
});
|
|
5
|
+
/** The first displayed word starts a line; all other gaps come from authored content. */
|
|
6
|
+
export function wordGaps(words: readonly Surface[]): readonly boolean[] {
|
|
7
|
+
return words.map((word, index) => index > 0 && word.separatorBefore === " ");
|
|
52
8
|
}
|
|
53
9
|
|
|
54
|
-
/**
|
|
55
|
-
* A run whose boundaries all agree carries one `column-gap` on its container. A mixed run cannot,
|
|
56
|
-
* because `column-gap` is uniform, and takes a margin per element instead.
|
|
57
|
-
*/
|
|
10
|
+
/** A uniform run can use column-gap; a mixed run requires individual separators. */
|
|
58
11
|
export function uniformGap(gaps: readonly boolean[]): boolean | undefined {
|
|
59
12
|
const boundaries = gaps.slice(1);
|
|
60
13
|
if (boundaries.length === 0) return false;
|
|
61
|
-
if (boundaries.every(
|
|
62
|
-
return boundaries.some(
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** Join surfaces back into prose, spacing only the boundaries that carry a gap. */
|
|
66
|
-
export function joinSurfaces(surfaces: readonly string[], separator: string): string {
|
|
67
|
-
const gaps = wordGaps(surfaces);
|
|
68
|
-
return surfaces.map((surface, index) => (gaps[index] ? `${separator}${surface}` : surface)).join("");
|
|
14
|
+
if (boundaries.every(gap => gap)) return true;
|
|
15
|
+
return boundaries.some(gap => gap) ? undefined : false;
|
|
69
16
|
}
|
|
@@ -114,10 +114,8 @@ function cueText(document: CaptionDocument | undefined, unitIds: readonly string
|
|
|
114
114
|
const selected = new Set(unitIds);
|
|
115
115
|
return document.words
|
|
116
116
|
.filter((word) => selected.has(word.unitId))
|
|
117
|
-
.map((word) => word.text)
|
|
118
|
-
.join("
|
|
119
|
-
.replace(/([\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}])\s+(?=[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}])/gu, "$1")
|
|
120
|
-
.trim();
|
|
117
|
+
.map((word, index) => (index === 0 ? "" : word.separatorBefore) + word.text)
|
|
118
|
+
.join("");
|
|
121
119
|
}
|
|
122
120
|
|
|
123
121
|
export function projectCaptionContents(context: StudioTrackCompanionContext): readonly StudioEntityDraft[] {
|
package/packages/cli/README.md
CHANGED
|
@@ -153,13 +153,14 @@ paths. A deleted Source or directory remains queryable: only its existing ancest
|
|
|
153
153
|
the missing path suffix is retained. This is local argument handling, with no saved alias inventory.
|
|
154
154
|
CLI, Studio and creation tools call that same package; the CLI is not another environment owner.
|
|
155
155
|
|
|
156
|
-
`doctor`, `programs up|status|down`, and `runtime up` accept repeated `--endpoint <instance>` values.
|
|
156
|
+
`doctor`, `programs prepare|up|status|down`, and `runtime up` accept repeated `--endpoint <instance>` values.
|
|
157
157
|
The same Endpoint scope reaches package preparation and Program operations. Omission means the whole
|
|
158
158
|
Profile. Build preflight instead uses the Endpoints resolved for that Build's concrete requests.
|
|
159
159
|
An unrelated offered capability does not add another credential or Program requirement.
|
|
160
160
|
Program rows show the configured Endpoint selector alongside an internal Program ID when they differ.
|
|
161
161
|
The `programs` JSON `ok` field reports whether this command succeeded; `ready` reports service
|
|
162
|
-
readiness
|
|
162
|
+
readiness for `up`/`status`, and preparation readiness for `prepare`. The latter does not start the
|
|
163
|
+
service. Successful stopping can therefore report `ok: true` with `ready: false`. A declined stop remains visible even
|
|
163
164
|
while the service is still preparing and cannot yet answer its health probe.
|
|
164
165
|
|
|
165
166
|
Upstream package installation reports an `install.log` path at preparation time. Exact package
|
|
@@ -349,8 +349,8 @@ function parseRuntimeCommand(tail: readonly string[]): RuntimeSelectionCommand |
|
|
|
349
349
|
|
|
350
350
|
function parseProgramsCommand(tail: readonly string[]): ProgramsCommand {
|
|
351
351
|
const [action, ...values] = tail;
|
|
352
|
-
if (action !== "up" && action !== "down" && action !== "status") {
|
|
353
|
-
throw new Error("programs takes up, down or status");
|
|
352
|
+
if (action !== "prepare" && action !== "up" && action !== "down" && action !== "status") {
|
|
353
|
+
throw new Error("programs takes prepare, up, down or status");
|
|
354
354
|
}
|
|
355
355
|
const [profile, rest] = optionalPositional(values);
|
|
356
356
|
const options = commandOptions(`programs ${action}`, rest,
|
|
@@ -146,7 +146,7 @@ export type ProgramsCommand =
|
|
|
146
146
|
| (CommandBase & RuntimeOption & {
|
|
147
147
|
readonly command: "programs";
|
|
148
148
|
readonly endpoints?: readonly string[];
|
|
149
|
-
readonly action: "down" | "status";
|
|
149
|
+
readonly action: "prepare" | "down" | "status";
|
|
150
150
|
readonly limit: number;
|
|
151
151
|
});
|
|
152
152
|
|
|
@@ -175,7 +175,7 @@ export async function runEnvironmentCommand(input: {
|
|
|
175
175
|
if (runtimeProfile === undefined) throw new Error("programs requires a Runtime Profile");
|
|
176
176
|
const profile = resolve(runtimeProfile);
|
|
177
177
|
const host = await runtimeHost(profile);
|
|
178
|
-
if (args.action === "up") {
|
|
178
|
+
if (args.action === "up" || args.action === "prepare") {
|
|
179
179
|
await host.prepare({ ...(args.endpoints === undefined ? {} : { endpoints: args.endpoints }), ...(reportPackageProgress === undefined ? {} : { onProgress: reportPackageProgress }) });
|
|
180
180
|
}
|
|
181
181
|
const controller = await runtimeController(profile);
|
|
@@ -185,6 +185,11 @@ export async function runEnvironmentCommand(input: {
|
|
|
185
185
|
...(args.maxWaitMs === undefined ? {} : { maxWaitMs: args.maxWaitMs }),
|
|
186
186
|
...(reportProgramProgress === undefined ? {} : { onProgress: reportProgramProgress }),
|
|
187
187
|
})
|
|
188
|
+
: args.action === "prepare"
|
|
189
|
+
? await controller.programs.prepare({
|
|
190
|
+
...(args.endpoints === undefined ? {} : { endpoints: args.endpoints }),
|
|
191
|
+
...(reportProgramProgress === undefined ? {} : { onProgress: reportProgramProgress }),
|
|
192
|
+
})
|
|
188
193
|
: args.action === "down"
|
|
189
194
|
? await controller.programs.down(args.endpoints === undefined ? {} : { endpoints: args.endpoints })
|
|
190
195
|
: await controller.programs.report(args.endpoints === undefined ? {} : { endpoints: args.endpoints });
|
|
@@ -199,7 +204,9 @@ export async function runEnvironmentCommand(input: {
|
|
|
199
204
|
const urgent = relevant.filter(needsAttention);
|
|
200
205
|
const shownPrograms = [...urgent, ...relevant.filter((item) => !needsAttention(item)).slice(0, Math.max(0, args.limit - urgent.length))];
|
|
201
206
|
const omittedPrograms = relevant.length - shownPrograms.length;
|
|
202
|
-
const title = args.action === "
|
|
207
|
+
const title = args.action === "prepare"
|
|
208
|
+
? lifecycleOk ? "External program resources prepared" : "External program preparation needs attention"
|
|
209
|
+
: args.action === "up"
|
|
203
210
|
? lifecycleOk ? "External programs ready" : "External programs need attention"
|
|
204
211
|
: args.action === "down"
|
|
205
212
|
? lifecycleOk ? "External programs stopped" : "External program stop needs attention"
|
|
@@ -20,7 +20,7 @@ export type OperationalMachineView =
|
|
|
20
20
|
| { readonly format: "hypit.cli-runtime-selection@1"; readonly selected: boolean; readonly removed?: boolean; readonly profile?: string; readonly project?: string }
|
|
21
21
|
| { readonly format: "hypit.cli-paths@1"; readonly project: string; readonly projectState: string; readonly profileSource: "argument" | "project" | "none"; readonly selectionFile: string; readonly profile?: string; readonly runtimeData?: string; readonly hostState: string; readonly machinePackages: string; readonly distribution?: string }
|
|
22
22
|
| { readonly format: "hypit.cli-package@1"; readonly action: "install" | "status"; readonly package: string; readonly ready: boolean }
|
|
23
|
-
| { readonly format: "hypit.cli-programs@1"; readonly action: "up" | "down" | "status"; readonly ok: boolean; readonly ready: boolean; readonly programCount: number; readonly readyCount: number; readonly programs: readonly ProgramStateView[]; readonly omittedPrograms?: number }
|
|
23
|
+
| { readonly format: "hypit.cli-programs@1"; readonly action: "prepare" | "up" | "down" | "status"; readonly ok: boolean; readonly ready: boolean; readonly programCount: number; readonly readyCount: number; readonly programs: readonly ProgramStateView[]; readonly omittedPrograms?: number }
|
|
24
24
|
| { readonly format: "hypit.cli-runtime-up@1"; readonly ready: boolean; readonly worker: string; readonly preparedPackages: number; readonly programs: { readonly total: number; readonly ready: number; readonly items: readonly ProgramStateView[] } }
|
|
25
25
|
| { readonly format: "hypit.cli-runtime-logs@1"; readonly lines: readonly string[]; readonly totalLines: number; readonly omittedLines: number; readonly path?: string }
|
|
26
26
|
| { readonly format: "hypit.cli-runtime-down@1"; readonly worker: string }
|
|
@@ -878,6 +878,7 @@ function commandHelp(topic: string, colors: Palette): readonly string[] | undefi
|
|
|
878
878
|
colors.accent(colors.strong("hypit programs")),
|
|
879
879
|
colors.dim("Prepare and operate external programs declared by Endpoints in one Runtime Profile."),
|
|
880
880
|
"",
|
|
881
|
+
" hypit programs prepare [--runtime <profile>] [--endpoint <instance>] # resources only; does not start services",
|
|
881
882
|
" hypit programs up [--runtime <profile>] [--workspace <project>] [--max-wait-ms <ms>]",
|
|
882
883
|
" hypit programs status [--runtime <profile>] [--workspace <project>]",
|
|
883
884
|
" hypit programs down [--runtime <profile>] [--workspace <project>]",
|
|
@@ -141,7 +141,11 @@ function platformBackend(service: string) {
|
|
|
141
141
|
return { read: macosReader(service), write: macosWriter(service), remove: macosDeleter(service) };
|
|
142
142
|
}
|
|
143
143
|
if (process.platform === "win32") return windowsBackend();
|
|
144
|
-
|
|
144
|
+
// A Profile that selects this Store cannot be repaired by anything the user does here, and the
|
|
145
|
+
// other two Stores are the answer, so name them where the failure is read.
|
|
146
|
+
throw new Error("OS CredentialStore supports macOS and Windows only; select "
|
|
147
|
+
+ "@hypit/credential-store-file (owner-private local file) or @hypit/credential-store-env "
|
|
148
|
+
+ "(externally supplied value) in this Profile's credentials instead");
|
|
145
149
|
}
|
|
146
150
|
|
|
147
151
|
/** One logical writable store backed by the current user's OS credential locker. */
|
|
@@ -26,4 +26,6 @@ no Tokens. Authored order is independent of gaps, overlap or placement order in
|
|
|
26
26
|
Caption uses this content query and the Narrative's display correspondence to select complete
|
|
27
27
|
subtitle units. Timeline projection uses the same semantic references to locate events in physical
|
|
28
28
|
time. Neither operation substitutes for the other. Token lookup returns Tokens, not reconstructed
|
|
29
|
-
source prose:
|
|
29
|
+
source prose: comments and source-preserving edits remain the author language's concern. Caption
|
|
30
|
+
Display Words separately carry `separatorBefore` (`""` or `" "`) so consumers can reconstruct the
|
|
31
|
+
authored display spelling without guessing from Token boundaries or importing an author parser.
|
|
@@ -29,6 +29,7 @@ export const narrativeExcerptSchema: ValueSchema = object({
|
|
|
29
29
|
tokenStart: { schema: integer }, tokenEndExclusive: { schema: integer },
|
|
30
30
|
});
|
|
31
31
|
const captionDisplayWord = object({
|
|
32
|
+
separatorBefore: { schema: { kind: "string", enum: ["", " "] } },
|
|
32
33
|
id: { schema: string }, unitId: { schema: string }, segmentId: { schema: string },
|
|
33
34
|
turnId: { schema: string }, role: { schema: string, optional: true }, text: { schema: string },
|
|
34
35
|
attributes: { schema: { kind: "array", items: object({
|
|
@@ -50,6 +50,8 @@ export type NarrativeMomentRef = NarrativeMoment & {
|
|
|
50
50
|
|
|
51
51
|
/** One author-visible word surface. Punctuation owned by the surface is preserved. */
|
|
52
52
|
export type CaptionDisplayWord = {
|
|
53
|
+
/** Authored display separator from the preceding word; ignored at a displayed line start. */
|
|
54
|
+
readonly separatorBefore: "" | " ";
|
|
53
55
|
readonly id: string;
|
|
54
56
|
readonly unitId: string;
|
|
55
57
|
readonly segmentId: string;
|
|
@@ -43,7 +43,8 @@ For multilingual transcription with quality as the priority and suitable hardwar
|
|
|
43
43
|
"expectedModel": "large-v3",
|
|
44
44
|
"expectedDevice": "cuda",
|
|
45
45
|
"expectedCompute": "float16",
|
|
46
|
-
"expectedBatchSize": 4
|
|
46
|
+
"expectedBatchSize": 4,
|
|
47
|
+
"alignmentLanguages": ["zh", "en"]
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
```
|
|
@@ -57,7 +58,11 @@ or hosted service for the actual language, material and time available rather th
|
|
|
57
58
|
successively larger models as a routine sequence.
|
|
58
59
|
|
|
59
60
|
`expectedModel` selects speech recognition. WhisperX separately loads the language-specific
|
|
60
|
-
alignment model when that language is first requested.
|
|
61
|
+
alignment model from prepared local resources when that language is first requested. Set
|
|
62
|
+
`alignmentLanguages` to the language codes needed by this production before preparation. This is a
|
|
63
|
+
preparation demand, not a central language whitelist; already cached supported languages remain usable.
|
|
64
|
+
Omission prepares only ASR and sentence data, and does not imply every alignment model is installed.
|
|
65
|
+
A larger ASR model can improve the words
|
|
61
66
|
fed into alignment, but does not select a larger aligner or guarantee better timing by itself.
|
|
62
67
|
The health response identifies the loaded ASR configuration; it does not establish that every
|
|
63
68
|
language's alignment weights are cached. See [WhisperX usage](https://github.com/m-bain/whisperX#usage-)
|
|
@@ -65,8 +70,9 @@ and [faster-whisper deployment](https://github.com/SYSTRAN/faster-whisper#usage)
|
|
|
65
70
|
|
|
66
71
|
## Preparation and downloads
|
|
67
72
|
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
An explicit `programs up` or `runtime up` prepares and starts the selected packaged service in the
|
|
74
|
+
machine Program Home. Selecting an Endpoint alone does not install it. The environment is reused
|
|
75
|
+
across projects and sessions. The current local package and
|
|
70
76
|
service are trusted code; this is not a community-plugin sandbox.
|
|
71
77
|
|
|
72
78
|
`hypit paths` reports the machine `hostState`. The managed installation lives below
|
|
@@ -75,21 +81,31 @@ service are trusted code; this is not a community-plugin sandbox.
|
|
|
75
81
|
the service host includes its port. The default service is `http://127.0.0.1:8765` and exposes
|
|
76
82
|
its configuration through `/health`. Inspect an existing Profile's address and expected settings
|
|
77
83
|
when locating that service. A custom `serviceCommand` supplies its own installation and start command.
|
|
78
|
-
`hypit programs
|
|
79
|
-
and
|
|
84
|
+
`hypit programs prepare --endpoint <instance>` prepares the configured ASR model, requested alignment
|
|
85
|
+
models and sentence data without starting a process. It also works when the service is already online.
|
|
86
|
+
`programs up` prepares and starts the selected Programs; `programs status` reports process readiness.
|
|
87
|
+
Adding a language requires preparation, not a service restart, when its cache location is unchanged.
|
|
88
|
+
`modelCacheDirectory` optionally selects a root with `huggingface/` and `torch/` subdirectories;
|
|
89
|
+
relative paths resolve against the Runtime data root. Omission retains the upstream Hugging Face and
|
|
90
|
+
torch caches and their environment settings. Changing a running service's cache selection requires
|
|
91
|
+
an explicit restart when idle, just like changing its model or hardware. The managed installation
|
|
92
|
+
does not require a global `whisperx` shell command.
|
|
80
93
|
|
|
81
94
|
Preparation and service processes inherit the environment of the command starting them. Set
|
|
82
95
|
network and cache variables there before `programs up` or `runtime up`. A service already running
|
|
83
96
|
retains its earlier environment. Inspect its reported log before deciding whether a selected
|
|
84
97
|
Program needs restarting, and account for active work using it.
|
|
85
98
|
|
|
99
|
+
If NLTK refuses a proxied fetch during preparation, follow the service’s
|
|
100
|
+
[explicit proxy preparation](../../services/whisperx/README.md#preparing-sentence-data-through-a-proxy).
|
|
101
|
+
|
|
86
102
|
Preparation commands write `install.log`; the running service writes `program.log`, with stderr in
|
|
87
103
|
`program.err.log` on Windows. Inspect the stderr file for Python model-loading and download messages.
|
|
88
104
|
`programs status` reports these files as `installationLogPath`, `logPath` and `errorLogPath` when they
|
|
89
|
-
exist, even before installation finishes. Preparation notices name the Python environment and
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
105
|
+
exist, even before installation finishes. Preparation notices name the Python environment and selected model/language resources separately. The service logs the start and completion of ASR loading, transcription,
|
|
106
|
+
language-alignment model loading and alignment, with elapsed times. Downloads happen only in
|
|
107
|
+
preparation; startup and inference only load local resources. A missing resource fails with a
|
|
108
|
+
preparation instruction. Transfer details come from the underlying client during preparation.
|
|
93
109
|
The service health endpoint becomes available after ASR loading. A startup readiness wait expiring
|
|
94
110
|
can leave that process still loading. Its PID is recorded when spawned; repeated `up` observes it,
|
|
95
111
|
and `programs down` can stop it during loading. PID liveness and service readiness are separate facts.
|