@dan-uni/dan-any 0.9.4 → 0.9.6

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,5 +1,4 @@
1
1
  import type { UniPool } from '../..';
2
2
  import type { Context, SubtitleStyle } from '../types';
3
3
  import type { RawConfig } from './raw';
4
- declare const _default: (list: UniPool, rawList: UniPool, config: SubtitleStyle, context?: Context, rawConfig?: RawConfig) => string;
5
- export default _default;
4
+ export declare const ass: (list: UniPool, rawList: UniPool, config: SubtitleStyle, context?: Context, rawConfig?: RawConfig) => string;
@@ -1,6 +1,6 @@
1
1
  import type { RGB } from '../types';
2
2
  import { DanmakuType } from '../types';
3
- declare const _default: (danmaku: {
3
+ export declare const dialogue: (danmaku: {
4
4
  type: (typeof DanmakuType)[keyof typeof DanmakuType];
5
5
  color: RGB;
6
6
  fontSizeType: number;
@@ -14,4 +14,3 @@ declare const _default: (danmaku: {
14
14
  scrollTime: number;
15
15
  fixTime: number;
16
16
  }) => string;
17
- export default _default;
@@ -1,3 +1,2 @@
1
1
  import type { Danmaku, SubtitleStyle } from '../types';
2
- declare const _default: (list: Danmaku[], config: SubtitleStyle) => string;
3
- export default _default;
2
+ export declare const event: (list: Danmaku[], config: SubtitleStyle) => string;
@@ -4,5 +4,5 @@ type Resolution = {
4
4
  playResX: number;
5
5
  playResY: number;
6
6
  };
7
- declare const _default: ({ playResX, playResY }: Resolution, { filename, title }: ExtraInfo) => string;
8
- export default _default;
7
+ export declare const info: ({ playResX, playResY }: Resolution, { filename, title }: ExtraInfo) => string;
8
+ export {};
@@ -1,3 +1,2 @@
1
1
  import type { SubtitleStyle } from '../types';
2
- declare const _default: ({ fontName, fontSize, color: configColor, outlineColor, backColor, bold, outline, shadow, opacity, }: SubtitleStyle) => string;
3
- export default _default;
2
+ export declare const style: ({ fontName, fontSize, color: configColor, outlineColor, backColor, bold, outline, shadow, opacity, }: SubtitleStyle) => string;
@@ -1,3 +1,2 @@
1
1
  import type { SubtitleStyle } from './types';
2
- declare const _default: (overrides?: {}) => SubtitleStyle;
3
- export default _default;
2
+ export declare const getConfig: (overrides?: {}) => SubtitleStyle;
@@ -25,8 +25,74 @@ export interface DM_XML_Bili {
25
25
  }[];
26
26
  };
27
27
  }
28
+ export interface DM_JSON_BiliUp {
29
+ /** 接口状态码,0 表示成功 */
30
+ code: number;
31
+ /** 文本形式的状态码,约定为字符串 "0" */
32
+ message: string;
33
+ /** TTL(time to live) 标识,本接口常量为 1 */
34
+ ttl: number;
35
+ data: {
36
+ /** 分页元信息 */
37
+ page: {
38
+ /** 当前页序号,从 1 开始 */
39
+ num: number;
40
+ /** 每页返回的弹幕条数 */
41
+ size: number;
42
+ /** 总页数 */
43
+ total: number;
44
+ };
45
+ result: {
46
+ /** 弹幕 ID,int64 */
47
+ id: number;
48
+ /** 弹幕 ID 字符串形式 */
49
+ id_str: string;
50
+ /** 弹幕类型:1 表示视频弹幕(当前接口恒为 1) */
51
+ type: number;
52
+ aid: number;
53
+ bvid: string;
54
+ oid: number;
55
+ mid: number;
56
+ /** 发送者 mid 的 CRC 哈希(正常接口里用的是这个,保护隐私) */
57
+ mid_hash: string;
58
+ /** 弹幕池 */
59
+ pool: number;
60
+ /** 属性位字符串,逗号分隔的数字列表,对应 attr 二进制位 */
61
+ attrs: string;
62
+ /** 弹幕出现时间,单位毫秒(注意,此处与protobuf接口保持一致,但xml中progress是秒) */
63
+ progress: number;
64
+ mode: number;
65
+ /** 弹幕内容, content */
66
+ msg: string;
67
+ state: number;
68
+ fontsize: number;
69
+ /** 弹幕颜色,需将16进制转化为普通弹幕的10进制,示例:"ffffff" */
70
+ color: string;
71
+ /** 发送时间戳,单位秒 */
72
+ ctime: number;
73
+ /** 发送者昵称 */
74
+ uname: string;
75
+ /** 发送者头像链接 */
76
+ uface: string;
77
+ /** 视频主标题 */
78
+ title: string;
79
+ self_seen: boolean;
80
+ /** 弹幕点赞数 */
81
+ like_count: number;
82
+ user_like: number;
83
+ /** 分 P 标题 */
84
+ p_title: string;
85
+ /** 视频封面链接 */
86
+ cover: string;
87
+ is_charge: boolean;
88
+ is_charge_plus: boolean;
89
+ following: boolean;
90
+ extra_cps: null;
91
+ }[];
92
+ };
93
+ }
28
94
  export interface DM_JSON_Dplayer {
29
- code: 0;
95
+ code: number;
30
96
  /**
31
97
  * progress,mode,color,midHash,content
32
98
  */
@@ -151,6 +217,7 @@ export declare class UniPool {
151
217
  * @param bin 符合`DmWebViewReplySchema`(bili视频meta)的protobuf二进制
152
218
  */
153
219
  static fromBiliCommandGrpc(bin: Uint8Array | ArrayBuffer, options?: Options): UniPool;
220
+ static fromBiliUp(json: DM_JSON_BiliUp, options?: Options): UniPool;
154
221
  static fromDplayer(json: DM_JSON_Dplayer & {
155
222
  danuni?: DanUniConvertTip;
156
223
  }, playerID: string, domain?: string, options?: Options): UniPool;
@@ -18,6 +18,7 @@ interface DMBili {
18
18
  mode: number;
19
19
  fontsize: number;
20
20
  color: number;
21
+ mid?: number;
21
22
  midHash: string;
22
23
  /**
23
24
  * 特殊类型解析:
@@ -97,6 +98,8 @@ interface ExtraBili {
97
98
  mode?: number;
98
99
  pool?: number;
99
100
  dmid?: bigint;
101
+ attr?: number;
102
+ mid?: number;
100
103
  adv?: string;
101
104
  code?: string;
102
105
  bas?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dan-uni/dan-any",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "A danmaku transformer lib, supporting danmaku from different platforms.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -32,20 +32,20 @@
32
32
  "buf": "buf generate"
33
33
  },
34
34
  "dependencies": {
35
- "@bufbuild/protobuf": "^2.7.0",
35
+ "@bufbuild/protobuf": "^2.9.0",
36
36
  "base16384": "^1.0.0",
37
37
  "class-transformer": "^0.5.1",
38
38
  "class-validator": "^0.14.2",
39
- "fast-xml-parser": "^5.2.5",
40
- "fs-extra": "^11.3.1",
39
+ "fast-xml-parser": "^5.3.0",
40
+ "fs-extra": "^11.3.2",
41
41
  "hh-mm-ss": "^1.2.0",
42
42
  "json-bigint": "^1.0.0",
43
43
  "jssha": "^3.3.1",
44
44
  "reflect-metadata": "^0.2.2"
45
45
  },
46
46
  "devDependencies": {
47
- "@bufbuild/buf": "^1.56.0",
48
- "@bufbuild/protoc-gen-es": "^2.7.0",
47
+ "@bufbuild/buf": "^1.57.2",
48
+ "@bufbuild/protoc-gen-es": "^2.9.0",
49
49
  "@types/fs-extra": "^11.0.4",
50
50
  "@types/hh-mm-ss": "^1.2.3",
51
51
  "@types/json-bigint": "^1.0.4",
@@ -12,7 +12,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
12
12
  it('generate ass from xml', () => {
13
13
  const filename = '898651903.xml'
14
14
  const xmlPath = path.join(__dirname, filename)
15
- const xmlText = fs.readFileSync(xmlPath, 'utf-8')
15
+ const xmlText = fs.readFileSync(xmlPath, 'utf8')
16
16
  const canvas = createCanvas(50, 50)
17
17
  const assText = generateASS(
18
18
  UniPool.fromBiliXML(xmlText),
@@ -22,5 +22,5 @@ it('generate ass from xml', () => {
22
22
  },
23
23
  canvas.getContext('2d') as unknown as CanvasRenderingContext2D,
24
24
  )
25
- fs.writeFileSync(path.join(__dirname, `${filename}.ass`), assText, 'utf-8')
25
+ fs.writeFileSync(path.join(__dirname, `${filename}.ass`), assText, 'utf8')
26
26
  })
@@ -3,17 +3,18 @@ import type { Context, SubtitleStyle } from '../types'
3
3
  import type { RawConfig } from './raw'
4
4
 
5
5
  import { UniPool2DanmakuLists } from '../util'
6
- import event from './event'
7
- import info from './info'
6
+ import { event } from './event'
7
+ import { info } from './info'
8
8
  import { raw } from './raw'
9
- import style from './style'
9
+ import { style } from './style'
10
10
 
11
- // eslint-disable-next-line import/no-default-export
12
- export default (
11
+ const default_context = { filename: 'unknown', title: 'unknown' }
12
+
13
+ export const ass = (
13
14
  list: UniPool,
14
15
  rawList: UniPool,
15
16
  config: SubtitleStyle,
16
- context: Context = { filename: 'unknown', title: 'unknown' },
17
+ context: Context = default_context,
17
18
  rawConfig?: RawConfig,
18
19
  ) => {
19
20
  const Elist = UniPool2DanmakuLists(list)
@@ -40,8 +40,7 @@ const fixCommand = ({ top, left }: { top: number; left: number }) =>
40
40
  const colorCommand = (color: RGB) => `\\c${formatColor(color)}`
41
41
  const borderColorCommand = (color: RGB) => `\\3c${formatColor(color)}`
42
42
 
43
- // eslint-disable-next-line import/no-default-export
44
- export default (
43
+ export const dialogue = (
45
44
  danmaku: {
46
45
  type: (typeof DanmakuType)[keyof typeof DanmakuType]
47
46
  color: RGB
@@ -1,7 +1,7 @@
1
1
  import type { Danmaku, SubtitleStyle } from '../types'
2
2
 
3
3
  import { DanmakuType } from '../types'
4
- import dialogue from './dialogue'
4
+ import { dialogue } from './dialogue'
5
5
 
6
6
  const calculateDanmakuPosition = (danmaku: Danmaku, config: SubtitleStyle) => {
7
7
  const { playResX, playResY, scrollTime, fixTime } = config
@@ -43,8 +43,7 @@ const calculateDanmakuPosition = (danmaku: Danmaku, config: SubtitleStyle) => {
43
43
  }
44
44
  }
45
45
 
46
- // eslint-disable-next-line import/no-default-export
47
- export default (list: Danmaku[], config: SubtitleStyle) => {
46
+ export const event = (list: Danmaku[], config: SubtitleStyle) => {
48
47
  const content = [
49
48
  '[Events]',
50
49
  'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text',
@@ -9,8 +9,7 @@ type Resolution = {
9
9
  playResY: number
10
10
  }
11
11
 
12
- // eslint-disable-next-line import/no-default-export
13
- export default (
12
+ export const info = (
14
13
  { playResX, playResY }: Resolution,
15
14
  { filename, title }: ExtraInfo,
16
15
  ) => {
@@ -9,8 +9,8 @@ import type { Context, Danmaku, SubtitleStyle } from '../types'
9
9
 
10
10
  type compressType = 'brotli' | 'gzip'
11
11
  type baseType = 'base64' | 'base18384'
12
- const compressTypes = ['brotli', 'gzip']
13
- const baseTypes = ['base64', 'base18384']
12
+ const compressTypes = new Set(['brotli', 'gzip'])
13
+ const baseTypes = new Set(['base64', 'base18384'])
14
14
 
15
15
  export interface RawConfig {
16
16
  compressType: compressType
@@ -20,7 +20,7 @@ export interface RawConfig {
20
20
  function fromUint16Array(array: Uint16Array): string {
21
21
  let result = ''
22
22
  for (const element of array) {
23
- result += String.fromCharCode(element)
23
+ result += String.fromCodePoint(element)
24
24
  }
25
25
  return result
26
26
  }
@@ -57,20 +57,20 @@ export function deRaw(ass: string):
57
57
  else {
58
58
  let compressType = lineCompressType.replace(';RawCompressType: ', '').trim()
59
59
  let baseType = lineBaseType.replace(';RawBaseType: ', '').trim()
60
- if (!compressTypes.includes(compressType)) compressType = 'gzip'
61
- if (!baseTypes.includes(baseType)) baseType = 'base64'
60
+ if (!compressTypes.has(compressType)) compressType = 'gzip'
61
+ if (!baseTypes.has(baseType)) baseType = 'base64'
62
62
  const text = lineRaw.replace(';Raw: ', '').trim()
63
63
  const buffer =
64
64
  baseType === 'base64'
65
65
  ? Buffer.from(text, 'base64')
66
66
  : Buffer.from(
67
- base16384.decode(Buffer.from(text, 'utf-8').toString('utf-8')),
67
+ base16384.decode(Buffer.from(text, 'utf8').toString('utf8')),
68
68
  )
69
69
  let decompress: Buffer
70
70
  if (compressType === 'brotli') decompress = brotliDecompressSync(buffer)
71
71
  else decompress = gunzipSync(buffer)
72
72
  try {
73
- return JSON.parse(decompress.toString('utf-8'))
73
+ return JSON.parse(decompress.toString('utf8'))
74
74
  } catch {
75
75
  return undefined
76
76
  }
@@ -2,8 +2,7 @@ import type { SubtitleStyle } from '../types'
2
2
 
3
3
  import { formatColor, getDecoratingColor, hexColorToRGB } from '../util'
4
4
 
5
- // eslint-disable-next-line import/no-default-export
6
- export default ({
5
+ export const style = ({
7
6
  fontName,
8
7
  fontSize,
9
8
  color: configColor,
@@ -11,7 +11,7 @@ import { assign, formatColor, hexColorToRGB } from './util'
11
11
  // const convertBlockRule = (rule: string) =>
12
12
  // builtinRules[rule] ? rule : new RegExp(rule)
13
13
 
14
- export default (overrides = {}): SubtitleStyle => {
14
+ export const getConfig = (overrides = {}): SubtitleStyle => {
15
15
  const defaults = {
16
16
  fontSize: [25, 25, 36],
17
17
  fontName: 'SimHei',
@@ -4,9 +4,9 @@ import type { RawConfig } from './ass/raw'
4
4
  import type { CanvasCtx, SubtitleStyle } from './types'
5
5
 
6
6
  import { UniPool } from '..'
7
- import ass from './ass/create'
7
+ import { ass } from './ass/create'
8
8
  import { deRaw } from './ass/raw'
9
- import getConfig from './config'
9
+ import { getConfig } from './config'
10
10
  import { DanmakuList2UniPool, layoutDanmaku } from './util'
11
11
 
12
12
  export { CanvasCtx }
@@ -65,6 +65,9 @@ export function parseAssRawField(
65
65
  options?: UniPoolOptions,
66
66
  ): UniPool {
67
67
  const raw = deRaw(ass)
68
- if (!raw) return UniPool.create()
69
- else return DanmakuList2UniPool(raw.list, options)
68
+ if (raw) {
69
+ return DanmakuList2UniPool(raw.list, options)
70
+ } else {
71
+ return UniPool.create()
72
+ }
70
73
  }
@@ -233,7 +233,7 @@ export const layoutDanmaku = (
233
233
  config: SubtitleStyle,
234
234
  canvasCtx: CanvasCtx,
235
235
  ): UniPool => {
236
- const list = [...UniPool2DanmakuLists(inputList)].sort(
236
+ const list = [...UniPool2DanmakuLists(inputList)].toSorted(
237
237
  (x, y) => x.time - y.time,
238
238
  )
239
239
  const layout = initializeLayout(config, canvasCtx)
package/src/index.test.ts CHANGED
@@ -1,4 +1,3 @@
1
- //基于以下注释,根据vitest生成测试用例
2
1
  import { createCanvas } from 'canvas'
3
2
  import { describe, expect, it } from 'vitest'
4
3
 
package/src/index.ts CHANGED
@@ -59,8 +59,74 @@ export interface DM_XML_Bili {
59
59
  }[]
60
60
  }
61
61
  }
62
+ export interface DM_JSON_BiliUp {
63
+ /** 接口状态码,0 表示成功 */
64
+ code: number
65
+ /** 文本形式的状态码,约定为字符串 "0" */
66
+ message: string
67
+ /** TTL(time to live) 标识,本接口常量为 1 */
68
+ ttl: number
69
+ data: {
70
+ /** 分页元信息 */
71
+ page: {
72
+ /** 当前页序号,从 1 开始 */
73
+ num: number
74
+ /** 每页返回的弹幕条数 */
75
+ size: number
76
+ /** 总页数 */
77
+ total: number
78
+ }
79
+ result: {
80
+ /** 弹幕 ID,int64 */
81
+ id: number
82
+ /** 弹幕 ID 字符串形式 */
83
+ id_str: string
84
+ /** 弹幕类型:1 表示视频弹幕(当前接口恒为 1) */
85
+ type: number
86
+ aid: number
87
+ bvid: string
88
+ oid: number
89
+ mid: number
90
+ /** 发送者 mid 的 CRC 哈希(正常接口里用的是这个,保护隐私) */
91
+ mid_hash: string
92
+ /** 弹幕池 */
93
+ pool: number
94
+ /** 属性位字符串,逗号分隔的数字列表,对应 attr 二进制位 */
95
+ attrs: string
96
+ /** 弹幕出现时间,单位毫秒(注意,此处与protobuf接口保持一致,但xml中progress是秒) */
97
+ progress: number
98
+ mode: number
99
+ /** 弹幕内容, content */
100
+ msg: string
101
+ state: number // ?
102
+ fontsize: number
103
+ /** 弹幕颜色,需将16进制转化为普通弹幕的10进制,示例:"ffffff" */
104
+ color: string
105
+ /** 发送时间戳,单位秒 */
106
+ ctime: number
107
+ /** 发送者昵称 */
108
+ uname: string
109
+ /** 发送者头像链接 */
110
+ uface: string
111
+ /** 视频主标题 */
112
+ title: string
113
+ self_seen: boolean // 尽自己可见?
114
+ /** 弹幕点赞数 */
115
+ like_count: number
116
+ user_like: number // ?
117
+ /** 分 P 标题 */
118
+ p_title: string
119
+ /** 视频封面链接 */
120
+ cover: string
121
+ is_charge: boolean // 该up是否开通充电计划?
122
+ is_charge_plus: boolean // 该up是否开通高级充电计划?
123
+ following: boolean // 当前登录用户是否关注该发送者?
124
+ extra_cps: null // ?
125
+ }[]
126
+ }
127
+ }
62
128
  export interface DM_JSON_Dplayer {
63
- code: 0
129
+ code: number
64
130
  /**
65
131
  * progress,mode,color,midHash,content
66
132
  */
@@ -172,17 +238,17 @@ export class UniPool {
172
238
  const default_stat: Stat[] = []
173
239
  const stats = this.dans.reduce((stat, dan) => {
174
240
  const valWithCount = stat.find((i) => i.val === dan[key])
175
- if (!valWithCount) {
176
- stat.push({ val: dan[key], count: 1 })
177
- } else {
241
+ if (valWithCount) {
178
242
  valWithCount.count++
243
+ } else {
244
+ stat.push({ val: dan[key], count: 1 })
179
245
  }
180
246
  return stat
181
247
  }, default_stat)
182
248
  return stats
183
249
  }
184
250
  getMost(key: keyof statItems) {
185
- return this.getStat(key).sort((a, b) => b.count - a.count)[0]
251
+ return this.getStat(key).toSorted((a, b) => b.count - a.count)[0]
186
252
  }
187
253
  get most() {
188
254
  return {
@@ -449,8 +515,11 @@ export class UniPool {
449
515
  try {
450
516
  const fileStr = new TextDecoder().decode(file)
451
517
  const prStr = parseStr(fileStr)
452
- if (!prStr) errmesg = `${err}(定位: bin->string)`
453
- else return prStr
518
+ if (prStr) {
519
+ return prStr
520
+ } else {
521
+ errmesg = `${err}(定位: bin->string)`
522
+ }
454
523
  } catch {}
455
524
  } else if (mod.includes('json')) {
456
525
  // pure-json
@@ -611,6 +680,7 @@ export class UniPool {
611
680
  return builder.build({
612
681
  '?xml': {
613
682
  '@_version': '1.0',
683
+ // eslint-disable-next-line unicorn/text-encoding-identifier-case
614
684
  '@_encoding': 'UTF-8',
615
685
  },
616
686
  danuni: { ...DanUniConvertTipTemplate, data: this.shared.SOID },
@@ -653,6 +723,41 @@ export class UniPool {
653
723
  options,
654
724
  )
655
725
  }
726
+ static fromBiliUp(json: DM_JSON_BiliUp, options?: Options) {
727
+ return new UniPool(
728
+ json.data.result.map((d) => {
729
+ // 处理 attrs 字符串转换为 attr 二进制
730
+ // attrs 格式如 "1,13,21",每个数字对应二进制位
731
+ const attrBin = d.attrs
732
+ ? d.attrs
733
+ .split(',')
734
+ .map(Number)
735
+ .reduce((bin, bitPosition) => bin | (1 << (bitPosition - 1)), 0)
736
+ : 0
737
+
738
+ return UniDM.fromBili(
739
+ {
740
+ id: BigInt(d.id_str || d.id),
741
+ progress: d.progress / 1000, // 毫秒转秒
742
+ mode: d.mode,
743
+ fontsize: d.fontsize,
744
+ color: Number.parseInt(d.color, 16),
745
+ mid: d.mid,
746
+ midHash: d.mid_hash,
747
+ content: d.msg,
748
+ ctime: BigInt(d.ctime),
749
+ pool: d.pool,
750
+ // idStr: d.id_str,
751
+ attr: attrBin,
752
+ oid: BigInt(d.oid),
753
+ },
754
+ BigInt(d.oid),
755
+ options,
756
+ )
757
+ }),
758
+ options,
759
+ )
760
+ }
656
761
  static fromDplayer(
657
762
  json: DM_JSON_Dplayer & { danuni?: DanUniConvertTip },
658
763
  playerID: string,
@@ -785,12 +890,15 @@ export class UniPool {
785
890
  /**
786
891
  * 转换为ASS字幕格式的弹幕,需播放器支持多行ASS渲染
787
892
  */
788
- toASS(
789
- canvasCtx: CanvasCtx,
790
- options: AssGenOptions = { substyle: {} },
791
- ): string {
893
+ toASS(canvasCtx: CanvasCtx, options?: AssGenOptions): string {
894
+ const defaultOptions: AssGenOptions = { substyle: {} }
895
+ const finalOptions = options ?? defaultOptions
792
896
  const fn = this.shared.SOID
793
- return generateASS(this, { filename: fn, title: fn, ...options }, canvasCtx)
897
+ return generateASS(
898
+ this,
899
+ { filename: fn, title: fn, ...finalOptions },
900
+ canvasCtx,
901
+ )
794
902
  }
795
903
  }
796
904
 
@@ -1,4 +1,4 @@
1
- // @generated by protoc-gen-es v2.7.0 with parameter "target=ts"
1
+ // @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
2
2
  // @generated from file bili/dm.proto (package bilibili.community.service.dm.v1, syntax proto3)
3
3
  /* eslint-disable */
4
4
 
@@ -1,4 +1,4 @@
1
- // @generated by protoc-gen-es v2.7.0 with parameter "target=ts"
1
+ // @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
2
2
  // @generated from file danuni.proto (package danuni.danmaku.v1, syntax proto3)
3
3
  /* eslint-disable */
4
4