@orderg-x/dsh-video-preview 0.5.0 → 0.5.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/lib/index.js +1 -1
- package/lib/look.js +39 -23
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -551,7 +551,7 @@ export function lookTool(ctx, getBrowser) {
|
|
|
551
551
|
return defineTool({
|
|
552
552
|
name: 'look_video',
|
|
553
553
|
description:
|
|
554
|
-
'看一个本地视频(或音频)的内容:在电脑上用看不见的 Chrome / Edge
|
|
554
|
+
'看一个本地视频(或音频)的内容:在电脑上用看不见的 Chrome / Edge 打开,按时间取几帧拼成一张图交给你(每格上方标了时间),' +
|
|
555
555
|
'再量声音(有没有声、多大、哪段没声、有没有爆音、是不是只有一边有声),顺便查手机上能不能放(编码、文件头位置)。' +
|
|
556
556
|
'你做完、改完视频(录屏、剪辑、转码、配音、合成动画)后,交给用户之前先用它看一眼;用户说视频「不对」「黑屏」「没声音」「手机放不了」时也用它。' +
|
|
557
557
|
`at 给几个时间点(如 ["0:12", "1:05"],最多 ${MAX_AT} 个)看那几刻的大图;from / to 只看一段;frames 改取几帧(最多 ${MAX_FRAMES})。` +
|
package/lib/look.js
CHANGED
|
@@ -103,21 +103,24 @@ export function pageTools() {
|
|
|
103
103
|
}
|
|
104
104
|
const luma = (d, i) => d[i] * 0.2126 + d[i + 1] * 0.7152 + d[i + 2] * 0.0722
|
|
105
105
|
/**
|
|
106
|
-
*
|
|
107
|
-
* 和一个
|
|
106
|
+
* 一帧的亮度、反差(16×9 上算)、亮着的点占多少(千分之几,160×90 上数,黑底上的字也数得到),
|
|
107
|
+
* 和一个 64×36 的灰度指纹(比前后帧是不是一样:录屏里只有一小块在变也看得出来)
|
|
108
108
|
*/
|
|
109
|
-
function stats(sg, mg) {
|
|
109
|
+
function stats(sg, mg, fg) {
|
|
110
110
|
const d = sg.getImageData(0, 0, 16, 9).data
|
|
111
|
-
const fp = []
|
|
112
111
|
let sum = 0
|
|
113
|
-
|
|
114
|
-
const
|
|
112
|
+
const ys = []
|
|
113
|
+
for (let i = 0; i < d.length; i += 4) { const y = luma(d, i); ys.push(y); sum += y }
|
|
114
|
+
const mean = sum / ys.length
|
|
115
115
|
let sq = 0
|
|
116
|
-
for (const y of
|
|
116
|
+
for (const y of ys) sq += (y - mean) * (y - mean)
|
|
117
117
|
const m = mg.getImageData(0, 0, 160, 90).data
|
|
118
118
|
let lit = 0
|
|
119
119
|
for (let i = 0; i < m.length; i += 4) if (luma(m, i) > 48) lit++
|
|
120
|
-
|
|
120
|
+
const f = fg.getImageData(0, 0, 64, 36).data
|
|
121
|
+
const fp = []
|
|
122
|
+
for (let i = 0; i < f.length; i += 4) fp.push(Math.round(luma(f, i)))
|
|
123
|
+
return { y: Math.round(mean), sd: Math.round(Math.sqrt(sq / ys.length)), lit: Math.round((lit / (m.length / 4)) * 10000) / 10, fp }
|
|
121
124
|
}
|
|
122
125
|
function label(g, text, x, y, size) {
|
|
123
126
|
g.font = `600 ${size}px -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif`
|
|
@@ -132,7 +135,7 @@ export function pageTools() {
|
|
|
132
135
|
g.fillText(text, x + size * 0.4, y + h / 2)
|
|
133
136
|
}
|
|
134
137
|
/**
|
|
135
|
-
* 取帧。sheet:拼成一张(cols 列、每格 cw×ch
|
|
138
|
+
* 取帧。sheet:拼成一张(cols 列、每格 cw×ch,上面一条 lh 高写时间);否则每帧一张大图(长边 edge)。
|
|
136
139
|
* 返回 { jpgs: [base64…], stats: [{ t, y, sd, lit, fp, ok }] }
|
|
137
140
|
*/
|
|
138
141
|
async function frames(times, labels, o) {
|
|
@@ -146,13 +149,17 @@ export function pageTools() {
|
|
|
146
149
|
mid.width = 160
|
|
147
150
|
mid.height = 90
|
|
148
151
|
const mg = mid.getContext('2d', { willReadFrequently: true })
|
|
152
|
+
const fpc = document.createElement('canvas')
|
|
153
|
+
fpc.width = 64
|
|
154
|
+
fpc.height = 36
|
|
155
|
+
const fg = fpc.getContext('2d', { willReadFrequently: true })
|
|
149
156
|
const out = { jpgs: [], stats: [] }
|
|
150
157
|
let sheet = null
|
|
151
158
|
let g = null
|
|
152
159
|
if (o.sheet) {
|
|
153
160
|
sheet = document.createElement('canvas')
|
|
154
161
|
sheet.width = o.cols * o.cw + (o.cols + 1) * o.gap
|
|
155
|
-
sheet.height = Math.ceil(times.length / o.cols) * o.ch + (Math.ceil(times.length / o.cols) + 1) * o.gap
|
|
162
|
+
sheet.height = Math.ceil(times.length / o.cols) * (o.ch + o.lh) + (Math.ceil(times.length / o.cols) + 1) * o.gap
|
|
156
163
|
g = sheet.getContext('2d')
|
|
157
164
|
g.fillStyle = '#1b1d22'
|
|
158
165
|
g.fillRect(0, 0, sheet.width, sheet.height)
|
|
@@ -161,12 +168,16 @@ export function pageTools() {
|
|
|
161
168
|
const ok = await seek(times[i])
|
|
162
169
|
sg.drawImage(v, 0, 0, 16, 9)
|
|
163
170
|
mg.drawImage(v, 0, 0, 160, 90)
|
|
164
|
-
|
|
171
|
+
fg.drawImage(v, 0, 0, 64, 36)
|
|
172
|
+
out.stats.push({ t: times[i], ok, ...stats(sg, mg, fg) })
|
|
165
173
|
if (sheet) {
|
|
166
174
|
const x = o.gap + (i % o.cols) * (o.cw + o.gap)
|
|
167
|
-
const y = o.gap + Math.floor(i / o.cols) * (o.ch + o.gap)
|
|
168
|
-
g.
|
|
169
|
-
|
|
175
|
+
const y = o.gap + Math.floor(i / o.cols) * (o.ch + o.lh + o.gap)
|
|
176
|
+
g.font = `600 ${o.fs}px -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif`
|
|
177
|
+
g.fillStyle = '#e6e6e6'
|
|
178
|
+
g.textBaseline = 'middle'
|
|
179
|
+
g.fillText(labels[i], x + 2, y + o.lh / 2)
|
|
180
|
+
g.drawImage(v, x, y + o.lh, o.cw, o.ch)
|
|
170
181
|
} else {
|
|
171
182
|
const k = Math.min(1, o.edge / Math.max(W, H))
|
|
172
183
|
const c = document.createElement('canvas')
|
|
@@ -229,13 +240,16 @@ export function sheetLayout(w, h, n) {
|
|
|
229
240
|
const gap = 8
|
|
230
241
|
let cw = Math.floor((SHEET_W - (cols + 1) * gap) / cols)
|
|
231
242
|
let ch = Math.round((cw * (h || 9)) / (w || 16))
|
|
232
|
-
|
|
243
|
+
// 每格上面留一条写时间,不盖住画面左上角(标题常在那)
|
|
244
|
+
const fs = Math.max(13, Math.round(cw / 18))
|
|
245
|
+
const lh = Math.round(fs * 1.6)
|
|
246
|
+
const tall = rows * (ch + lh) + (rows + 1) * gap
|
|
233
247
|
if (tall > 1568) {
|
|
234
|
-
const k = (1568 - (rows + 1) * gap) / (rows * ch)
|
|
248
|
+
const k = (1568 - (rows + 1) * gap - rows * lh) / (rows * ch)
|
|
235
249
|
cw = Math.floor(cw * k)
|
|
236
250
|
ch = Math.floor(ch * k)
|
|
237
251
|
}
|
|
238
|
-
return { cols, cw, ch, gap }
|
|
252
|
+
return { cols, cw, ch, gap, fs, lh }
|
|
239
253
|
}
|
|
240
254
|
|
|
241
255
|
/**
|
|
@@ -343,9 +357,11 @@ export function audioFindings(a, { duration, short = false } = {}) {
|
|
|
343
357
|
|
|
344
358
|
const BLACK_Y = 16 // 平均亮度
|
|
345
359
|
const BLACK_LIT = 3 // 亮着的点不到千分之三(黑底上有字、有画面的不算黑屏;只有鼠标指针的还算)
|
|
346
|
-
const
|
|
360
|
+
const CELL_DIFF = 8 // 指纹里一格亮度差这么多才算变了(压缩噪点不算)
|
|
347
361
|
|
|
348
|
-
|
|
362
|
+
/** 两帧指纹里变了的格子:64×36 里不超过 2 格(闪烁的光标、一点噪点)算一样 */
|
|
363
|
+
const changed = (a, b) => a.reduce((n, x, i) => n + (Math.abs(x - b[i]) > CELL_DIFF ? 1 : 0), 0)
|
|
364
|
+
const same = (a, b) => changed(a, b) <= Math.max(2, a.length * 0.001)
|
|
349
365
|
|
|
350
366
|
/** 取到的帧 → 问题:黑屏、画面一直没变 */
|
|
351
367
|
export function frameFindings(stats, { duration, short = false, sheet = true } = {}) {
|
|
@@ -368,13 +384,13 @@ export function frameFindings(stats, { duration, short = false, sheet = true } =
|
|
|
368
384
|
if (last && sheet) issues.push({ level: 'warn', text: `最后一帧(${t(stats[stats.length - 1].t)})是黑的:结尾可能多了一段黑屏` })
|
|
369
385
|
}
|
|
370
386
|
if (sheet && stats.length >= 3 && duration > 2) {
|
|
371
|
-
const
|
|
372
|
-
if (
|
|
387
|
+
const allSame = stats.slice(1).every((s, i) => same(s.fp, stats[i].fp))
|
|
388
|
+
if (allSame) issues.push({ level: 'warn', text: `取的 ${stats.length} 帧几乎一模一样:整段画面可能没动(录屏卡住了、录到了静止的窗口);要是本来就是静态画面就没事` })
|
|
373
389
|
else {
|
|
374
390
|
// 连着好几格一样,而且占了一大段
|
|
375
391
|
let s = 0
|
|
376
392
|
for (let i = 1; i <= stats.length; i++) {
|
|
377
|
-
const cont = i < stats.length &&
|
|
393
|
+
const cont = i < stats.length && same(stats[i].fp, stats[i - 1].fp)
|
|
378
394
|
if (!cont) {
|
|
379
395
|
if (i - s >= 4 && stats[i - 1].t - stats[s].t >= duration * 0.3) issues.push({ level: 'warn', text: `${t(stats[s].t)}–${t(stats[i - 1].t)} 画面没变(${i - s} 帧一样):要是本该有动作,可能卡住了` })
|
|
380
396
|
s = i
|
|
@@ -427,7 +443,7 @@ export function formatLook({ name, size, probe, res, kind, ext = '', audioSkippe
|
|
|
427
443
|
}
|
|
428
444
|
if (res && res.jpgs.length) {
|
|
429
445
|
if (res.sheet) {
|
|
430
|
-
lines.push(`图 1:${res.times.length} 帧,从 ${fmtTime(res.times[0], short)} 到 ${fmtTime(res.times[res.times.length - 1], short)}
|
|
446
|
+
lines.push(`图 1:${res.times.length} 帧,从 ${fmtTime(res.times[0], short)} 到 ${fmtTime(res.times[res.times.length - 1], short)} 均匀取,从左到右、从上到下,每格上方是时间。`)
|
|
431
447
|
} else {
|
|
432
448
|
res.times.forEach((t, i) => lines.push(`图 ${i + 1}:${fmtTime(t, short)} 那一刻(原画面缩到长边 ${BIG_EDGE} 以内)。`))
|
|
433
449
|
}
|