feat: 完善媒体管理与备份迁移流程
- 视频上传生成缩略图并提供占位封面和失败原因展示 - 支持 HLS 按需重新封装 MP4,并限制临时文件生命周期与访问权限 - 完善视频、文件 API 文档、搜索、批量删除和单次转码质量参数 - 修复备份恢复与跨后端迁移遗漏缩略图、HLS 清单和分片的问题 - 将备份任务、归档和下载权限按用户隔离 - 增加仅清理当前用户数据的高危存储清空流程和二次文字确认 - 修复 S3 中文文件名元数据及 CLI 数据库连接问题 - 补充备份恢复、用户隔离、媒体资源和转码相关测试
This commit is contained in:
1 parent
9ebab8270c
commit
a64be50b73
60 files changed
+2703
-325
No files matched your search
@@ -1,13 +1,20 @@
|
||||
import { promises as fs } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
cleanupExpiredHlsDownloads,
|
||||
DEFAULT_TRANSCODE_CONCURRENCY,
|
||||
DEFAULT_TRANSCODE_PRESET,
|
||||
DEFAULT_TRANSCODE_THREADS,
|
||||
getTranscodeConcurrency,
|
||||
getTranscodeSettings,
|
||||
hlsSegmentNames,
|
||||
hlsPlaylistKey,
|
||||
markHlsDownloadForCleanup,
|
||||
parseTranscodePreset,
|
||||
setTranscodeSettings,
|
||||
summarizeTranscodeFailure,
|
||||
thumbKeyFor
|
||||
} from './transcode'
|
||||
import { isVideoSidecarKey, mediaTypeForKey } from './image-key'
|
||||
@@ -39,6 +46,44 @@ describe('isVideoSidecarKey', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('hlsSegmentNames', () => {
|
||||
it('returns unique relative segment names and ignores tags', () => {
|
||||
expect(hlsSegmentNames([
|
||||
'#EXTM3U',
|
||||
'#EXTINF:6,',
|
||||
'seg000.ts',
|
||||
'#EXTINF:6,',
|
||||
'seg001.ts',
|
||||
'seg000.ts'
|
||||
].join('\n'))).toEqual(['seg000.ts', 'seg001.ts'])
|
||||
})
|
||||
|
||||
it('ignores absolute and traversal paths', () => {
|
||||
expect(hlsSegmentNames([
|
||||
'../secret.ts',
|
||||
'https://example.com/video.ts',
|
||||
'folder/seg.ts',
|
||||
'seg.ts'
|
||||
].join('\n'))).toEqual(['seg.ts'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('cleanupExpiredHlsDownloads', () => {
|
||||
it('keeps fresh downloads and removes files after their marker expires', async () => {
|
||||
const now = Date.now()
|
||||
const fresh = await fs.mkdtemp(join(tmpdir(), 'pichost-hls-download-fresh-'))
|
||||
const expired = await fs.mkdtemp(join(tmpdir(), 'pichost-hls-download-expired-'))
|
||||
|
||||
await markHlsDownloadForCleanup(fresh, now + 60_000)
|
||||
await markHlsDownloadForCleanup(expired, now - 1)
|
||||
await cleanupExpiredHlsDownloads(now)
|
||||
|
||||
await expect(fs.access(fresh)).resolves.toBeUndefined()
|
||||
await expect(fs.access(expired)).rejects.toThrow()
|
||||
await fs.rm(fresh, { recursive: true, force: true })
|
||||
})
|
||||
})
|
||||
|
||||
describe('mediaTypeForKey', () => {
|
||||
it('maps folders to media types', () => {
|
||||
expect(mediaTypeForKey('images/2026/10/a.webp')).toBe('image')
|
||||
@@ -47,6 +92,18 @@ describe('mediaTypeForKey', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('summarizeTranscodeFailure', () => {
|
||||
it('recognizes a missing FFmpeg binary', () => {
|
||||
expect(summarizeTranscodeFailure(new Error('spawn ffmpeg ENOENT')))
|
||||
.toBe('ffmpeg-missing')
|
||||
})
|
||||
|
||||
it('maps other FFmpeg errors to unknown', () => {
|
||||
const error = new Error(`first line\n${'invalid data '.repeat(20)}`)
|
||||
expect(summarizeTranscodeFailure(error)).toBe('unknown')
|
||||
})
|
||||
})
|
||||
|
||||
describe('transcode settings', () => {
|
||||
it('defaults to one concurrent job with one thread', () => {
|
||||
expect(DEFAULT_TRANSCODE_CONCURRENCY).toBe(1)
|
||||
|
||||
Reference in new issue
Block a user