自定义素材与配置
September 12, 2026 · View on GitHub
README.md 的补充材料。
放哪里
首次运行时插件会建好 %USERPROFILE%\.dsh\dsh-gal\,里面有 packs\(附一份格式速查)和 ui\,
把文件丢进去即可;用户目录优先,且插件升级不会被冲掉。
| 内容 | 用户目录 | 插件内置目录 |
|---|---|---|
| 立绘包 / 语音包 | %USERPROFILE%\.dsh\dsh-gal\packs\ | <插件目录>\assets\packs\ |
| 对话框图片 | %USERPROFILE%\.dsh\dsh-gal\ui\dialog.png | <插件目录>\assets\ui\dialog.png |
| 对话框几何 | %USERPROFILE%\.dsh\dsh-gal\ui\dialog.json | <插件目录>\assets\ui\dialog.json |
| 设定图标 | %USERPROFILE%\.dsh\dsh-gal\ui\settings.png | <插件目录>\assets\ui\settings.png |
| 点击音效 | %USERPROFILE%\.dsh\dsh-gal\ui\click.wav | <插件目录>\assets\ui\click.wav |
| 定价表 | %USERPROFILE%\.dsh\dsh-gal\pricing.json | <插件目录>\assets\pricing.json |
包是整体覆盖、不是合并:用户目录里出现叫 neri 的目录,哪怕只放 voices\,也会把内置的
neri 整个换掉。只想补语音就另起一个包名(如 neri-voice),再用设定面板把「立绘包 / 语音包」
分别选成两个包。改完刷新页面生效。
立绘包 / 语音包
<包根目录>\
└── mychar\ ← 目录名就是包 id,设置面板里显示的就是它
├── pack.json ← 可选:显示名 / 默认立绘 / 手动裁切
├── sprites\ ← 立绘:png jpg webp gif avif bmp
│ ├── 01.png
│ └── 02.png
├── voices\ ← 语音:wav mp3 ogg oga m4a aac flac
│ ├── my0001.wav
│ └── my0002.wav
└── script.csv ← 可选:语音 → 台词对照表
- 只放
sprites\→ 立绘包;只放voices\→ 语音包;两者都放就是一个包两用。 - 两个文件夹也可以叫
立绘\/语音\,或立ち絵\/ボイス\—— 中文、日文目录名都认, 不用为了插件改名(同名的英文目录优先)。台词表放包目录或语音目录里都能找到。 - 也可以不建子目录,把图片和语音直接丢在包目录里。
- 立绘的透明边距会自动裁掉,不需要自己切图。
voices\里有台词的行会被优先随机到。
立绘镜像
有的立绘是朝左画的,挂在右下角时想让它朝右,就整套左右镜像一遍。仓库里带了这个工具 (无损:解码 → 逐行翻转 → 重新编码,写完再逐像素回验,而且镜像两次就等于还原):
node scripts/mirror-sprites.mjs "D:\path\to\pack\立绘" # 原地镜像
node scripts/mirror-sprites.mjs "D:\path\to\pack\立绘" --out "D:\path\to\pack\立绘-镜像"
只处理 8 位、非隔行的 PNG(灰度 / RGB / RGBA);调色板、16 位、隔行图会直接报错而不是悄悄改坏。
镜像后文件名不变,所以 pack.json 里的 defaultSprite 不用改。
语音顺序与权重
默认洗牌池:把整个语音包洗成一轮,逐条不放回地播;一轮放完自动重洗,新一轮不会拿上一轮
结尾那句开头。所以同一句台词在整轮放完之前不会再次出现——400 次点击里任何一句最多 1 次。
想改回"每次都独立随机",设定面板里把「语音顺序」切成 纯随机 即可(立刻生效)。
要让某几句多放 / 少放 / 不播,在包目录里放 weights.json:
{
"mas0033": 3,
"……。": 0.2,
"mas0044": 0
}
| 写法 | 含义 |
|---|---|
键 = 语音文件名(带不带 .wav 都行,如 mas0033) | 只调这一条 |
键 = 台词原文(完全匹配,如 ……。) | 调说这句话的所有语音 |
1(默认) | 正常频率 |
0.25 | 四分之一频率 |
3 | 三倍频率(一轮里出现 3 次,位置随机且不会连在一起) |
0 | 这一条永不播放 |
- 只写要调的那几条就行,其余默认 1。
- 不做任何自动合并:
……。和……是两条不同的键;标点、空格、全半角都不归一化。 - 文件缺失 / JSON 语法错误 / 键写错 → 那一项按 1 处理,不会报错、不会让某句消失。
- 设定面板的「语音包」下面会显示
· 权重 n 条,方便确认文件生效了。
默认立绘怎么定
对话框消失后立绘切回默认立绘(常态姿势):
| 优先级 | 怎么声明 |
|---|---|
| 1 | pack.json 里的 defaultSprite,例如 "defaultSprite": "01.png" |
| 2 | 没写 → 按自然顺序取第一张(2.png 排在 10.png 前) |
设置面板的「立绘包」下面会写明当前用的是哪张、以及是 pack.json 指定 还是 自动选定。
替换包里的同名文件(例如把整套立绘换成镜像版)之后,刷新页面就能看到新图:立绘与语音的 URL 带文件版本号(修改时间 + 大小),换了内容就是换了地址,不会读到浏览器缓存里的旧素材。
script.csv 格式
clip,chapter,speaker_jp,japanese,chinese,sec
my0001,1,ねり,旅行、行こうよ!,去旅行嘛!,1.725
my0002,2,ねり,お兄ちゃんいまパンツ見たでしょ!,哥哥刚才是不是在看我的内裤!,2.114
clip必须和voices\里的文件名(不含扩展名)一致:my0001↔my0001.wav。japanese/chinese两列分别对应设定里的「日文 / 中文」。- 字段可以用双引号包住,允许换行;列名认不出来时按
文件名,章节,说话人,日语,中文,时长的位置兜底。 - 台词里夹着的音效标记(如
えいっ<dash=2>、ねいっ。)会被自动去掉,不用自己清理;1 < 2这种正常的尖括号不受影响。 - 没有
script.csv也能用,只是点击立绘改为显示余额页。
pack.json(可选)
{
"displayName": "我的角色",
"defaultSprite": "01.png",
"crop": { "x": 0.164, "y": 0.168, "w": 0.679, "h": 0.832 }
}
| 字段 | 说明 |
|---|---|
displayName | 设置面板下拉里显示的名字,缺省用目录名 |
description | 包的说明文字,可省略 |
defaultSprite | 默认立绘文件名;写的文件不存在时回退到自动选定 |
crop | 手动指定裁切范围(0~1)。通常不用写:会自动扫描 PNG 透明通道;只有 JPG 或自动结果不满意时才需要 |
包 id 取目录名,对照表在包目录和 voices\ 里自动找 .csv(script 开头的优先)。
换掉对话框底图:dialog.json
首选在设定面板里换:「对话框」一组里的 更换图片… 选一张自己的图、空白底图 换成自带的
纯白面板、恢复默认 还原——三者都立即生效,插件会自动为新图写好几何。
手改文件也行(用户目录优先,改完刷新页面即可):
%USERPROFILE%\.dsh\dsh-gal\ui\dialog.png ← 你的底图
%USERPROFILE%\.dsh\dsh-gal\ui\dialog.json ← 描述它的几何
换成自己的对话框图片时,同目录放一个 dialog.json 描述它的几何:
{
"image": { "width": 1280, "height": 720 },
"crop": { "x": 0, "y": 0.42, "w": 1, "h": 0.58 },
"inset": { "left": 0.05, "right": 0.05, "top": 0.07, "bottom": 0.04 },
"footer": { "heightRatio": 0.42, "maxWidthRatio": 0.66 },
"radius": 10,
"logoShare": 0.72
}
| 字段 | 说明 |
|---|---|
crop | 显示原图的哪一块(0~1)。默认只取下方 58%,把上方空白裁掉 |
inset | 内容区相对裁切后画面的内边距 |
footer.heightRatio | 底栏(余额 / 花费)高度最多占文字区的比例,0.15–0.7 |
footer.maxWidthRatio | 底栏最多横向占多宽,0.3–1 |
radius | 圆角像素 |
logoShare | 底栏最多用掉文字区高度的几分之几。自带底图右下角有 logo,所以是 0.72;自己的底图没有 logo 就写 1,余额/花费页会用满整块 |
自带的空白底图是 assets\ui\dialog-blank.png + dialog-blank.json$(1200 \times 700、$crop 全图、
logoShare: 1),用 node scripts/make-blank-plate.mjs 重新生成。
定价表
pricing.json(用户目录优先)—— 改价不用改代码:
{
"updatedAt": "2026-08-17",
"peakHours": [[9, 12], [14, 18]],
"weekendOffPeak": true,
"models": {
"deepseek-v4-flash": { "hit": [0.05, 0.1], "miss": [1.5, 3.0], "out": [4.5, 9.0] },
"deepseek-v4-pro": { "hit": [0.15, 0.3], "miss": [4.5, 9.0], "out": [13.5, 27.0] },
"_default": { "hit": [0.05, 0.1], "miss": [1.5, 3.0], "out": [4.5, 9.0] }
}
}
每条是 [空闲时段价, 高峰时段价],单位人民币 / 百万 token。高峰为北京时间周一至周五
09:00–12:00 与 14:00–18:00,周末全天按空闲价;推理 token 按输出价计费。
配置文件
%USERPROFILE%\.dsh\.dsh-gal.json:
{
"version": 3,
"spritePack": "neri",
"voicePack": "neri",
"spriteFile": "large_neri_07face.png",
"scale": 5,
"volume": 0.5,
"lang": "ja",
"autoPlay": false,
"autoPlayMinutes": 1,
"dialogScale": 5,
"dialogEnabled": true,
"dialogSide": "above",
"dialogOpacity": 0,
"dialogHoldSeconds": 3,
"spriteRevertOnHide": true,
"spriteVisible": true,
"pos": { "hx": "right", "hd": 24, "vy": "bottom", "vd": 24 },
"extraPackRoots": []
}
| 键 | 说明 |
|---|---|
scale | 立绘档位 1–10,默认 5 |
dialogScale | 对话框档位 1–10,默认 5(单位与立绘不同) |
volume | 0–1 的小数 |
lang | ja(日文)或 zh(中文) |
voiceOrder | 语音抽取方式:shuffle(洗牌池,默认)或 random(纯随机) |
autoPlay / autoPlayMinutes | 自动播放开关与间隔(分钟,0.5–240) |
dialogEnabled | 对话框总开关 |
dialogSide | above(立绘上方)或 below |
dialogOpacity | 百分比 0–95。0 = 原图完全不透明,越大越透明 |
dialogHoldSeconds | 语音播完后停留几秒再淡出,默认 3,范围 0.5–120 |
spriteRevertOnHide | 对话框消失后是否切回默认立绘,默认 true |
spriteVisible | 设为 false 只隐藏立绘,保留对话框 |
pos | 位置锚点:hx 为 left/right、hd 是离该边的像素距离;vy 为 top/bottom、vd 同理 |
extraPackRoots | 额外的包目录数组,每个目录的直接子目录会被当成包 |
pricingFile | 自定义定价表的绝对路径 |
设定面板里改(缩放 / 位置 / 不透明度 / 选包 / 换底图)立即生效。
手改这个文件需要重启 DSH Desktop——配置是插件启动时读入一次,之后走内存;
ui\ 下的素材与 packs\ 都是每次请求实时读的,刷新页面即可。