@linto-ai/transcript-ui-plugin-subtitle 0.9.23 → 0.9.24-unstable.202610151359
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linto-ai/transcript-ui-plugin-subtitle",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.24-unstable.202610151359",
|
|
4
4
|
"description": "Subtitle display plugin for @linto-ai/transcript-ui — fullscreen/banner captions with an optional watermark",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vue",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"vue": "^3.5.0"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@linto-ai/transcript-ui-core": "0.9.
|
|
42
|
-
"@linto-ai/transcript-ui-i18n": "0.9.
|
|
41
|
+
"@linto-ai/transcript-ui-core": "0.9.24-unstable.202610151359",
|
|
42
|
+
"@linto-ai/transcript-ui-i18n": "0.9.24-unstable.202610151359",
|
|
43
43
|
"diff": "^8.0.3",
|
|
44
44
|
"lucide-vue-next": "^0.564.0"
|
|
45
45
|
},
|
package/src/SubtitleBanner.vue
CHANGED
|
@@ -43,8 +43,7 @@ onBeforeUnmount(() => {
|
|
|
43
43
|
<canvas
|
|
44
44
|
ref="canvas"
|
|
45
45
|
class="subtitle-canvas"
|
|
46
|
-
:class="{ 'subtitle-canvas--shrunk': watermarkVisible }"
|
|
47
|
-
:height="canvasHeight"></canvas>
|
|
46
|
+
:class="{ 'subtitle-canvas--shrunk': watermarkVisible }"></canvas>
|
|
48
47
|
<SubtitleWatermark :visible="watermarkVisible" />
|
|
49
48
|
</div>
|
|
50
49
|
</template>
|
|
@@ -92,13 +92,16 @@ onUnmounted(() => {
|
|
|
92
92
|
</template>
|
|
93
93
|
|
|
94
94
|
<style scoped>
|
|
95
|
+
/* Fixed to the viewport on its own: when requestFullscreen() is refused
|
|
96
|
+
(iPhone Safari, WebViews), the view must still cover the editor instead of
|
|
97
|
+
sitting as one more flex child of the layout. */
|
|
95
98
|
.subtitle-fullscreen {
|
|
96
|
-
position:
|
|
99
|
+
position: fixed;
|
|
100
|
+
inset: 0;
|
|
101
|
+
z-index: var(--z-fullscreen);
|
|
97
102
|
display: flex;
|
|
98
103
|
align-items: center;
|
|
99
104
|
justify-content: center;
|
|
100
|
-
width: 100%;
|
|
101
|
-
height: 100%;
|
|
102
105
|
background-color: var(--color-black);
|
|
103
106
|
}
|
|
104
107
|
|
package/src/SubtitleScroller.ts
CHANGED
|
@@ -21,6 +21,10 @@ export class SubtitleDrawer {
|
|
|
21
21
|
protected font: string
|
|
22
22
|
protected paddingInline: number
|
|
23
23
|
protected isResizing = false
|
|
24
|
+
/** Canvas size in CSS pixels: the unit of every draw and measure call. The
|
|
25
|
+
* bitmap itself is scaled by devicePixelRatio (see resizeBitmap). */
|
|
26
|
+
protected width = 0
|
|
27
|
+
protected height = 0
|
|
24
28
|
private resizeObserver: ResizeObserver
|
|
25
29
|
|
|
26
30
|
constructor(
|
|
@@ -40,13 +44,11 @@ export class SubtitleDrawer {
|
|
|
40
44
|
this.font = font
|
|
41
45
|
this.paddingInline = paddingInline
|
|
42
46
|
|
|
43
|
-
this.
|
|
44
|
-
this.canvas.height = this.canvas.clientHeight
|
|
47
|
+
this.resizeBitmap()
|
|
45
48
|
|
|
46
49
|
this.resizeObserver = new ResizeObserver(() => {
|
|
47
50
|
this.isResizing = true
|
|
48
|
-
this.
|
|
49
|
-
this.canvas.height = this.canvas.clientHeight
|
|
51
|
+
this.resizeBitmap()
|
|
50
52
|
this.onResize()
|
|
51
53
|
this.isResizing = false
|
|
52
54
|
})
|
|
@@ -66,7 +68,20 @@ export class SubtitleDrawer {
|
|
|
66
68
|
|
|
67
69
|
resetDrawing(): void {
|
|
68
70
|
const ctx = this.canvas.getContext("2d")!
|
|
69
|
-
ctx.clearRect(0, 0, this.
|
|
71
|
+
ctx.clearRect(0, 0, this.width, this.height)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Sizes the bitmap in device pixels so text stays sharp on high-density
|
|
75
|
+
// screens, then scales the context back so callers keep drawing in CSS
|
|
76
|
+
// pixels. Assigning canvas.width resets the context, hence the transform
|
|
77
|
+
// being set again on every call.
|
|
78
|
+
private resizeBitmap(): void {
|
|
79
|
+
const pixelRatio = window.devicePixelRatio || 1
|
|
80
|
+
this.width = this.canvas.clientWidth
|
|
81
|
+
this.height = this.canvas.clientHeight
|
|
82
|
+
this.canvas.width = Math.round(this.width * pixelRatio)
|
|
83
|
+
this.canvas.height = Math.round(this.height * pixelRatio)
|
|
84
|
+
this.canvas.getContext("2d")!.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0)
|
|
70
85
|
}
|
|
71
86
|
|
|
72
87
|
protected drawText(text: string, x: number, y: number): void {
|
|
@@ -77,11 +92,17 @@ export class SubtitleDrawer {
|
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
protected drawFirstLine(text: string): void {
|
|
80
|
-
this.drawText(text, 0, this.fontSize)
|
|
95
|
+
this.drawText(text, 0, this.computeTextBlockTop() + this.fontSize)
|
|
81
96
|
}
|
|
82
97
|
|
|
83
98
|
protected drawSecondLine(text: string): void {
|
|
84
|
-
this.drawText(text, 0, this.fontSize + this.lineHeight)
|
|
99
|
+
this.drawText(text, 0, this.computeTextBlockTop() + this.fontSize + this.lineHeight)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// The two lines form a block of 2 × lineHeight, centered vertically. The
|
|
103
|
+
// banner canvas is exactly that tall, so there the block starts at the top.
|
|
104
|
+
private computeTextBlockTop(): number {
|
|
105
|
+
return (this.height - 2 * this.lineHeight) / 2
|
|
85
106
|
}
|
|
86
107
|
|
|
87
108
|
protected onResize(): void {
|
|
@@ -125,6 +146,15 @@ export class SubtitleScroller extends SubtitleDrawer {
|
|
|
125
146
|
this.draw()
|
|
126
147
|
}
|
|
127
148
|
|
|
149
|
+
/** Drops the provisional line being built and repaints without it — the
|
|
150
|
+
* last finalized line stays up. What the canvas showed just before the
|
|
151
|
+
* partial started, in other words. */
|
|
152
|
+
clearPartial(): void {
|
|
153
|
+
if (this.isResizing) return
|
|
154
|
+
this.currentState = { previousText: "", previousIndexes: [] }
|
|
155
|
+
this.draw()
|
|
156
|
+
}
|
|
157
|
+
|
|
128
158
|
newFinal(text: string): void {
|
|
129
159
|
if (this.isResizing) return
|
|
130
160
|
this.currentState = splitPartialSubtitles(
|
|
@@ -186,13 +216,13 @@ export class SubtitleScroller extends SubtitleDrawer {
|
|
|
186
216
|
|
|
187
217
|
// Normal cut threshold: keeps both inline margins free.
|
|
188
218
|
private computeIfTextIsTooLong(text: string): boolean {
|
|
189
|
-
return this.textWidth(text) > this.
|
|
219
|
+
return this.textWidth(text) > this.width - 2 * this.paddingInline
|
|
190
220
|
}
|
|
191
221
|
|
|
192
222
|
// Overflow threshold: the text is drawn at x = paddingInline, so it reaches the
|
|
193
|
-
// canvas edge once its width exceeds
|
|
223
|
+
// canvas edge once its width exceeds width - paddingInline. Spilling into
|
|
194
224
|
// the inline margin is tolerated; going past this re-cuts an already-shown line.
|
|
195
225
|
private computeIfTextOverflows(text: string): boolean {
|
|
196
|
-
return this.textWidth(text) > this.
|
|
226
|
+
return this.textWidth(text) > this.width - this.paddingInline
|
|
197
227
|
}
|
|
198
228
|
}
|
|
@@ -36,6 +36,16 @@ export function useSubtitleScroller(options: UseSubtitleScrollerOptions) {
|
|
|
36
36
|
},
|
|
37
37
|
)
|
|
38
38
|
|
|
39
|
+
// Clearing core.live.partial is routine — it happens on every finalized
|
|
40
|
+
// turn — so the scroller ignores it and keeps the text on screen. Turning
|
|
41
|
+
// the setting off is the one case where the line must actually go.
|
|
42
|
+
watch(
|
|
43
|
+
() => core.live?.partialsVisible.value,
|
|
44
|
+
(visible) => {
|
|
45
|
+
if (visible === false) scroller?.clearPartial()
|
|
46
|
+
},
|
|
47
|
+
)
|
|
48
|
+
|
|
39
49
|
const unsubTurnAdd = core.onActiveTranslation("turn:add", ({ turn }) => {
|
|
40
50
|
if (!scroller) return
|
|
41
51
|
const text =
|