@dan-uni/dan-any 0.7.8 → 0.8.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.
@@ -1,6 +1,7 @@
1
1
  import type { RawConfig } from './ass/raw';
2
- import type { SubtitleStyle } from './types';
2
+ import type { CanvasCtx, SubtitleStyle } from './types';
3
3
  import { UniPool } from '..';
4
+ export { CanvasCtx };
4
5
  export type Options = {
5
6
  filename?: string;
6
7
  title?: string;
@@ -24,5 +25,5 @@ const assText = generateASS(xmlText, { filename, title: 'Quick Example' })
24
25
  fs.writeFileSync(`${filename}.ass`, assText, 'utf-8')
25
26
  ```
26
27
  */
27
- export declare function generateASS(danmaku: UniPool, options: Options): string;
28
+ export declare function generateASS(danmaku: UniPool, options: Options, canvasCtx: CanvasCtx): string;
28
29
  export declare function parseAssRawField(ass: string): UniPool;
@@ -1,4 +1,28 @@
1
+ import type { CanvasRenderingContext2D as NodeCRCtx2D } from 'canvas';
1
2
  import type { UniDM } from '../utils/dm-gen';
3
+ /**
4
+ * 请根据您的使用环境提供一个 50x50 的 2D Canvas 上下文
5
+ * @example
6
+ * // Node.js + canvas
7
+ * import { createCanvas } from 'canvas'
8
+ * const canvas = createCanvas(50, 50)
9
+ * const ctx = canvas.getContext('2d')
10
+ * @example
11
+ * // Node.js + Fabric.js
12
+ * import { StaticCanvas } from 'fabric/node'
13
+ * const ctx = new StaticCanvas(null, { width: 50, height: 50 }).getContext()
14
+ * @example
15
+ * // Browser + Native Canvas
16
+ * const canvas = document.createElement('canvas')
17
+ * canvas.width = 50
18
+ * canvas.height = 50
19
+ * const ctx = canvas.getContext('2d')
20
+ * @example
21
+ * // Browser + Fabric.js
22
+ * import { Canvas } from 'fabric'
23
+ * const ctx = new Canvas('canvas', { width: 50, height: 50 }).getContext()
24
+ */
25
+ export type CanvasCtx = NodeCRCtx2D | CanvasRenderingContext2D;
2
26
  export interface Context {
3
27
  filename: string;
4
28
  title: string;
@@ -1,4 +1,4 @@
1
1
  import type { UniPool } from '../..';
2
- import type { SubtitleStyle } from '../types';
3
- export declare const measureTextWidth: (fontName: string, fontSize: number, bold: boolean, text: string) => number;
4
- export declare const layoutDanmaku: (inputList: UniPool, config: SubtitleStyle) => UniPool;
2
+ import type { CanvasCtx, SubtitleStyle } from '../types';
3
+ export declare const measureTextWidthConstructor: (canvasContext: CanvasCtx) => (fontName: string, fontSize: number, bold: boolean, text: string) => number;
4
+ export declare const layoutDanmaku: (inputList: UniPool, config: SubtitleStyle, canvasCtx: CanvasCtx) => UniPool;
@@ -1,10 +1,15 @@
1
1
  import 'reflect-metadata/lite';
2
- import type { Options as AssGenOptions } from './ass-gen';
2
+ import type { Options as AssGenOptions, CanvasCtx } from './ass-gen';
3
3
  import type { CommandDm as DM_JSON_BiliCommandGrpc } from './proto/gen/bili/dm_pb';
4
4
  import { UniDM } from './utils/dm-gen';
5
5
  import * as UniDMTools from './utils/dm-gen';
6
6
  import * as UniIDTools from './utils/id-gen';
7
7
  import * as platform from './utils/platform';
8
+ interface DanUniConvertTip {
9
+ meassage: string;
10
+ version: string;
11
+ data?: string;
12
+ }
8
13
  export interface DM_XML_Bili {
9
14
  i: {
10
15
  chatserver: string;
@@ -28,12 +33,14 @@ export interface DM_JSON_Dplayer {
28
33
  data: [number, number, number, string, string][];
29
34
  }
30
35
  export interface DM_JSON_Artplayer {
31
- text: string;
32
- time?: number;
33
- mode?: number;
34
- color?: string;
35
- border?: boolean;
36
- style?: {};
36
+ danmuku: {
37
+ text: string;
38
+ time?: number;
39
+ mode?: number;
40
+ color?: string;
41
+ border?: boolean;
42
+ style?: {};
43
+ }[];
37
44
  }
38
45
  export interface DM_JSON_DDPlay {
39
46
  count: number | string;
@@ -54,7 +61,13 @@ interface Options {
54
61
  export declare class UniPool {
55
62
  dans: UniDM[];
56
63
  options: Options;
57
- constructor(dans: UniDM[], options?: Options);
64
+ private info;
65
+ constructor(dans: UniDM[], options?: Options, info?: {
66
+ /**
67
+ * 是否从已被转换过的第三方格式弹幕再次转换而来
68
+ */
69
+ fromConverted: boolean;
70
+ });
58
71
  pipe(fn: UniPoolPipe): Promise<UniPool>;
59
72
  pipeSync(fn: UniPoolPipeSync): UniPool;
60
73
  get shared(): shareItems;
@@ -125,26 +138,58 @@ export declare class UniPool {
125
138
  */
126
139
  merge(lifetime?: number): UniPool;
127
140
  minify(): (Partial<UniDMTools.UniDMObj> & Pick<UniDMTools.UniDMObj, "SOID">)[];
128
- convert2(format: DM_format, continue_on_error?: boolean): string | Uint8Array<ArrayBufferLike> | UniDM[] | DM_JSON_Dplayer | DM_JSON_DDPlay | DM_JSON_Artplayer[];
141
+ convert2(format: DM_format, continue_on_error?: boolean): string | Uint8Array<ArrayBufferLike> | UniDM[] | (DM_JSON_Dplayer & {
142
+ danuni?: DanUniConvertTip;
143
+ }) | (DM_JSON_Artplayer & {
144
+ danuni?: DanUniConvertTip;
145
+ }) | (DM_JSON_DDPlay & {
146
+ danuni?: DanUniConvertTip;
147
+ });
129
148
  static fromPb(bin: Uint8Array | ArrayBuffer, options?: Options): UniPool;
130
149
  /**
131
150
  * 转为 protobuf 二进制
132
151
  */
133
152
  toPb(): Uint8Array<ArrayBufferLike>;
134
153
  static fromBiliXML(xml: string, options?: Options): UniPool;
135
- toBiliXML(): string;
154
+ toBiliXML(options?: {
155
+ /**
156
+ * 当SOID非来源bili时,若此处指定则使用该值为cid,否则使用SOID
157
+ */
158
+ cid?: bigint;
159
+ /**
160
+ * 当仅含有来自bili的弹幕时,启用将保持发送者标识不含`@`
161
+ * @description
162
+ * bili的弹幕含midHash(crc),不启用该处使用senderID填充,启用则去除`@bili`部分,提高兼容性
163
+ */
164
+ avoidSenderIDWithAt?: boolean;
165
+ }): string;
136
166
  static fromBiliGrpc(bin: Uint8Array | ArrayBuffer, options?: Options): UniPool;
137
167
  /**
138
168
  * @param bin 符合`DmWebViewReplySchema`(bili视频meta)的protobuf二进制
139
169
  */
140
170
  static fromBiliCommandGrpc(bin: Uint8Array | ArrayBuffer, options?: Options): UniPool;
141
- static fromDplayer(json: DM_JSON_Dplayer, playerID: string, domain?: string, options?: Options): UniPool;
142
- toDplayer(): DM_JSON_Dplayer;
143
- static fromArtplayer(json: DM_JSON_Artplayer[], playerID: string, domain?: string, options?: Options): UniPool;
144
- toArtplayer(): DM_JSON_Artplayer[];
145
- static fromDDPlay(json: DM_JSON_DDPlay, episodeId: string, options?: Options): UniPool;
146
- toDDplay(): DM_JSON_DDPlay;
171
+ static fromDplayer(json: DM_JSON_Dplayer & {
172
+ danuni?: DanUniConvertTip;
173
+ }, playerID: string, domain?: string, options?: Options): UniPool;
174
+ toDplayer(): DM_JSON_Dplayer & {
175
+ danuni?: DanUniConvertTip;
176
+ };
177
+ static fromArtplayer(json: DM_JSON_Artplayer & {
178
+ danuni?: DanUniConvertTip;
179
+ }, playerID: string, domain?: string, options?: Options): UniPool;
180
+ toArtplayer(): DM_JSON_Artplayer & {
181
+ danuni?: DanUniConvertTip;
182
+ };
183
+ static fromDDPlay(json: DM_JSON_DDPlay & {
184
+ danuni?: DanUniConvertTip;
185
+ }, episodeId: string, options?: Options): UniPool;
186
+ toDDplay(): DM_JSON_DDPlay & {
187
+ danuni?: DanUniConvertTip;
188
+ };
147
189
  static fromASS(ass: string): UniPool;
148
- toASS(options?: AssGenOptions): string;
190
+ /**
191
+ * 转换为ASS字幕格式的弹幕,需播放器支持多行ASS渲染
192
+ */
193
+ toASS(canvasCtx: CanvasCtx, options?: AssGenOptions): string;
149
194
  }
150
195
  export { platform, UniDM, UniDMTools, UniIDTools, type DM_JSON_BiliCommandGrpc, };
@@ -272,9 +272,13 @@ export declare class UniDM {
272
272
  */
273
273
  static transCtime(oriCtime: ctime, tsUnit?: 'ms' | 's'): Date;
274
274
  static transMode(oriMode: number, fmt: 'bili' | 'dplayer' | 'artplayer' | 'ddplay'): Modes;
275
- static fromBili(args: DMBili, cid?: bigint, options?: Options): UniDM;
275
+ static fromBili(args: DMBili, cid?: bigint, options?: Options, recSOID?: string): UniDM;
276
276
  toBiliXML(options?: {
277
- skipBiliCommand: boolean;
277
+ skipBiliCommand?: boolean;
278
+ /**
279
+ * 见 ../index.ts UniPool.toBiliXML() 的 options,该option不宜手动调用,判断逻辑未封装
280
+ */
281
+ avoidSenderIDWithAt?: boolean;
278
282
  }): {
279
283
  '#text': string;
280
284
  '@_p': string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dan-uni/dan-any",
3
- "version": "0.7.8",
3
+ "version": "0.8.5",
4
4
  "description": "A danmaku transformer lib, supporting danmaku from different platforms.",
5
5
  "keywords": [
6
6
  "bangumi",
@@ -31,12 +31,10 @@
31
31
  "buf": "buf generate"
32
32
  },
33
33
  "dependencies": {
34
- "@bufbuild/protobuf": "^2.6.2",
34
+ "@bufbuild/protobuf": "^2.6.3",
35
35
  "base16384": "^1.0.0",
36
- "canvas": "^3.1.2",
37
36
  "class-transformer": "^0.5.1",
38
37
  "class-validator": "^0.14.2",
39
- "fabric": "^6.7.1",
40
38
  "fast-xml-parser": "^5.2.5",
41
39
  "fs-extra": "^11.3.0",
42
40
  "hh-mm-ss": "^1.2.0",
@@ -46,10 +44,11 @@
46
44
  },
47
45
  "devDependencies": {
48
46
  "@bufbuild/buf": "^1.56.0",
49
- "@bufbuild/protoc-gen-es": "^2.6.2",
47
+ "@bufbuild/protoc-gen-es": "^2.6.3",
50
48
  "@types/fs-extra": "^11.0.4",
51
49
  "@types/hh-mm-ss": "^1.2.3",
52
50
  "@types/json-bigint": "^1.0.4",
51
+ "canvas": "^3.1.2",
53
52
  "protobufjs": "^7.5.3"
54
53
  }
55
54
  }
package/rslib.config.ts CHANGED
@@ -18,7 +18,6 @@ export default defineConfig({
18
18
  output: {
19
19
  filename: { js: 'index.umd.min.js' },
20
20
  target: 'web',
21
- externals: ['fabric/node'],
22
21
  },
23
22
  dts: true,
24
23
  umdName: pkg.name,
@@ -1,11 +1,15 @@
1
- import { assert, assertType, it } from 'vitest'
1
+ import { createCanvas } from 'canvas'
2
+ import { assertType, it } from 'vitest'
2
3
 
3
- import { measureTextWidth } from '../util/layout'
4
+ import { measureTextWidthConstructor } from '../util/layout'
4
5
 
5
6
  it('canvas measureTextWidth', () => {
6
7
  const text = '一段测试文字'
7
- const width = measureTextWidth('SimHei', 25, false, text)
8
+ const canvas = createCanvas(50, 50)
9
+ const width = measureTextWidthConstructor(
10
+ canvas.getContext('2d') as unknown as CanvasRenderingContext2D,
11
+ )('SimHei', 25, false, text)
8
12
  assertType<number>(width)
9
13
  console.info(width, text.length)
10
- assert(width >= 25 * text.length)
14
+ // assert(width >= 25 * text.length)
11
15
  })
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs'
2
2
  import path, { dirname } from 'node:path'
3
3
  import { fileURLToPath } from 'node:url'
4
+ import { createCanvas } from 'canvas'
4
5
  import { it } from 'vitest'
5
6
 
6
7
  import { generateASS } from '../'
@@ -12,9 +13,14 @@ it('generate ass from xml', () => {
12
13
  const filename = '898651903.xml'
13
14
  const xmlPath = path.join(__dirname, filename)
14
15
  const xmlText = fs.readFileSync(xmlPath, 'utf-8')
15
- const assText = generateASS(UniPool.fromBiliXML(xmlText), {
16
- // filename,
17
- // title: '我的忏悔',
18
- })
16
+ const canvas = createCanvas(50, 50)
17
+ const assText = generateASS(
18
+ UniPool.fromBiliXML(xmlText),
19
+ {
20
+ // filename,
21
+ // title: '我的忏悔',
22
+ },
23
+ canvas.getContext('2d') as unknown as CanvasRenderingContext2D,
24
+ )
19
25
  fs.writeFileSync(path.join(__dirname, `${filename}.ass`), assText, 'utf-8')
20
26
  })
@@ -1,6 +1,6 @@
1
1
  // import parse from './parse/bilibili'
2
2
  import type { RawConfig } from './ass/raw'
3
- import type { SubtitleStyle } from './types'
3
+ import type { CanvasCtx, SubtitleStyle } from './types'
4
4
 
5
5
  import { UniPool } from '..'
6
6
  import ass from './ass/create'
@@ -8,6 +8,8 @@ import { deRaw } from './ass/raw'
8
8
  import getConfig from './config'
9
9
  import { DanmakuList2UniPool, layoutDanmaku } from './util'
10
10
 
11
+ export { CanvasCtx }
12
+
11
13
  export type Options = {
12
14
  filename?: string
13
15
  title?: string
@@ -32,13 +34,17 @@ const assText = generateASS(xmlText, { filename, title: 'Quick Example' })
32
34
  fs.writeFileSync(`${filename}.ass`, assText, 'utf-8')
33
35
  ```
34
36
  */
35
- export function generateASS(danmaku: UniPool, options: Options): string {
37
+ export function generateASS(
38
+ danmaku: UniPool,
39
+ options: Options,
40
+ canvasCtx: CanvasCtx,
41
+ ): string {
36
42
  // const result = parse(text)
37
43
  const config = getConfig(options.substyle)
38
44
  // const filteredList = filterDanmaku(result.list, config.block)
39
45
  // const mergedList = mergeDanmaku(result.list, config.mergeIn)
40
46
  const mergedList = danmaku.merge(config.mergeIn)
41
- const layoutList = layoutDanmaku(mergedList, config)
47
+ const layoutList = layoutDanmaku(mergedList, config, canvasCtx)
42
48
  const content = ass(
43
49
  layoutList,
44
50
  danmaku,
@@ -1,7 +1,31 @@
1
1
  // export type BlockRule = string | RegExp
2
-
2
+ import type { CanvasRenderingContext2D as NodeCRCtx2D } from 'canvas'
3
3
  import type { UniDM } from '../utils/dm-gen'
4
4
 
5
+ /**
6
+ * 请根据您的使用环境提供一个 50x50 的 2D Canvas 上下文
7
+ * @example
8
+ * // Node.js + canvas
9
+ * import { createCanvas } from 'canvas'
10
+ * const canvas = createCanvas(50, 50)
11
+ * const ctx = canvas.getContext('2d')
12
+ * @example
13
+ * // Node.js + Fabric.js
14
+ * import { StaticCanvas } from 'fabric/node'
15
+ * const ctx = new StaticCanvas(null, { width: 50, height: 50 }).getContext()
16
+ * @example
17
+ * // Browser + Native Canvas
18
+ * const canvas = document.createElement('canvas')
19
+ * canvas.width = 50
20
+ * canvas.height = 50
21
+ * const ctx = canvas.getContext('2d')
22
+ * @example
23
+ * // Browser + Fabric.js
24
+ * import { Canvas } from 'fabric'
25
+ * const ctx = new Canvas('canvas', { width: 50, height: 50 }).getContext()
26
+ */
27
+ export type CanvasCtx = NodeCRCtx2D | CanvasRenderingContext2D
28
+
5
29
  export interface Context {
6
30
  filename: string
7
31
  title: string
@@ -1,7 +1,5 @@
1
- import { Canvas as WebCanvas } from 'fabric'
2
- import { StaticCanvas as NodeCanvas } from 'fabric/node'
3
1
  import type { UniPool } from '../..'
4
- import type { Danmaku, SubtitleStyle } from '../types'
2
+ import type { CanvasCtx, Danmaku, SubtitleStyle } from '../types'
5
3
 
6
4
  import { DanmakuType, FontSize } from '../types'
7
5
  import { DanmakuList2UniPool, UniPool2DanmakuLists } from './danconvert'
@@ -78,18 +76,7 @@ const splitGrids = ({
78
76
  }
79
77
  }
80
78
 
81
- export const measureTextWidth = (() => {
82
- let isWeb
83
- try {
84
- isWeb = !!window
85
- } catch {
86
- isWeb = false
87
- }
88
- const Canvas = isWeb ? WebCanvas : NodeCanvas
89
- const canvasContext = new Canvas(undefined, {
90
- width: 50,
91
- height: 50,
92
- }).getContext()
79
+ export const measureTextWidthConstructor = (canvasContext: CanvasCtx) => {
93
80
  const supportTextMeasure = !!canvasContext.measureText('中')
94
81
 
95
82
  if (supportTextMeasure) {
@@ -110,7 +97,7 @@ export const measureTextWidth = (() => {
110
97
  )
111
98
  return (_fontName: string, fontSize: number, _bold: boolean, text: string) =>
112
99
  text.length * fontSize
113
- })()
100
+ }
114
101
 
115
102
  // 找到能用的行
116
103
  const resolveAvailableFixGrid = (grids: FixGrid[], time: number) => {
@@ -149,7 +136,7 @@ const resolveAvailableScrollGrid = (
149
136
  return -1
150
137
  }
151
138
 
152
- const initializeLayout = (config: SubtitleStyle) => {
139
+ const initializeLayout = (config: SubtitleStyle, canvasCtx: CanvasCtx) => {
153
140
  const {
154
141
  playResX,
155
142
  playResY,
@@ -171,7 +158,12 @@ const initializeLayout = (config: SubtitleStyle) => {
171
158
  const targetGrids = grids[danmaku.type as keyof DanmakuGrids]
172
159
  const danmakuFontSize = fontSize[danmaku.fontSizeType]
173
160
  const rectWidth =
174
- measureTextWidth(fontName, danmakuFontSize, bold, danmaku.content) +
161
+ measureTextWidthConstructor(canvasCtx)(
162
+ fontName,
163
+ danmakuFontSize,
164
+ bold,
165
+ danmaku.content,
166
+ ) +
175
167
  paddingLeft +
176
168
  paddingRight
177
169
  const verticalOffset =
@@ -239,11 +231,12 @@ const initializeLayout = (config: SubtitleStyle) => {
239
231
  export const layoutDanmaku = (
240
232
  inputList: UniPool,
241
233
  config: SubtitleStyle,
234
+ canvasCtx: CanvasCtx,
242
235
  ): UniPool => {
243
236
  const list = [...UniPool2DanmakuLists(inputList)].sort(
244
237
  (x, y) => x.time - y.time,
245
238
  )
246
- const layout = initializeLayout(config)
239
+ const layout = initializeLayout(config, canvasCtx)
247
240
 
248
241
  return DanmakuList2UniPool(list.map(layout).filter((danmaku) => !!danmaku))
249
242
  }
package/src/index.test.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  //基于以下注释,根据vitest生成测试用例
2
+ import { createCanvas } from 'canvas'
2
3
  import { describe, it } from 'vitest'
3
4
 
4
5
  import { UniPool } from './index'
@@ -33,28 +34,42 @@ describe('转化自', () => {
33
34
  console.info(xml)
34
35
  console.info(pool)
35
36
  console.info(pool.toBiliXML())
37
+ console.info(pool.toBiliXML({ avoidSenderIDWithAt: true }))
36
38
  })
37
39
  it('artplayer(json)', () => {
38
- const json = [
39
- {
40
- text: 'artplayer测试弹幕', // 弹幕文本
41
- time: 10, // 弹幕时间, 默认为当前播放器时间
42
- mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
43
- color: '#FFFFFF', // 弹幕颜色,默认为白色
44
- border: false, // 弹幕是否有描边, 默认为 false
45
- style: { border: '10rem' }, // 弹幕自定义样式, 默认为空对象
46
- },
47
- ],
40
+ const json = {
41
+ danmuku: [
42
+ {
43
+ text: 'artplayer测试弹幕', // 弹幕文本
44
+ time: 10, // 弹幕时间, 默认为当前播放器时间
45
+ mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
46
+ color: '#FFFFFF', // 弹幕颜色,默认为白色
47
+ border: false, // 弹幕是否有描边, 默认为 false
48
+ style: { border: '10rem' }, // 弹幕自定义样式, 默认为空对象
49
+ },
50
+ ],
51
+ },
48
52
  pool = UniPool.fromArtplayer(json, 'playerid-test', 'acfun')
49
53
  console.info(json)
50
54
  console.info(pool)
51
55
  })
52
56
  it('ass[双向]', () => {
57
+ const canvas = createCanvas(50, 50)
53
58
  const pool = UniPool.fromBiliXML(xml)
54
- const ass = pool.toASS()
59
+ const ass = pool.toASS(canvas.getContext('2d'))
55
60
  console.info(ass)
56
61
  console.info(UniPool.fromASS(ass))
57
62
  })
63
+ it('pb[双向]', () => {
64
+ const pool = UniPool.fromBiliXML(xml)
65
+ const pb = pool.toPb()
66
+ console.info(UniPool.fromPb(pb))
67
+ })
68
+ it('DDplay[双向]', () => {
69
+ const pool = UniPool.fromBiliXML(xml)
70
+ const ddplay = pool.toDDplay()
71
+ console.info(UniPool.fromDDPlay(ddplay, '1'))
72
+ })
58
73
  })
59
74
 
60
75
  describe('共通值', () => {