@dan-uni/dan-any 0.7.7 → 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 -18
- package/dist/index.umd.min.js +18 -27
- 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 +5 -5
- package/src/ass-gen/__tests__/canvas.test.ts +6 -2
- 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 -8
- 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
package/dist/index.js
CHANGED
|
@@ -8,7 +8,6 @@ import { Expose, plainToInstance } from "class-transformer";
|
|
|
8
8
|
import { IsDate, IsEmail, IsEnum, IsInt, IsNotEmpty, IsNumber, IsOptional, IsString, Max, Min, validateOrReject } from "class-validator";
|
|
9
9
|
import hh_mm_ss from "hh-mm-ss";
|
|
10
10
|
import jssha from "jssha";
|
|
11
|
-
import { createCanvas } from "canvas";
|
|
12
11
|
import { brotliCompressSync, brotliDecompressSync, gunzipSync, gzipSync } from "node:zlib";
|
|
13
12
|
import { decode, encode as external_base16384_encode } from "base16384";
|
|
14
13
|
var __webpack_require__ = {};
|
|
@@ -1133,14 +1132,14 @@ class UniPool {
|
|
|
1133
1132
|
return this.dans;
|
|
1134
1133
|
case 'danuni.bin':
|
|
1135
1134
|
return this.toPb();
|
|
1135
|
+
case 'bili.xml':
|
|
1136
|
+
return this.toBiliXML();
|
|
1136
1137
|
case 'dplayer.json':
|
|
1137
1138
|
return this.toDplayer();
|
|
1138
1139
|
case 'artplayer.json':
|
|
1139
1140
|
return this.toArtplayer();
|
|
1140
1141
|
case 'ddplay.json':
|
|
1141
1142
|
return this.toDDplay();
|
|
1142
|
-
case 'common.ass':
|
|
1143
|
-
return this.toASS();
|
|
1144
1143
|
default:
|
|
1145
1144
|
{
|
|
1146
1145
|
const message = '(err) Unknown format or unsupported now!';
|
|
@@ -1314,7 +1313,7 @@ class UniPool {
|
|
|
1314
1313
|
static fromASS(ass) {
|
|
1315
1314
|
return parseAssRawField(ass);
|
|
1316
1315
|
}
|
|
1317
|
-
toASS(options = {
|
|
1316
|
+
toASS(canvasCtx, options = {
|
|
1318
1317
|
substyle: {}
|
|
1319
1318
|
}) {
|
|
1320
1319
|
const fn = this.shared.SOID;
|
|
@@ -1322,7 +1321,7 @@ class UniPool {
|
|
|
1322
1321
|
filename: fn,
|
|
1323
1322
|
title: fn,
|
|
1324
1323
|
...options
|
|
1325
|
-
});
|
|
1324
|
+
}, canvasCtx);
|
|
1326
1325
|
}
|
|
1327
1326
|
}
|
|
1328
1327
|
const color_pad = (s)=>s.length < 2 ? `0${s}` : s;
|
|
@@ -1432,8 +1431,7 @@ const splitGrids = ({ fontSize, padding, playResY, bottomSpace })=>{
|
|
|
1432
1431
|
2: arrayOfLength(linesCount, 0)
|
|
1433
1432
|
};
|
|
1434
1433
|
};
|
|
1435
|
-
const
|
|
1436
|
-
const canvasContext = createCanvas(50, 50).getContext('2d');
|
|
1434
|
+
const measureTextWidthConstructor = (canvasContext)=>{
|
|
1437
1435
|
const supportTextMeasure = !!canvasContext.measureText("\u4E2D");
|
|
1438
1436
|
if (supportTextMeasure) return (fontName, fontSize, bold, text)=>{
|
|
1439
1437
|
canvasContext.font = `${bold ? 'bold' : 'normal'} ${fontSize}px ${fontName}`;
|
|
@@ -1442,7 +1440,7 @@ const measureTextWidth = (()=>{
|
|
|
1442
1440
|
};
|
|
1443
1441
|
console.warn('[Warn] node-canvas is installed without text measure support, layout may not be correct');
|
|
1444
1442
|
return (_fontName, fontSize, _bold, text)=>text.length * fontSize;
|
|
1445
|
-
}
|
|
1443
|
+
};
|
|
1446
1444
|
const resolveAvailableFixGrid = (grids, time)=>{
|
|
1447
1445
|
for (const [i, grid] of grids.entries())if (grid <= time) return i;
|
|
1448
1446
|
return -1;
|
|
@@ -1455,7 +1453,7 @@ const resolveAvailableScrollGrid = (grids, rectWidth, screenWidth, time, duratio
|
|
|
1455
1453
|
}
|
|
1456
1454
|
return -1;
|
|
1457
1455
|
};
|
|
1458
|
-
const initializeLayout = (config)=>{
|
|
1456
|
+
const initializeLayout = (config, canvasCtx)=>{
|
|
1459
1457
|
const { playResX, playResY, fontName, fontSize, bold, padding, scrollTime, fixTime, bottomSpace } = config;
|
|
1460
1458
|
const [paddingTop, paddingRight, paddingBottom, paddingLeft] = padding;
|
|
1461
1459
|
const defaultFontSize = fontSize[FontSize.NORMAL];
|
|
@@ -1464,7 +1462,7 @@ const initializeLayout = (config)=>{
|
|
|
1464
1462
|
return (danmaku)=>{
|
|
1465
1463
|
const targetGrids = grids[danmaku.type];
|
|
1466
1464
|
const danmakuFontSize = fontSize[danmaku.fontSizeType];
|
|
1467
|
-
const rectWidth =
|
|
1465
|
+
const rectWidth = measureTextWidthConstructor(canvasCtx)(fontName, danmakuFontSize, bold, danmaku.content) + paddingLeft + paddingRight;
|
|
1468
1466
|
const verticalOffset = paddingTop + Math.round((defaultFontSize - danmakuFontSize) / 2);
|
|
1469
1467
|
if (danmaku.type === DanmakuType.SCROLL) {
|
|
1470
1468
|
const scrollGrids = targetGrids;
|
|
@@ -1509,11 +1507,11 @@ const initializeLayout = (config)=>{
|
|
|
1509
1507
|
}
|
|
1510
1508
|
};
|
|
1511
1509
|
};
|
|
1512
|
-
const layoutDanmaku = (inputList, config)=>{
|
|
1510
|
+
const layoutDanmaku = (inputList, config, canvasCtx)=>{
|
|
1513
1511
|
const list = [
|
|
1514
1512
|
...UniPool2DanmakuLists(inputList)
|
|
1515
1513
|
].sort((x, y)=>x.time - y.time);
|
|
1516
|
-
const layout = initializeLayout(config);
|
|
1514
|
+
const layout = initializeLayout(config, canvasCtx);
|
|
1517
1515
|
return DanmakuList2UniPool(list.map(layout).filter((danmaku)=>!!danmaku));
|
|
1518
1516
|
};
|
|
1519
1517
|
const formatTime = (seconds)=>{
|
|
@@ -1748,10 +1746,10 @@ const ass_gen_config = (overrides = {})=>{
|
|
|
1748
1746
|
config.backColor = config.backColor && formatColor(hexColorToRGB(config.backColor));
|
|
1749
1747
|
return config;
|
|
1750
1748
|
};
|
|
1751
|
-
function generateASS(danmaku, options) {
|
|
1749
|
+
function generateASS(danmaku, options, canvasCtx) {
|
|
1752
1750
|
const config = ass_gen_config(options.substyle);
|
|
1753
1751
|
const mergedList = danmaku.merge(config.mergeIn);
|
|
1754
|
-
const layoutList = layoutDanmaku(mergedList, config);
|
|
1752
|
+
const layoutList = layoutDanmaku(mergedList, config, canvasCtx);
|
|
1755
1753
|
const content = ass_create(layoutList, danmaku, config, {
|
|
1756
1754
|
filename: options?.filename || 'unknown',
|
|
1757
1755
|
title: options?.title || 'unknown'
|
|
@@ -2061,14 +2059,14 @@ class src_UniPool {
|
|
|
2061
2059
|
return this.dans;
|
|
2062
2060
|
case 'danuni.bin':
|
|
2063
2061
|
return this.toPb();
|
|
2062
|
+
case 'bili.xml':
|
|
2063
|
+
return this.toBiliXML();
|
|
2064
2064
|
case 'dplayer.json':
|
|
2065
2065
|
return this.toDplayer();
|
|
2066
2066
|
case 'artplayer.json':
|
|
2067
2067
|
return this.toArtplayer();
|
|
2068
2068
|
case 'ddplay.json':
|
|
2069
2069
|
return this.toDDplay();
|
|
2070
|
-
case 'common.ass':
|
|
2071
|
-
return this.toASS();
|
|
2072
2070
|
default:
|
|
2073
2071
|
{
|
|
2074
2072
|
const message = '(err) Unknown format or unsupported now!';
|
|
@@ -2242,7 +2240,7 @@ class src_UniPool {
|
|
|
2242
2240
|
static fromASS(ass) {
|
|
2243
2241
|
return parseAssRawField(ass);
|
|
2244
2242
|
}
|
|
2245
|
-
toASS(options = {
|
|
2243
|
+
toASS(canvasCtx, options = {
|
|
2246
2244
|
substyle: {}
|
|
2247
2245
|
}) {
|
|
2248
2246
|
const fn = this.shared.SOID;
|
|
@@ -2250,7 +2248,7 @@ class src_UniPool {
|
|
|
2250
2248
|
filename: fn,
|
|
2251
2249
|
title: fn,
|
|
2252
2250
|
...options
|
|
2253
|
-
});
|
|
2251
|
+
}, canvasCtx);
|
|
2254
2252
|
}
|
|
2255
2253
|
}
|
|
2256
2254
|
export { UniDM, dm_gen_namespaceObject as UniDMTools, id_gen_namespaceObject as UniIDTools, src_UniPool as UniPool, platform_namespaceObject as platform };
|
package/dist/index.umd.min.js
CHANGED
|
@@ -4744,14 +4744,6 @@
|
|
|
4744
4744
|
return intrinsic;
|
|
4745
4745
|
};
|
|
4746
4746
|
},
|
|
4747
|
-
"../../node_modules/.pnpm/canvas@3.1.2/node_modules/canvas/browser.js": function(__unused_webpack_module, exports1) {
|
|
4748
|
-
exports1.createCanvas = function(width, height) {
|
|
4749
|
-
return Object.assign(document.createElement('canvas'), {
|
|
4750
|
-
width: width,
|
|
4751
|
-
height: height
|
|
4752
|
-
});
|
|
4753
|
-
};
|
|
4754
|
-
},
|
|
4755
4747
|
"../../node_modules/.pnpm/define-data-property@1.1.4/node_modules/define-data-property/index.js": function(module1, __unused_webpack_exports, __webpack_require__) {
|
|
4756
4748
|
"use strict";
|
|
4757
4749
|
var $defineProperty = __webpack_require__("../../node_modules/.pnpm/es-define-property@1.0.1/node_modules/es-define-property/index.js");
|
|
@@ -17037,7 +17029,8 @@ and limitations under the License.
|
|
|
17037
17029
|
const field = map.field();
|
|
17038
17030
|
let key;
|
|
17039
17031
|
let val;
|
|
17040
|
-
const
|
|
17032
|
+
const len = reader.uint32();
|
|
17033
|
+
const end = reader.pos + len;
|
|
17041
17034
|
while(reader.pos < end){
|
|
17042
17035
|
const [fieldNo] = reader.tag();
|
|
17043
17036
|
switch(fieldNo){
|
|
@@ -24104,14 +24097,14 @@ and limitations under the License.
|
|
|
24104
24097
|
return this.dans;
|
|
24105
24098
|
case 'danuni.bin':
|
|
24106
24099
|
return this.toPb();
|
|
24100
|
+
case 'bili.xml':
|
|
24101
|
+
return this.toBiliXML();
|
|
24107
24102
|
case 'dplayer.json':
|
|
24108
24103
|
return this.toDplayer();
|
|
24109
24104
|
case 'artplayer.json':
|
|
24110
24105
|
return this.toArtplayer();
|
|
24111
24106
|
case 'ddplay.json':
|
|
24112
24107
|
return this.toDDplay();
|
|
24113
|
-
case 'common.ass':
|
|
24114
|
-
return this.toASS();
|
|
24115
24108
|
default:
|
|
24116
24109
|
{
|
|
24117
24110
|
const message = '(err) Unknown format or unsupported now!';
|
|
@@ -24285,7 +24278,7 @@ and limitations under the License.
|
|
|
24285
24278
|
static fromASS(ass) {
|
|
24286
24279
|
return parseAssRawField(ass);
|
|
24287
24280
|
}
|
|
24288
|
-
toASS(options = {
|
|
24281
|
+
toASS(canvasCtx, options = {
|
|
24289
24282
|
substyle: {}
|
|
24290
24283
|
}) {
|
|
24291
24284
|
const fn = this.shared.SOID;
|
|
@@ -24293,7 +24286,7 @@ and limitations under the License.
|
|
|
24293
24286
|
filename: fn,
|
|
24294
24287
|
title: fn,
|
|
24295
24288
|
...options
|
|
24296
|
-
});
|
|
24289
|
+
}, canvasCtx);
|
|
24297
24290
|
}
|
|
24298
24291
|
}
|
|
24299
24292
|
const color_pad = (s)=>s.length < 2 ? `0${s}` : s;
|
|
@@ -24332,7 +24325,6 @@ and limitations under the License.
|
|
|
24332
24325
|
};
|
|
24333
24326
|
const getDecoratingColor = (color)=>isDarkColor(color) ? WHITE : BLACK;
|
|
24334
24327
|
const isWhite = (color)=>255 === color.r && 255 === color.g && 255 === color.b;
|
|
24335
|
-
var browser = __webpack_require__("../../node_modules/.pnpm/canvas@3.1.2/node_modules/canvas/browser.js");
|
|
24336
24328
|
const DanmakuType = {
|
|
24337
24329
|
SCROLL: 1,
|
|
24338
24330
|
BOTTOM: 2,
|
|
@@ -24404,8 +24396,7 @@ and limitations under the License.
|
|
|
24404
24396
|
2: arrayOfLength(linesCount, 0)
|
|
24405
24397
|
};
|
|
24406
24398
|
};
|
|
24407
|
-
const
|
|
24408
|
-
const canvasContext = (0, browser.createCanvas)(50, 50).getContext('2d');
|
|
24399
|
+
const measureTextWidthConstructor = (canvasContext)=>{
|
|
24409
24400
|
const supportTextMeasure = !!canvasContext.measureText("\u4E2D");
|
|
24410
24401
|
if (supportTextMeasure) return (fontName, fontSize, bold, text)=>{
|
|
24411
24402
|
canvasContext.font = `${bold ? 'bold' : 'normal'} ${fontSize}px ${fontName}`;
|
|
@@ -24414,7 +24405,7 @@ and limitations under the License.
|
|
|
24414
24405
|
};
|
|
24415
24406
|
console.warn('[Warn] node-canvas is installed without text measure support, layout may not be correct');
|
|
24416
24407
|
return (_fontName, fontSize, _bold, text)=>text.length * fontSize;
|
|
24417
|
-
}
|
|
24408
|
+
};
|
|
24418
24409
|
const resolveAvailableFixGrid = (grids, time)=>{
|
|
24419
24410
|
for (const [i, grid] of grids.entries())if (grid <= time) return i;
|
|
24420
24411
|
return -1;
|
|
@@ -24427,7 +24418,7 @@ and limitations under the License.
|
|
|
24427
24418
|
}
|
|
24428
24419
|
return -1;
|
|
24429
24420
|
};
|
|
24430
|
-
const initializeLayout = (config)=>{
|
|
24421
|
+
const initializeLayout = (config, canvasCtx)=>{
|
|
24431
24422
|
const { playResX, playResY, fontName, fontSize, bold, padding, scrollTime, fixTime, bottomSpace } = config;
|
|
24432
24423
|
const [paddingTop, paddingRight, paddingBottom, paddingLeft] = padding;
|
|
24433
24424
|
const defaultFontSize = fontSize[FontSize.NORMAL];
|
|
@@ -24436,7 +24427,7 @@ and limitations under the License.
|
|
|
24436
24427
|
return (danmaku)=>{
|
|
24437
24428
|
const targetGrids = grids[danmaku.type];
|
|
24438
24429
|
const danmakuFontSize = fontSize[danmaku.fontSizeType];
|
|
24439
|
-
const rectWidth =
|
|
24430
|
+
const rectWidth = measureTextWidthConstructor(canvasCtx)(fontName, danmakuFontSize, bold, danmaku.content) + paddingLeft + paddingRight;
|
|
24440
24431
|
const verticalOffset = paddingTop + Math.round((defaultFontSize - danmakuFontSize) / 2);
|
|
24441
24432
|
if (danmaku.type === DanmakuType.SCROLL) {
|
|
24442
24433
|
const scrollGrids = targetGrids;
|
|
@@ -24481,11 +24472,11 @@ and limitations under the License.
|
|
|
24481
24472
|
}
|
|
24482
24473
|
};
|
|
24483
24474
|
};
|
|
24484
|
-
const layoutDanmaku = (inputList, config)=>{
|
|
24475
|
+
const layoutDanmaku = (inputList, config, canvasCtx)=>{
|
|
24485
24476
|
const list = [
|
|
24486
24477
|
...UniPool2DanmakuLists(inputList)
|
|
24487
24478
|
].sort((x, y)=>x.time - y.time);
|
|
24488
|
-
const layout = initializeLayout(config);
|
|
24479
|
+
const layout = initializeLayout(config, canvasCtx);
|
|
24489
24480
|
return DanmakuList2UniPool(list.map(layout).filter((danmaku)=>!!danmaku));
|
|
24490
24481
|
};
|
|
24491
24482
|
const formatTime = (seconds)=>{
|
|
@@ -24723,10 +24714,10 @@ and limitations under the License.
|
|
|
24723
24714
|
config.backColor = config.backColor && formatColor(hexColorToRGB(config.backColor));
|
|
24724
24715
|
return config;
|
|
24725
24716
|
};
|
|
24726
|
-
function generateASS(danmaku, options) {
|
|
24717
|
+
function generateASS(danmaku, options, canvasCtx) {
|
|
24727
24718
|
const config = ass_gen_config(options.substyle);
|
|
24728
24719
|
const mergedList = danmaku.merge(config.mergeIn);
|
|
24729
|
-
const layoutList = layoutDanmaku(mergedList, config);
|
|
24720
|
+
const layoutList = layoutDanmaku(mergedList, config, canvasCtx);
|
|
24730
24721
|
const content = ass_create(layoutList, danmaku, config, {
|
|
24731
24722
|
filename: options?.filename || 'unknown',
|
|
24732
24723
|
title: options?.title || 'unknown'
|
|
@@ -25037,14 +25028,14 @@ and limitations under the License.
|
|
|
25037
25028
|
return this.dans;
|
|
25038
25029
|
case 'danuni.bin':
|
|
25039
25030
|
return this.toPb();
|
|
25031
|
+
case 'bili.xml':
|
|
25032
|
+
return this.toBiliXML();
|
|
25040
25033
|
case 'dplayer.json':
|
|
25041
25034
|
return this.toDplayer();
|
|
25042
25035
|
case 'artplayer.json':
|
|
25043
25036
|
return this.toArtplayer();
|
|
25044
25037
|
case 'ddplay.json':
|
|
25045
25038
|
return this.toDDplay();
|
|
25046
|
-
case 'common.ass':
|
|
25047
|
-
return this.toASS();
|
|
25048
25039
|
default:
|
|
25049
25040
|
{
|
|
25050
25041
|
const message = '(err) Unknown format or unsupported now!';
|
|
@@ -25218,7 +25209,7 @@ and limitations under the License.
|
|
|
25218
25209
|
static fromASS(ass) {
|
|
25219
25210
|
return parseAssRawField(ass);
|
|
25220
25211
|
}
|
|
25221
|
-
toASS(options = {
|
|
25212
|
+
toASS(canvasCtx, options = {
|
|
25222
25213
|
substyle: {}
|
|
25223
25214
|
}) {
|
|
25224
25215
|
const fn = this.shared.SOID;
|
|
@@ -25226,7 +25217,7 @@ and limitations under the License.
|
|
|
25226
25217
|
filename: fn,
|
|
25227
25218
|
title: fn,
|
|
25228
25219
|
...options
|
|
25229
|
-
});
|
|
25220
|
+
}, canvasCtx);
|
|
25230
25221
|
}
|
|
25231
25222
|
}
|
|
25232
25223
|
})();
|
|
@@ -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",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"author": "rinne",
|
|
19
19
|
"main": "dist/index.js",
|
|
20
|
-
"module": "
|
|
20
|
+
"module": "dist/index.js",
|
|
21
21
|
"types": "dist/src/index.d.ts",
|
|
22
22
|
"browser": "dist/index.umd.min.js",
|
|
23
23
|
"scripts": {
|
|
@@ -31,9 +31,8 @@
|
|
|
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
38
|
"fast-xml-parser": "^5.2.5",
|
|
@@ -45,10 +44,11 @@
|
|
|
45
44
|
},
|
|
46
45
|
"devDependencies": {
|
|
47
46
|
"@bufbuild/buf": "^1.56.0",
|
|
48
|
-
"@bufbuild/protoc-gen-es": "^2.6.
|
|
47
|
+
"@bufbuild/protoc-gen-es": "^2.6.3",
|
|
49
48
|
"@types/fs-extra": "^11.0.4",
|
|
50
49
|
"@types/hh-mm-ss": "^1.2.3",
|
|
51
50
|
"@types/json-bigint": "^1.0.4",
|
|
51
|
+
"canvas": "^3.1.2",
|
|
52
52
|
"protobufjs": "^7.5.3"
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import { createCanvas } from 'canvas'
|
|
1
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
14
|
// assert(width >= 25 * text.length)
|
|
@@ -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,6 +1,5 @@
|
|
|
1
|
-
import { createCanvas } from 'canvas'
|
|
2
1
|
import type { UniPool } from '../..'
|
|
3
|
-
import type { Danmaku, SubtitleStyle } from '../types'
|
|
2
|
+
import type { CanvasCtx, Danmaku, SubtitleStyle } from '../types'
|
|
4
3
|
|
|
5
4
|
import { DanmakuType, FontSize } from '../types'
|
|
6
5
|
import { DanmakuList2UniPool, UniPool2DanmakuLists } from './danconvert'
|
|
@@ -77,8 +76,7 @@ const splitGrids = ({
|
|
|
77
76
|
}
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
export const
|
|
81
|
-
const canvasContext = createCanvas(50, 50).getContext('2d')
|
|
79
|
+
export const measureTextWidthConstructor = (canvasContext: CanvasCtx) => {
|
|
82
80
|
const supportTextMeasure = !!canvasContext.measureText('中')
|
|
83
81
|
|
|
84
82
|
if (supportTextMeasure) {
|
|
@@ -99,7 +97,7 @@ export const measureTextWidth = (() => {
|
|
|
99
97
|
)
|
|
100
98
|
return (_fontName: string, fontSize: number, _bold: boolean, text: string) =>
|
|
101
99
|
text.length * fontSize
|
|
102
|
-
}
|
|
100
|
+
}
|
|
103
101
|
|
|
104
102
|
// 找到能用的行
|
|
105
103
|
const resolveAvailableFixGrid = (grids: FixGrid[], time: number) => {
|
|
@@ -138,7 +136,7 @@ const resolveAvailableScrollGrid = (
|
|
|
138
136
|
return -1
|
|
139
137
|
}
|
|
140
138
|
|
|
141
|
-
const initializeLayout = (config: SubtitleStyle) => {
|
|
139
|
+
const initializeLayout = (config: SubtitleStyle, canvasCtx: CanvasCtx) => {
|
|
142
140
|
const {
|
|
143
141
|
playResX,
|
|
144
142
|
playResY,
|
|
@@ -160,7 +158,12 @@ const initializeLayout = (config: SubtitleStyle) => {
|
|
|
160
158
|
const targetGrids = grids[danmaku.type as keyof DanmakuGrids]
|
|
161
159
|
const danmakuFontSize = fontSize[danmaku.fontSizeType]
|
|
162
160
|
const rectWidth =
|
|
163
|
-
|
|
161
|
+
measureTextWidthConstructor(canvasCtx)(
|
|
162
|
+
fontName,
|
|
163
|
+
danmakuFontSize,
|
|
164
|
+
bold,
|
|
165
|
+
danmaku.content,
|
|
166
|
+
) +
|
|
164
167
|
paddingLeft +
|
|
165
168
|
paddingRight
|
|
166
169
|
const verticalOffset =
|
|
@@ -228,11 +231,12 @@ const initializeLayout = (config: SubtitleStyle) => {
|
|
|
228
231
|
export const layoutDanmaku = (
|
|
229
232
|
inputList: UniPool,
|
|
230
233
|
config: SubtitleStyle,
|
|
234
|
+
canvasCtx: CanvasCtx,
|
|
231
235
|
): UniPool => {
|
|
232
236
|
const list = [...UniPool2DanmakuLists(inputList)].sort(
|
|
233
237
|
(x, y) => x.time - y.time,
|
|
234
238
|
)
|
|
235
|
-
const layout = initializeLayout(config)
|
|
239
|
+
const layout = initializeLayout(config, canvasCtx)
|
|
236
240
|
|
|
237
241
|
return DanmakuList2UniPool(list.map(layout).filter((danmaku) => !!danmaku))
|
|
238
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
|
|