返回 oh-my-ppt
exporter.test.ts
根目录 / tests / unit / html-video / exporter.test.ts
1 import type { NativeImage } from 'electron'
2 import { describe, expect, it, vi } from 'vitest'
3
4 vi.mock('electron', () => ({
5 BrowserWindow: class BrowserWindow {}
6 }))
7
8 vi.mock('@electron-toolkit/utils', () => ({
9 is: { dev: true }
10 }))
11
12 vi.mock('electron-log/main.js', () => ({
13 default: {
14 info: vi.fn(),
15 warn: vi.fn(),
16 error: vi.fn()
17 }
18 }))
19
20 import {
21 VIDEO_EXPORT_FRAME_SIZE,
22 VIDEO_EXPORT_EVEN_DIMENSIONS_FILTER,
23 buildVideoExportFfmpegArgs,
24 normalizeCapturedVideoFrameImage,
25 normalizeVideoExportCaptureFps,
26 normalizeVideoExportFps,
27 normalizeVideoExportSecondsPerPage,
28 resolveVideoExportFrameLayout
29 } from '../../../src/main/io/html-video/exporter'
30
31 describe('html video exporter', () => {
32 it('normalizes captured frames back to the export canvas size', () => {
33 const normalizedImage = {}
34 const image = {
35 resize: vi.fn(() => normalizedImage)
36 }
37
38 expect(normalizeCapturedVideoFrameImage(image as unknown as NativeImage)).toBe(normalizedImage)
39 expect(image.resize).toHaveBeenCalledWith({
40 width: VIDEO_EXPORT_FRAME_SIZE.width,
41 height: VIDEO_EXPORT_FRAME_SIZE.height,
42 quality: 'best'
43 })
44 })
45
46 it('scales ffmpeg output to even dimensions before libx264 encoding', () => {
47 const args = buildVideoExportFfmpegArgs({
48 concatPath: '/tmp/video-export/concat.txt',
49 outputPath: '/tmp/video-export/out.mp4',
50 fps: 30
51 })
52
53 const filterIndex = args.indexOf('-vf')
54 const codecIndex = args.indexOf('-c:v')
55
56 expect(filterIndex).toBeGreaterThan(-1)
57 expect(codecIndex).toBeGreaterThan(filterIndex)
58 expect(args[filterIndex + 1]).toBe(VIDEO_EXPORT_EVEN_DIMENSIONS_FILTER)
59 expect(VIDEO_EXPORT_EVEN_DIMENSIONS_FILTER).toContain('scale=')
60 expect(VIDEO_EXPORT_EVEN_DIMENSIONS_FILTER).toContain('ceil(iw/2)*2')
61 expect(VIDEO_EXPORT_EVEN_DIMENSIONS_FILTER).toContain('ceil(ih/2)*2')
62 expect(args).toContain('libx264')
63 expect(args).toContain('yuv420p')
64 })
65
66 it('keeps video timing options within supported bounds', () => {
67 expect(normalizeVideoExportFps(120)).toBe(60)
68 expect(normalizeVideoExportFps(5)).toBe(12)
69 expect(normalizeVideoExportCaptureFps(30, 24)).toBe(24)
70 expect(normalizeVideoExportSecondsPerPage(0)).toBe(1)
71 })
72
73 it('uses a 2K video frame and centers non-16:9 slide canvases inside it', () => {
74 expect(VIDEO_EXPORT_FRAME_SIZE).toEqual({ width: 2560, height: 1440 })
75
76 const portrait = resolveVideoExportFrameLayout({
77 frameWidth: 2560,
78 frameHeight: 1440,
79 slideWidth: 900,
80 slideHeight: 1600
81 })
82 expect(portrait.scale).toBeCloseTo(1440 / 1600)
83 expect(portrait.top).toBeCloseTo(0)
84 expect(portrait.left).toBeCloseTo((2560 - 900 * (1440 / 1600)) / 2)
85
86 const square = resolveVideoExportFrameLayout({
87 frameWidth: 2560,
88 frameHeight: 1440,
89 slideWidth: 1200,
90 slideHeight: 1200
91 })
92 expect(square.scale).toBeCloseTo(1440 / 1200)
93 expect(square.top).toBeCloseTo(0)
94 expect(square.left).toBeCloseTo(560)
95 })
96 })
97
97 lines TYPESCRIPT