@nxtedition/lib 22.0.13 → 22.0.14

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.
Files changed (2) hide show
  1. package/ass.js +50 -16
  2. package/package.json +2 -1
package/ass.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // @ts-check
2
2
  import fp from 'lodash/fp.js'
3
+ import { wordwrap } from './wordwrap.js'
3
4
 
4
5
  const BASE_WIDTH = 1920
5
6
  const BASE_HEIGHT = 1080
@@ -38,10 +39,11 @@ const BASE_HEIGHT = 1080
38
39
  * @property {string} [encoding]
39
40
  *
40
41
  * @typedef {object} Options
41
- * @property {Styles} [styles]
42
+ * @property {{ [styleName: string]: Styles }} [styles]
42
43
  * @property {number} [width]
43
44
  * @property {number} [height]
44
45
  * @property {boolean} [scaledBorderAndShadow] TODO: default to true when all client styles depend on it?
46
+ * @property {boolean} [futureWordWrapping]
45
47
  */
46
48
 
47
49
  /**
@@ -51,27 +53,54 @@ const BASE_HEIGHT = 1080
51
53
  */
52
54
  export function encodeASS(
53
55
  events,
54
- { styles = {}, width = BASE_WIDTH, height = BASE_HEIGHT, scaledBorderAndShadow = false } = {},
56
+ {
57
+ styles = {},
58
+ width = BASE_WIDTH,
59
+ height = BASE_HEIGHT,
60
+ scaledBorderAndShadow = false,
61
+ futureWordWrapping = false,
62
+ } = {},
55
63
  ) {
64
+ const scale = typeof width === 'number' ? BASE_WIDTH / width : 1
65
+ const playResX = scale * (width || BASE_WIDTH)
66
+ const playResY = scale * (height || BASE_HEIGHT)
56
67
  return [
57
- encASSHeader({ width, height, scaledBorderAndShadow }),
68
+ encASSHeader({ playResX, playResY, scaledBorderAndShadow, futureWordWrapping }),
58
69
  encASSStyles(styles),
59
- encASSEvents(events),
70
+ encASSEvents(events, styles, playResX, futureWordWrapping),
60
71
  ].join('\n')
61
72
  }
62
73
 
63
74
  /**
64
75
  * @param {SubtitleEvent[]} events
76
+ * @param {{ [styleName: string]: Styles }} styles
77
+ * @param {number} scriptResX
78
+ * @param {boolean} futureWordWrapping
65
79
  */
66
- function formatDialogues(events) {
80
+ function formatDialogues(events, styles, scriptResX, futureWordWrapping) {
67
81
  let s = ''
68
82
  for (const { start, duration, end = duration != null ? start + duration : null, text, style } of [
69
83
  ...events,
70
84
  ].sort((a, b) => a.start - b.start)) {
85
+ const styleName = style || 'nxt-default'
86
+ const eventStyle = styles[styleName]
87
+ const fontSize = parseFloat(eventStyle.fontsize || '32')
88
+ const fontFamily = eventStyle.fontname ?? 'Arial'
89
+ const maxWidth = Math.max(
90
+ 0,
91
+ scriptResX - parseFloat(eventStyle.marginL || '0') - parseFloat(eventStyle.marginR || '0'),
92
+ )
71
93
  if (typeof text === 'string' && text.length > 0 && Number.isFinite(start)) {
72
94
  s += `Dialogue: 0,${formatASSTime(start) || '0:00:00.00'},${
73
95
  formatASSTime(end) || '9:59:59.00'
74
- },${style || 'nxt-default'},,0000,0000,0000,,${(text || '').replace(/\\n|\n/, '\\N')}\n`
96
+ },${styleName},,0000,0000,0000,,${text
97
+ .split(/\\n|\n|\\N/)
98
+ .map((line) => line.trim())
99
+ .flatMap((line) =>
100
+ futureWordWrapping ? wordwrap(line, fontSize, fontFamily, maxWidth) : [line],
101
+ )
102
+ .map((line) => line.trim())
103
+ .join('\\N')}`
75
104
  }
76
105
  }
77
106
  return s
@@ -79,15 +108,18 @@ function formatDialogues(events) {
79
108
 
80
109
  /**
81
110
  * @param {SubtitleEvent[]} events
111
+ * @param {{ [styleName: string]: Styles }} styles
112
+ * @param {number} scriptResX
113
+ * @param {boolean} futureWordWrapping
82
114
  */
83
- function encASSEvents(events) {
115
+ function encASSEvents(events, styles, scriptResX, futureWordWrapping) {
84
116
  return `[Events]
85
117
  Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
86
- ${formatDialogues(events)}`
118
+ ${formatDialogues(events, styles, scriptResX, futureWordWrapping)}`
87
119
  }
88
120
 
89
121
  /**
90
- * @param {Styles} styles
122
+ * @param {{ [styleName: string]: Styles }} styles
91
123
  */
92
124
  const formatStyles = fp.pipe(
93
125
  fp.entries,
@@ -157,12 +189,12 @@ ${formatStyles(styles)}`
157
189
 
158
190
  /**
159
191
  * @param {object} params
160
- * @param {number} [params.width]
161
- * @param {number} [params.height]
192
+ * @param {number} params.playResX
193
+ * @param {number} params.playResY
162
194
  * @param {boolean} [params.scaledBorderAndShadow]
195
+ * @param {boolean} params.futureWordWrapping
163
196
  */
164
- function encASSHeader({ width, height, scaledBorderAndShadow }) {
165
- const scale = typeof width === 'number' ? BASE_WIDTH / width : 1
197
+ function encASSHeader({ playResX, playResY, scaledBorderAndShadow, futureWordWrapping }) {
166
198
  // WrapStyle
167
199
  // 0: smart wrapping, lines are evenly broken
168
200
  // 1: end-of-line word wrapping, only \N breaks
@@ -171,9 +203,11 @@ function encASSHeader({ width, height, scaledBorderAndShadow }) {
171
203
  const header = [
172
204
  '[Script Info]',
173
205
  'ScriptType: v4.00+',
174
- `PlayResX: ${scale * (width || BASE_WIDTH)}`,
175
- `PlayResY: ${scale * (height || BASE_HEIGHT)}`,
176
- 'WrapStyle: 1',
206
+ `PlayResX: ${playResX}`,
207
+ `PlayResY: ${playResY}`,
208
+ // new way is to not let ass wrap (2) -- we handle it ourselves
209
+ // to make sure hub and render gets the same result
210
+ `WrapStyle: ${futureWordWrapping ? 2 : 1}`,
177
211
  ]
178
212
  if (scaledBorderAndShadow) {
179
213
  header.push('ScaledBorderAndShadow: Yes')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "22.0.13",
3
+ "version": "22.0.14",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",
@@ -59,6 +59,7 @@
59
59
  "@elastic/transport": "^8.9.1",
60
60
  "@nxtedition/nxt-undici": "^4.2.26",
61
61
  "@swc/wasm-web": "^1.10.1",
62
+ "canvas": "^3.1.0",
62
63
  "content-type": "^1.0.5",
63
64
  "date-fns": "^3.6.0",
64
65
  "fast-querystring": "^1.1.1",