Skip to content

Commit 90f6aba

Browse files
committed
feat: support parsing json format danmaku files
1 parent c12dea7 commit 90f6aba

File tree

6 files changed

+89
-36
lines changed

6 files changed

+89
-36
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ out
44
.DS_Store
55
*.log*
66
.eslintcache
7-
.env
7+
.env
8+
*.mas.*

electron-builder.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,6 @@ releaseInfo:
7070
releaseNotes: |
7171
修复 windows 下以英文开头的网络路径无法识别的问题
7272
修复 windows 通过拖拽窗口退出最大化状态按扭无法切换的问题
73-
73+
优化调整弹幕字体大小的体验
74+
支持导入 JSON 格式弹幕文件
75+
修复手动导入弹幕后会导致取消勾选弹幕失效

src/main/lib/danmaku.ts

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
1-
export const parseBilibiliDanmaku = (params: { danmakus: BilibliDanmakuItem[] }) => {
2-
const { danmakus } = params
3-
return danmakus.map((item) => {
4-
const [time, type, _, color, timestamp] = item.$.p.split(',').map(Number)
5-
const txt = item._
6-
return {
7-
cid: timestamp,
8-
m: txt,
9-
p: `${time},${type},${decimalToHex(color)},${timestamp}`,
1+
import { parseStringPromise } from 'xml2js'
2+
3+
export const parseBilibiliDanmaku = async (params: {
4+
fileData: string
5+
type: '.xml' | '.json'
6+
}) => {
7+
const { fileData, type } = params
8+
switch (type) {
9+
case '.xml': {
10+
const result = (await parseStringPromise(fileData)) as BilibiliXmlDanmakus
11+
const danmakus = result.i.d
12+
return danmakus?.map((item) => {
13+
const [time, type, _, color, timestamp] = item.$.p.split(',').map(Number)
14+
const txt = item._
15+
return {
16+
cid: timestamp,
17+
m: txt,
18+
p: `${time},${type},${decimalToHex(color)},${timestamp}`,
19+
}
20+
})
21+
}
22+
case '.json': {
23+
const danmakus = JSON.parse(fileData) as BilibiliJsonDanmakuItem[]
24+
return danmakus.map((item) => ({
25+
cid: item.ctime,
26+
m: item.content,
27+
p: `${((item?.progress ?? 0) / 1000).toFixed(1)},${item.mode},${decimalToHex(item.color)},${item.id}`,
28+
}))
1029
}
11-
})
30+
}
1231
}
1332

1433
type Mode = 'top' | 'bottom' | 'scroll'
@@ -18,7 +37,7 @@ export const DanmuPosition: Record<number, Mode> = {
1837
5: 'top',
1938
}
2039

21-
export interface BilibliDanmakuItem {
40+
export interface BilibliXmlDanmakuItem {
2241
_: string
2342
$: {
2443
p: string
@@ -34,10 +53,27 @@ export interface BilibiliXmlDanmakus {
3453
state: string[]
3554
real_name: string[]
3655
source: string[]
37-
d: BilibliDanmakuItem[]
56+
d: BilibliXmlDanmakuItem[]
3857
}
3958
}
4059

41-
export const decimalToHex = (decimal: number): string => {
60+
interface BilibiliJsonDanmakuItem {
61+
id: number // 评论的唯一标识符
62+
mode: number // 弹幕的显示模式(例如滚动、顶部、底部等)
63+
progress?: number // 弹幕的显示进度
64+
fontsize: number // 字体大小
65+
color: number // 字体颜色,通常为十进制表示的 RGB 值
66+
midHash: string // 用户的哈希值,可能是用于匿名标识用户
67+
content: string // 弹幕内容
68+
ctime: number // 创建时间,通常是 UNIX 时间戳
69+
weight: number // 权重,可能影响弹幕的显示优先级
70+
idStr: string // 字符串形式的唯一标识符
71+
attr: number // 弹幕的附加属性
72+
}
73+
74+
export const decimalToHex = (decimal?: number): string => {
75+
if (!decimal) {
76+
return '#FFFFFF'
77+
}
4278
return `#${decimal.toString(16).padStart(6, '0').toUpperCase()}`
4379
}

src/main/tipc/player.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import fs from 'node:fs'
22
import path from 'node:path'
33

44
import { MARCHEN_PROTOCOL_PREFIX } from '@main/constants/protocol'
5-
import type { BilibiliXmlDanmakus } from '@main/lib/danmaku'
65
import { parseBilibiliDanmaku } from '@main/lib/danmaku'
76
import FFmpeg from '@main/lib/ffmpeg'
87
import { getFilePathFromProtocolURL } from '@main/lib/protocols'
@@ -11,7 +10,6 @@ import { showFileSelectionDialog } from '@main/modules/showDialog'
1110
import { calculateFileHashByBuffer } from '@renderer/lib/calc-file-hash'
1211
import { dialog } from 'electron'
1312
import naturalCompare from 'string-natural-compare'
14-
import { parseStringPromise } from 'xml2js'
1513

1614
import { t } from './_instance'
1715

@@ -180,18 +178,28 @@ export const playerRoute = {
180178
isDialogOpen = true
181179
try {
182180
const filePath = await showFileSelectionDialog({
183-
filters: [{ name: '弹幕文件', extensions: ['xml'] }],
181+
filters: [{ name: '弹幕文件', extensions: ['xml', 'json'] }],
184182
})
185183
if (!filePath) {
186-
return
184+
return {
185+
ok: 0,
186+
message: '请选择弹幕文件',
187+
}
188+
}
189+
const extName = path.extname(filePath).toLowerCase()
190+
if (extName !== '.xml' && extName !== '.json') {
191+
return {
192+
ok: 0,
193+
message: '请选择正确的弹幕文件',
194+
}
187195
}
188196
const fileData = fs.readFileSync(filePath, 'utf-8')
189-
const result = (await parseStringPromise(fileData)) as BilibiliXmlDanmakus
190197
return {
191198
ok: 1,
192199
data: {
193-
danmaku: parseBilibiliDanmaku({
194-
danmakus: result.i.d,
200+
danmaku: await parseBilibiliDanmaku({
201+
fileData,
202+
type: extName,
195203
}),
196204
source: filePath,
197205
},

src/renderer/src/components/modules/player/setting/items/damaku/AddDanmaku.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { useToast } from '@renderer/components/ui/toast'
77
import { db } from '@renderer/database/db'
88
import type { DB_Danmaku, DB_History } from '@renderer/database/schemas/history'
99
import { tipcClient } from '@renderer/lib/client'
10-
import { parseDanmakuData } from '@renderer/lib/danmaku'
10+
import { mergeDanmaku, parseDanmakuData } from '@renderer/lib/danmaku'
1111
import queryClient from '@renderer/lib/query-client'
1212
import { isWeb } from '@renderer/lib/utils'
1313
import { apiClient } from '@renderer/request'
@@ -98,20 +98,18 @@ export const AddDanmaku = () => {
9898
if (!player) {
9999
return
100100
}
101-
const oldDanmaku = player.danmu?.config.comments
101+
const mergedDanmakuData = mergeDanmaku(danmaku)
102102

103103
const parsedDanmaku = parseDanmakuData({
104-
danmuData,
104+
danmuData: [...(mergedDanmakuData ?? []), ...(danmuData ?? [])],
105105
duration: +danmakuDuration,
106106
})
107-
108-
const mergedDanmakus = [...(oldDanmaku || []), ...(parsedDanmaku || [])]
109107
player.danmu?.clear()
110108

111-
player.danmu?.updateComments(mergedDanmakus, true)
109+
player.danmu?.updateComments(parsedDanmaku, true)
112110
setResponsiveSettingsUpdate(player)
113111
},
114-
[danmakuDuration, player, setResponsiveSettingsUpdate],
112+
[danmaku, danmakuDuration, player, setResponsiveSettingsUpdate],
115113
)
116114

117115
const handleImportDanmakuFile = useCallback(async () => {
@@ -174,7 +172,7 @@ export const AddDanmaku = () => {
174172
{!isWeb && (
175173
<div className="flex flex-col gap-3">
176174
<Label htmlFor="width" className="text-zinc-600">
177-
从弹幕文件导入,支持 XML 格式
175+
从弹幕文件导入,支持 XML 和 JSON 格式
178176
</Label>
179177
<Button size="sm" variant="outline" onClick={handleImportDanmakuFile}>
180178
点击导入弹幕文件

src/renderer/src/components/modules/settings/views/player/list.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,35 @@
1-
import type { SelectGroup } from "@renderer/components/modules/shared/setting/SettingSelect"
1+
import type { SelectGroup } from '@renderer/components/modules/shared/setting/SettingSelect'
22

33
export const danmakuFontSizeList = [
44
{
5-
label: '极小',
5+
label: '80%',
66
value: '22',
77
},
88
{
9-
label: '较小',
9+
label: '90%',
1010
value: '24',
1111
},
1212
{
13-
label: '适中',
13+
label: '100%',
1414
value: '26',
1515
default: true,
1616
},
1717
{
18-
label: '较大',
18+
label: '110%',
1919
value: '28',
2020
},
2121
{
22-
label: '极大',
22+
label: '120%',
2323
value: '30',
2424
},
25+
{
26+
label: '130%',
27+
value: '32',
28+
},
29+
{
30+
label: '140%',
31+
value: '34',
32+
},
2533
] satisfies SelectGroup[]
2634

2735
export const danmakuDurationList = [

0 commit comments

Comments
 (0)