@dan-uni/dan-any 0.7.8 → 0.8.1
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/dist/index.js +16 -29
- package/dist/index.umd.min.js +171 -10719
- package/dist/src/ass-gen/index.d.ts +3 -2
- package/dist/src/ass-gen/types.d.ts +24 -0
- package/dist/src/ass-gen/util/layout.d.ts +3 -3
- package/dist/src/index.d.ts +5 -2
- package/package.json +4 -5
- package/rslib.config.ts +0 -1
- package/src/ass-gen/__tests__/canvas.test.ts +8 -4
- package/src/ass-gen/__tests__/generate.test.ts +10 -4
- package/src/ass-gen/index.ts +9 -3
- package/src/ass-gen/types.ts +25 -1
- package/src/ass-gen/util/layout.ts +12 -19
- package/src/index.test.ts +3 -1
- package/src/index.ts +13 -7
- package/src/proto/gen/bili/dm_pb.ts +1 -1
- package/src/proto/gen/danuni_pb.ts +1 -1
|
@@ -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
|
|
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;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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';
|
|
@@ -145,6 +145,9 @@ export declare class UniPool {
|
|
|
145
145
|
static fromDDPlay(json: DM_JSON_DDPlay, episodeId: string, options?: Options): UniPool;
|
|
146
146
|
toDDplay(): DM_JSON_DDPlay;
|
|
147
147
|
static fromASS(ass: string): UniPool;
|
|
148
|
-
|
|
148
|
+
/**
|
|
149
|
+
* 转换为ASS字幕格式的弹幕,需播放器支持多行ASS渲染
|
|
150
|
+
*/
|
|
151
|
+
toASS(canvasCtx: CanvasCtx, options?: AssGenOptions): string;
|
|
149
152
|
}
|
|
150
153
|
export { platform, UniDM, UniDMTools, UniIDTools, type DM_JSON_BiliCommandGrpc, };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dan-uni/dan-any",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
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.
|
|
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.
|
|
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
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createCanvas } from 'canvas'
|
|
2
|
+
import { assertType, it } from 'vitest'
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import { measureTextWidthConstructor } from '../util/layout'
|
|
4
5
|
|
|
5
6
|
it('canvas measureTextWidth', () => {
|
|
6
7
|
const text = '一段测试文字'
|
|
7
|
-
const
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
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
|
})
|
package/src/ass-gen/index.ts
CHANGED
|
@@ -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(
|
|
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,
|
package/src/ass-gen/types.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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'
|
|
@@ -50,8 +51,9 @@ describe('转化自', () => {
|
|
|
50
51
|
console.info(pool)
|
|
51
52
|
})
|
|
52
53
|
it('ass[双向]', () => {
|
|
54
|
+
const canvas = createCanvas(50, 50)
|
|
53
55
|
const pool = UniPool.fromBiliXML(xml)
|
|
54
|
-
const ass = pool.toASS()
|
|
56
|
+
const ass = pool.toASS(canvas.getContext('2d'))
|
|
55
57
|
console.info(ass)
|
|
56
58
|
console.info(UniPool.fromASS(ass))
|
|
57
59
|
})
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import 'reflect-metadata/lite'
|
|
|
2
2
|
|
|
3
3
|
import { XMLBuilder, XMLParser } from 'fast-xml-parser'
|
|
4
4
|
import JSONbig from 'json-bigint'
|
|
5
|
-
import type { Options as AssGenOptions } from './ass-gen'
|
|
5
|
+
import type { Options as AssGenOptions, CanvasCtx } from './ass-gen'
|
|
6
6
|
import type { CommandDm as DM_JSON_BiliCommandGrpc } from './proto/gen/bili/dm_pb'
|
|
7
7
|
|
|
8
8
|
import { create, fromBinary, toBinary } from '@bufbuild/protobuf'
|
|
@@ -377,8 +377,8 @@ export class UniPool {
|
|
|
377
377
|
return this.dans
|
|
378
378
|
case 'danuni.bin':
|
|
379
379
|
return this.toPb()
|
|
380
|
-
|
|
381
|
-
|
|
380
|
+
case 'bili.xml':
|
|
381
|
+
return this.toBiliXML()
|
|
382
382
|
// case 'bili.bin':
|
|
383
383
|
// return this.toBiliBin()
|
|
384
384
|
// case 'bili.cmd.bin':
|
|
@@ -389,8 +389,8 @@ export class UniPool {
|
|
|
389
389
|
return this.toArtplayer()
|
|
390
390
|
case 'ddplay.json':
|
|
391
391
|
return this.toDDplay()
|
|
392
|
-
case 'common.ass':
|
|
393
|
-
|
|
392
|
+
// case 'common.ass':
|
|
393
|
+
// return this.toASS()
|
|
394
394
|
default: {
|
|
395
395
|
const message = '(err) Unknown format or unsupported now!'
|
|
396
396
|
if (continue_on_error) return message
|
|
@@ -649,9 +649,15 @@ export class UniPool {
|
|
|
649
649
|
static fromASS(ass: string) {
|
|
650
650
|
return parseAssRawField(ass)
|
|
651
651
|
}
|
|
652
|
-
|
|
652
|
+
/**
|
|
653
|
+
* 转换为ASS字幕格式的弹幕,需播放器支持多行ASS渲染
|
|
654
|
+
*/
|
|
655
|
+
toASS(
|
|
656
|
+
canvasCtx: CanvasCtx,
|
|
657
|
+
options: AssGenOptions = { substyle: {} },
|
|
658
|
+
): string {
|
|
653
659
|
const fn = this.shared.SOID
|
|
654
|
-
return generateASS(this, { filename: fn, title: fn, ...options })
|
|
660
|
+
return generateASS(this, { filename: fn, title: fn, ...options }, canvasCtx)
|
|
655
661
|
}
|
|
656
662
|
}
|
|
657
663
|
|