@dan-uni/dan-any 0.0.7 → 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 +10 -1
- package/dist/index.js +5956 -1
- package/dist/index.umd.min.js +19951 -1
- package/dist/index.umd.min.js.LICENSE.txt +17 -0
- package/dist/src/ass-gen/__tests__/generate.test.d.ts +1 -0
- package/dist/src/ass-gen/ass/create.d.ts +4 -0
- package/dist/src/ass-gen/ass/dialogue.d.ts +17 -0
- package/dist/src/ass-gen/ass/event.d.ts +3 -0
- package/dist/src/ass-gen/ass/info.d.ts +8 -0
- package/dist/src/ass-gen/ass/raw.d.ts +10 -0
- package/dist/src/ass-gen/ass/style.d.ts +3 -0
- package/dist/src/ass-gen/config.d.ts +3 -0
- package/dist/src/ass-gen/index.d.ts +26 -0
- package/dist/src/ass-gen/types.d.ts +47 -0
- package/dist/src/ass-gen/util/color.d.ts +18 -0
- package/dist/src/ass-gen/util/danconvert.d.ts +4 -0
- package/dist/src/ass-gen/util/index.d.ts +4 -0
- package/dist/src/ass-gen/util/lang.d.ts +3 -0
- package/dist/src/ass-gen/util/layout.d.ts +4 -0
- package/dist/{index.d.ts → src/index.d.ts} +16 -1
- package/dist/src/index.test.d.ts +1 -0
- package/dist/{utils → src/utils}/dm-gen.d.ts +26 -9
- package/dist/src/utils/dm-gen.test.d.ts +1 -0
- package/package.json +9 -7
- package/rslib.config.ts +81 -0
- package/src/ass-gen/__tests__/898651903.xml +1619 -0
- package/src/ass-gen/__tests__/898651903.xml.ass +1392 -0
- package/src/ass-gen/__tests__/canvas.test.ts +11 -0
- package/src/ass-gen/__tests__/generate.test.ts +20 -0
- package/src/ass-gen/ass/create.ts +26 -0
- package/src/ass-gen/ass/dialogue.ts +91 -0
- package/src/ass-gen/ass/event.ts +58 -0
- package/src/ass-gen/ass/info.ts +28 -0
- package/src/ass-gen/ass/raw.ts +69 -0
- package/src/ass-gen/ass/style.ts +67 -0
- package/src/ass-gen/config.ts +45 -0
- package/src/ass-gen/index.ts +52 -0
- package/src/ass-gen/types.ts +52 -0
- package/src/ass-gen/util/color.ts +55 -0
- package/src/ass-gen/util/danconvert.ts +36 -0
- package/src/ass-gen/util/index.ts +10 -0
- package/src/ass-gen/util/lang.ts +35 -0
- package/src/ass-gen/util/layout.ts +238 -0
- package/src/index.test.ts +16 -0
- package/src/index.ts +114 -0
- package/src/utils/dm-gen.test.ts +66 -0
- package/src/utils/dm-gen.ts +153 -34
- package/tsconfig.json +7 -7
- package/CHANGELOG.md +0 -9
- package/dist/index.min.js +0 -1
- package/rollup.config.mjs +0 -51
- /package/dist/{index.test.d.ts → src/ass-gen/__tests__/canvas.test.d.ts} +0 -0
- /package/dist/{proto → src/proto}/gen/bili/dm_pb.d.ts +0 -0
- /package/dist/{proto → src/proto}/gen/danuni_pb.d.ts +0 -0
- /package/dist/{utils → src/utils}/id-gen.d.ts +0 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { createCanvas } from 'canvas'
|
|
2
|
+
import type { UniPool } from '../..'
|
|
3
|
+
import type { Danmaku, SubtitleStyle } from '../types'
|
|
4
|
+
|
|
5
|
+
import { DanmakuType, FontSize } from '../types'
|
|
6
|
+
import { DanmakuList2UniPool, UniPool2DanmakuLists } from './danconvert'
|
|
7
|
+
import { arrayOfLength } from './lang'
|
|
8
|
+
|
|
9
|
+
// 计算一个矩形移进屏幕的时间(头进屏幕到尾巴进屏幕)
|
|
10
|
+
const computeScrollInTime = (
|
|
11
|
+
rectWidth: number,
|
|
12
|
+
screenWidth: number,
|
|
13
|
+
scrollTime: number,
|
|
14
|
+
) => {
|
|
15
|
+
const speed = (screenWidth + rectWidth) / scrollTime
|
|
16
|
+
return rectWidth / speed
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 计算一个矩形在屏幕上的时间(头进屏幕到头离开屏幕)
|
|
20
|
+
const computeScrollOverTime = (
|
|
21
|
+
rectWidth: number,
|
|
22
|
+
screenWidth: number,
|
|
23
|
+
scrollTime: number,
|
|
24
|
+
) => {
|
|
25
|
+
const speed = (screenWidth + rectWidth) / scrollTime
|
|
26
|
+
return screenWidth / speed
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ScrollGrid {
|
|
30
|
+
start: number
|
|
31
|
+
end: number
|
|
32
|
+
width: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type FixGrid = number
|
|
36
|
+
|
|
37
|
+
interface DanmakuGrids {
|
|
38
|
+
// [DanmakuType.SCROLL]: ScrollGrid[];
|
|
39
|
+
// [DanmakuType.TOP]: FixGrid[];
|
|
40
|
+
// [DanmakuType.BOTTOM]: FixGrid[];
|
|
41
|
+
1: ScrollGrid[] // DanmakuType.SCROLL
|
|
42
|
+
3: FixGrid[] // DanmakuType.TOP
|
|
43
|
+
2: FixGrid[] // DanmakuType.BOTTOM
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const splitGrids = ({
|
|
47
|
+
fontSize,
|
|
48
|
+
padding,
|
|
49
|
+
playResY,
|
|
50
|
+
bottomSpace,
|
|
51
|
+
}: {
|
|
52
|
+
fontSize: number[]
|
|
53
|
+
padding: number[]
|
|
54
|
+
playResY: number
|
|
55
|
+
bottomSpace: number
|
|
56
|
+
}): DanmakuGrids => {
|
|
57
|
+
const defaultFontSize = fontSize[FontSize.NORMAL]
|
|
58
|
+
const paddingTop = padding[0]
|
|
59
|
+
const paddingBottom = padding[2]
|
|
60
|
+
const linesCount = Math.floor(
|
|
61
|
+
(playResY - bottomSpace) / (defaultFontSize + paddingTop + paddingBottom),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
// 首先以通用的字号把屏幕的高度分成若干行,字幕只允许落在一个行里
|
|
65
|
+
return {
|
|
66
|
+
// 每一行里的数字是当前在这一行里的最后一条弹幕区域(算入padding)的右边离开屏幕的时间,
|
|
67
|
+
// 这个时间和下一条弹幕的左边离开屏幕的时间相比较,能确定在整个弹幕的飞行过程中是否会相撞(不同长度弹幕飞行速度不同)|,
|
|
68
|
+
// 当每一条弹幕加到一行里时,就会把这个时间算出来,获取新的弹幕时就可以判断哪一行是允许放的就放进去
|
|
69
|
+
1: arrayOfLength(linesCount, {
|
|
70
|
+
start: 0,
|
|
71
|
+
end: 0,
|
|
72
|
+
width: 0,
|
|
73
|
+
}),
|
|
74
|
+
// 对于固定的弹幕,每一行里都存放弹幕的消失时间,只要这行的弹幕没消失就不能放新弹幕进来
|
|
75
|
+
3: arrayOfLength(linesCount, 0),
|
|
76
|
+
2: arrayOfLength(linesCount, 0),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const measureTextWidth = (() => {
|
|
81
|
+
const canvasContext = createCanvas(50, 50).getContext('2d')
|
|
82
|
+
const supportTextMeasure = !!canvasContext.measureText('中')
|
|
83
|
+
|
|
84
|
+
if (supportTextMeasure) {
|
|
85
|
+
return (
|
|
86
|
+
fontName: string,
|
|
87
|
+
fontSize: number,
|
|
88
|
+
bold: boolean,
|
|
89
|
+
text: string,
|
|
90
|
+
) => {
|
|
91
|
+
canvasContext.font = `${bold ? 'bold' : 'normal'} ${fontSize}px ${fontName}`
|
|
92
|
+
const textWidth = canvasContext.measureText(text).width
|
|
93
|
+
return Math.round(textWidth)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.warn(
|
|
98
|
+
'[Warn] node-canvas is installed without text measure support, layout may not be correct',
|
|
99
|
+
)
|
|
100
|
+
return (_fontName: string, fontSize: number, _bold: boolean, text: string) =>
|
|
101
|
+
text.length * fontSize
|
|
102
|
+
})()
|
|
103
|
+
|
|
104
|
+
// 找到能用的行
|
|
105
|
+
const resolveAvailableFixGrid = (grids: FixGrid[], time: number) => {
|
|
106
|
+
for (const [i, grid] of grids.entries()) {
|
|
107
|
+
if (grid <= time) {
|
|
108
|
+
return i
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return -1
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const resolveAvailableScrollGrid = (
|
|
116
|
+
grids: ScrollGrid[],
|
|
117
|
+
rectWidth: number,
|
|
118
|
+
screenWidth: number,
|
|
119
|
+
time: number,
|
|
120
|
+
duration: number,
|
|
121
|
+
) => {
|
|
122
|
+
for (const [i, previous] of grids.entries()) {
|
|
123
|
+
// 对于滚动弹幕,要算两个位置:
|
|
124
|
+
//
|
|
125
|
+
// 1. 前一条弹幕的尾巴进屏幕之前,后一条弹幕不能开始出现
|
|
126
|
+
// 2. 前一条弹幕的尾巴离开屏幕之前,后一条弹幕的头不能离开屏幕
|
|
127
|
+
const previousInTime =
|
|
128
|
+
previous.start +
|
|
129
|
+
computeScrollInTime(previous.width, screenWidth, duration)
|
|
130
|
+
const currentOverTime =
|
|
131
|
+
time + computeScrollOverTime(rectWidth, screenWidth, duration)
|
|
132
|
+
|
|
133
|
+
if (time >= previousInTime && currentOverTime >= previous.end) {
|
|
134
|
+
return i
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return -1
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const initializeLayout = (config: SubtitleStyle) => {
|
|
142
|
+
const {
|
|
143
|
+
playResX,
|
|
144
|
+
playResY,
|
|
145
|
+
fontName,
|
|
146
|
+
fontSize,
|
|
147
|
+
bold,
|
|
148
|
+
padding,
|
|
149
|
+
scrollTime,
|
|
150
|
+
fixTime,
|
|
151
|
+
bottomSpace,
|
|
152
|
+
} = config
|
|
153
|
+
const [paddingTop, paddingRight, paddingBottom, paddingLeft] = padding
|
|
154
|
+
|
|
155
|
+
const defaultFontSize = fontSize[FontSize.NORMAL]
|
|
156
|
+
const grids = splitGrids(config)
|
|
157
|
+
const gridHeight = defaultFontSize + paddingTop + paddingBottom
|
|
158
|
+
|
|
159
|
+
return (danmaku: Danmaku) => {
|
|
160
|
+
const targetGrids = grids[danmaku.type as keyof DanmakuGrids]
|
|
161
|
+
const danmakuFontSize = fontSize[danmaku.fontSizeType]
|
|
162
|
+
const rectWidth =
|
|
163
|
+
measureTextWidth(fontName, danmakuFontSize, bold, danmaku.content) +
|
|
164
|
+
paddingLeft +
|
|
165
|
+
paddingRight
|
|
166
|
+
const verticalOffset =
|
|
167
|
+
paddingTop + Math.round((defaultFontSize - danmakuFontSize) / 2)
|
|
168
|
+
|
|
169
|
+
if (danmaku.type === DanmakuType.SCROLL) {
|
|
170
|
+
const scrollGrids = targetGrids as ScrollGrid[]
|
|
171
|
+
const gridNumber = resolveAvailableScrollGrid(
|
|
172
|
+
scrollGrids,
|
|
173
|
+
rectWidth,
|
|
174
|
+
playResX,
|
|
175
|
+
danmaku.time,
|
|
176
|
+
scrollTime,
|
|
177
|
+
)
|
|
178
|
+
if (gridNumber < 0) {
|
|
179
|
+
// console.warn(`[Warn] Collision ${danmaku.time}: ${danmaku.content}`)
|
|
180
|
+
return null
|
|
181
|
+
}
|
|
182
|
+
targetGrids[gridNumber] = {
|
|
183
|
+
width: rectWidth,
|
|
184
|
+
start: danmaku.time,
|
|
185
|
+
end: danmaku.time + scrollTime,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const top = gridNumber * gridHeight + verticalOffset
|
|
189
|
+
const start = playResX + paddingLeft
|
|
190
|
+
const end = -rectWidth
|
|
191
|
+
|
|
192
|
+
return { ...danmaku, top, start, end }
|
|
193
|
+
} else {
|
|
194
|
+
const gridNumber = resolveAvailableFixGrid(
|
|
195
|
+
targetGrids as FixGrid[],
|
|
196
|
+
danmaku.time,
|
|
197
|
+
)
|
|
198
|
+
if (gridNumber < 0) {
|
|
199
|
+
// console.warn(`[Warn] Collision ${danmaku.time}: ${danmaku.content}`)
|
|
200
|
+
return null
|
|
201
|
+
}
|
|
202
|
+
if (danmaku.type === DanmakuType.TOP) {
|
|
203
|
+
targetGrids[gridNumber] = danmaku.time + fixTime
|
|
204
|
+
|
|
205
|
+
const top = gridNumber * gridHeight + verticalOffset
|
|
206
|
+
// 固定弹幕横向按中心点计算
|
|
207
|
+
const left = Math.round(playResX / 2)
|
|
208
|
+
|
|
209
|
+
return { ...danmaku, top, left }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
targetGrids[gridNumber] = danmaku.time + fixTime
|
|
213
|
+
|
|
214
|
+
// 底部字幕的格子是留出`bottomSpace`的位置后从下往上算的
|
|
215
|
+
const top =
|
|
216
|
+
playResY -
|
|
217
|
+
bottomSpace -
|
|
218
|
+
gridHeight * gridNumber -
|
|
219
|
+
gridHeight +
|
|
220
|
+
verticalOffset
|
|
221
|
+
const left = Math.round(playResX / 2)
|
|
222
|
+
|
|
223
|
+
return { ...danmaku, top, left }
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export const layoutDanmaku = (
|
|
229
|
+
inputList: UniPool,
|
|
230
|
+
config: SubtitleStyle,
|
|
231
|
+
): UniPool => {
|
|
232
|
+
const list = [...UniPool2DanmakuLists(inputList)].sort(
|
|
233
|
+
(x, y) => x.time - y.time,
|
|
234
|
+
)
|
|
235
|
+
const layout = initializeLayout(config)
|
|
236
|
+
|
|
237
|
+
return DanmakuList2UniPool(list.map(layout).filter((danmaku) => !!danmaku))
|
|
238
|
+
}
|
package/src/index.test.ts
CHANGED
|
@@ -48,6 +48,12 @@ describe('转化自', () => {
|
|
|
48
48
|
console.info(json)
|
|
49
49
|
console.info(pool)
|
|
50
50
|
})
|
|
51
|
+
it('ass(双向)', () => {
|
|
52
|
+
const pool = UniPool.fromBiliXML(xml)
|
|
53
|
+
const ass = pool.toASS()
|
|
54
|
+
console.info(ass)
|
|
55
|
+
console.info(UniPool.fromASS(ass))
|
|
56
|
+
})
|
|
51
57
|
})
|
|
52
58
|
|
|
53
59
|
describe('共通值', () => {
|
|
@@ -59,3 +65,13 @@ describe('共通值', () => {
|
|
|
59
65
|
console.info(pool.split('pool'))
|
|
60
66
|
})
|
|
61
67
|
})
|
|
68
|
+
|
|
69
|
+
describe('其它', () => {
|
|
70
|
+
const pool = UniPool.fromBiliXML(xml)
|
|
71
|
+
it('最小化', () => {
|
|
72
|
+
console.info(pool.minify())
|
|
73
|
+
})
|
|
74
|
+
it('合并范围内重复', () => {
|
|
75
|
+
console.info(pool.merge(10).minify())
|
|
76
|
+
})
|
|
77
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { XMLParser } from 'fast-xml-parser'
|
|
2
|
+
import type { Options as AssGenOptions } from './ass-gen'
|
|
2
3
|
import type { CommandDm as DM_JSON_BiliCommandGrpc } from './proto/gen/bili/dm_pb'
|
|
3
4
|
// import type * as UniDMType from './utils/dm-gen'
|
|
4
5
|
import type { platfrom } from './utils/id-gen'
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
timestampNow,
|
|
11
12
|
} from '@bufbuild/protobuf/wkt'
|
|
12
13
|
|
|
14
|
+
import { generateASS, parseAssRawField } from './ass-gen'
|
|
13
15
|
import {
|
|
14
16
|
// DanmakuElem as DM_JSON_BiliGrpc,
|
|
15
17
|
DmSegMobileReplySchema,
|
|
@@ -71,6 +73,7 @@ export type DM_format =
|
|
|
71
73
|
| 'dplayer.json'
|
|
72
74
|
| 'artplayer.json'
|
|
73
75
|
| 'ddplay.json'
|
|
76
|
+
| 'common.ass'
|
|
74
77
|
|
|
75
78
|
type shareItems = Partial<
|
|
76
79
|
Pick<
|
|
@@ -95,6 +98,9 @@ export class UniPool {
|
|
|
95
98
|
static create() {
|
|
96
99
|
return new UniPool([])
|
|
97
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* 合并弹幕/弹幕库
|
|
103
|
+
*/
|
|
98
104
|
assign(dans: UniPool | UniDM | UniDM[]) {
|
|
99
105
|
if (dans instanceof UniPool) {
|
|
100
106
|
return new UniPool([...this.dans, ...dans.dans])
|
|
@@ -104,6 +110,9 @@ export class UniPool {
|
|
|
104
110
|
return new UniPool([...this.dans, ...dans])
|
|
105
111
|
} else return this
|
|
106
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* 按共通属性拆分弹幕库
|
|
115
|
+
*/
|
|
107
116
|
split(key: keyof shareItems) {
|
|
108
117
|
if (this.shared[key]) return [this]
|
|
109
118
|
const set = new Set(this.dans.map((d) => d[key]))
|
|
@@ -111,6 +120,105 @@ export class UniPool {
|
|
|
111
120
|
return new UniPool(this.dans.filter((d) => d[key] === v))
|
|
112
121
|
})
|
|
113
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* 合并一定时间段内的重复弹幕,防止同屏出现过多
|
|
125
|
+
* @param lifetime 查重时间区段,单位秒 (默认为0,表示不查重)
|
|
126
|
+
*/
|
|
127
|
+
merge(lifetime = 0) {
|
|
128
|
+
if (!this.shared.FCID) {
|
|
129
|
+
console.error(
|
|
130
|
+
"本功能仅支持同弹幕库内使用,可先 .split('FCID') 在分别使用",
|
|
131
|
+
)
|
|
132
|
+
return this
|
|
133
|
+
}
|
|
134
|
+
if (lifetime <= 0) return this
|
|
135
|
+
const mergeContext = this.dans.reduce<
|
|
136
|
+
[
|
|
137
|
+
UniDM[],
|
|
138
|
+
Record<string, UniDM>,
|
|
139
|
+
Record<string, UniDMTools.ExtraDanUniMerge>,
|
|
140
|
+
]
|
|
141
|
+
>(
|
|
142
|
+
([result, cache, mergeObj], danmaku) => {
|
|
143
|
+
const key = ['content', 'mode', 'platform', 'pool', 'SPMO']
|
|
144
|
+
.map((k) => danmaku[k as keyof UniDM])
|
|
145
|
+
.join('|')
|
|
146
|
+
const cached = cache[key]
|
|
147
|
+
const lastAppearTime = cached?.progress || 0
|
|
148
|
+
if (
|
|
149
|
+
cached &&
|
|
150
|
+
danmaku.progress - lastAppearTime <= lifetime &&
|
|
151
|
+
danmaku.isSameAs(cached)
|
|
152
|
+
) {
|
|
153
|
+
const senders = mergeObj[key].senders
|
|
154
|
+
senders.push(danmaku.senderID)
|
|
155
|
+
const extra = danmaku.extra
|
|
156
|
+
extra.danuni = extra.danuni || {}
|
|
157
|
+
extra.danuni.merge = {
|
|
158
|
+
count: senders.length,
|
|
159
|
+
duration: danmaku.progress - cached.progress,
|
|
160
|
+
senders,
|
|
161
|
+
}
|
|
162
|
+
danmaku.extraStr = JSON.stringify(extra)
|
|
163
|
+
cache[key] = danmaku
|
|
164
|
+
mergeObj[key] = extra.danuni.merge
|
|
165
|
+
return [result, cache, mergeObj]
|
|
166
|
+
} else {
|
|
167
|
+
mergeObj[key] = {
|
|
168
|
+
count: 1,
|
|
169
|
+
duration: 0,
|
|
170
|
+
senders: [danmaku.senderID],
|
|
171
|
+
}
|
|
172
|
+
cache[key] = danmaku
|
|
173
|
+
// 初始化merge信息,包含第一个sender
|
|
174
|
+
const extra = danmaku.extra
|
|
175
|
+
extra.danuni = extra.danuni || {}
|
|
176
|
+
extra.danuni.merge = mergeObj[key]
|
|
177
|
+
danmaku.extraStr = JSON.stringify(extra)
|
|
178
|
+
result.push(danmaku)
|
|
179
|
+
return [result, cache, mergeObj]
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
[[], {}, {}],
|
|
183
|
+
)
|
|
184
|
+
// 处理结果,删除senders<=1的merge字段
|
|
185
|
+
const [result, _cache, mergeObj] = mergeContext
|
|
186
|
+
result.forEach((danmaku, i) => {
|
|
187
|
+
const key = ['content', 'mode', 'platform', 'pool', 'SPMO']
|
|
188
|
+
.map((k) => danmaku[k as keyof UniDM])
|
|
189
|
+
.join('|')
|
|
190
|
+
const extra = result[i].extra,
|
|
191
|
+
mergeData = mergeObj[key]
|
|
192
|
+
result[i].extraStr = JSON.stringify({
|
|
193
|
+
...extra,
|
|
194
|
+
danuni: {
|
|
195
|
+
...extra.danuni,
|
|
196
|
+
merge: mergeData,
|
|
197
|
+
},
|
|
198
|
+
} satisfies UniDMTools.Extra)
|
|
199
|
+
if (mergeData?.count) {
|
|
200
|
+
if (mergeData.count <= 1) {
|
|
201
|
+
const updatedExtra = { ...extra }
|
|
202
|
+
if (updatedExtra.danuni) {
|
|
203
|
+
delete updatedExtra.danuni.merge
|
|
204
|
+
if (Object.keys(updatedExtra.danuni).length === 0) {
|
|
205
|
+
delete updatedExtra.danuni
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
result[i].extraStr =
|
|
209
|
+
Object.keys(updatedExtra).length > 0
|
|
210
|
+
? JSON.stringify(updatedExtra)
|
|
211
|
+
: undefined
|
|
212
|
+
} else {
|
|
213
|
+
result[i].senderID = 'merge@bot'
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
return new UniPool(result)
|
|
218
|
+
}
|
|
219
|
+
minify() {
|
|
220
|
+
return this.dans.map((d) => d.minify())
|
|
221
|
+
}
|
|
114
222
|
convert2(format: DM_format) {
|
|
115
223
|
switch (format) {
|
|
116
224
|
case 'danuni.json':
|
|
@@ -339,6 +447,12 @@ export class UniPool {
|
|
|
339
447
|
}),
|
|
340
448
|
}
|
|
341
449
|
}
|
|
450
|
+
static fromASS(ass: string) {
|
|
451
|
+
return parseAssRawField(ass)
|
|
452
|
+
}
|
|
453
|
+
toASS(options: AssGenOptions = { substyle: {} }): string {
|
|
454
|
+
return generateASS(this, options)
|
|
455
|
+
}
|
|
342
456
|
}
|
|
343
457
|
|
|
344
458
|
export {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//基于以下注释,根据vitest生成测试用例
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import type { UniDMObj } from './dm-gen'
|
|
4
|
+
|
|
5
|
+
import { UniDM, UniPool } from '..'
|
|
6
|
+
|
|
7
|
+
const xml = `<i>
|
|
8
|
+
<chatserver>chat.bilibili.com</chatserver>
|
|
9
|
+
<chatid>1156756312</chatid>
|
|
10
|
+
<mission>0</mission>
|
|
11
|
+
<maxlimit>2947</maxlimit>
|
|
12
|
+
<state>0</state>
|
|
13
|
+
<real_name>0</real_name>
|
|
14
|
+
<source>k-v</source>
|
|
15
|
+
<d p="13.213,1,25,16777215,1686314041,3,ff41173d,1335658005672492032">喜欢</d>
|
|
16
|
+
<d p="13.331,1,25,16777215,1686948453,3,56a3c5d5,1340979831550069760">不喜欢</d>
|
|
17
|
+
<d p="13.374,1,25,16777215,1686300770,3,647fe355,1335546672880933888">不喜欢</d>
|
|
18
|
+
<d p="13.499,1,25,16777215,1686301548,3,2848bf1c,1335553202649003264">不喜欢</d>
|
|
19
|
+
</i>`
|
|
20
|
+
|
|
21
|
+
describe('其它', () => {
|
|
22
|
+
const pool = UniPool.fromBiliXML(xml)
|
|
23
|
+
it('比较(常规)', () => {
|
|
24
|
+
// 确保测试用例为预期值
|
|
25
|
+
expect(pool.dans[0].content).toBe('喜欢')
|
|
26
|
+
expect(pool.dans[1].content).toBe('不喜欢')
|
|
27
|
+
// 正式测试
|
|
28
|
+
const a = pool.dans[0].isSameAs(pool.dans[1]),
|
|
29
|
+
b = pool.dans[1].isSameAs(pool.dans[2]),
|
|
30
|
+
c = pool.dans[1].isSameAs(pool.dans[3])
|
|
31
|
+
console.info(a, b, c)
|
|
32
|
+
expect(a).toBe(false)
|
|
33
|
+
expect(b).toBe(true)
|
|
34
|
+
expect(c).toBe(true)
|
|
35
|
+
})
|
|
36
|
+
it('比较(extra)', () => {
|
|
37
|
+
const commonSample = {
|
|
38
|
+
FCID: 'test@du',
|
|
39
|
+
content: 'T Sample',
|
|
40
|
+
extra: {
|
|
41
|
+
danuni: {
|
|
42
|
+
merge: {
|
|
43
|
+
count: 1,
|
|
44
|
+
duration: 0,
|
|
45
|
+
senders: ['test@du'],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
} satisfies Partial<UniDMObj>
|
|
50
|
+
const pool2 = [
|
|
51
|
+
UniDM.create({ ...commonSample, extra: undefined }),
|
|
52
|
+
UniDM.create({ ...commonSample, extra: {} }),
|
|
53
|
+
UniDM.create({ ...commonSample, extra: { danuni: {} } }),
|
|
54
|
+
UniDM.create({ ...commonSample }),
|
|
55
|
+
UniDM.create({ ...commonSample, extra: { artplayer: { border: true } } }),
|
|
56
|
+
]
|
|
57
|
+
for (const pool of pool2) {
|
|
58
|
+
console.info(pool.extraStr)
|
|
59
|
+
console.info(pool2[0].isSameAs(pool))
|
|
60
|
+
}
|
|
61
|
+
expect(pool2[0].isSameAs(pool2[1])).toBe(true)
|
|
62
|
+
expect(pool2[0].isSameAs(pool2[2])).toBe(true)
|
|
63
|
+
expect(pool2[0].isSameAs(pool2[3])).toBe(true)
|
|
64
|
+
expect(pool2[0].isSameAs(pool2[4])).toBe(false)
|
|
65
|
+
})
|
|
66
|
+
})
|