Media cache: still-picture lane re-encodes album-art videos as one looped frame in H.264
This commit is contained in:
@@ -36,6 +36,11 @@ beforeAll(async () => {
|
||||
'-c:v', 'libx264', '-preset', 'ultrafast', '-pix_fmt', 'yuv420p', fx('noaudio.mp4'));
|
||||
ff('-f', 'lavfi', '-i', 'testsrc=size=640x360:rate=30:duration=10', '-f', 'lavfi', '-i', 'sine=frequency=440:duration=10',
|
||||
'-c:v', 'mpeg4', '-c:a', 'aac', '-shortest', fx('mpeg4.mp4'));
|
||||
// Album-art upload: one picture with light grain for 40 s, at YouTube-like bitrate.
|
||||
ff('-f', 'lavfi', '-i', 'testsrc=size=640x360:rate=1:duration=1', '-frames:v', '1', fx('cover.png'));
|
||||
ff('-loop', '1', '-framerate', '25', '-i', fx('cover.png'), '-f', 'lavfi', '-i', 'sine=frequency=330:duration=40',
|
||||
'-vf', 'noise=alls=4:allf=t', '-t', '40', '-c:v', 'libx264', '-preset', 'ultrafast', '-b:v', '600k', '-g', '125',
|
||||
'-pix_fmt', 'yuv420p', '-c:a', 'aac', '-shortest', fx('still40.mp4'));
|
||||
// HEVC as the compression lane writes it (hvc1) and as Safari rejects it (hev1).
|
||||
ff('-f', 'lavfi', '-i', 'testsrc=size=640x360:rate=30:duration=10', '-f', 'lavfi', '-i', 'sine=frequency=440:duration=10',
|
||||
'-c:v', 'libx265', '-preset', 'ultrafast', '-pix_fmt', 'yuv420p', '-tag:v', 'hvc1', '-x265-params', 'log-level=error',
|
||||
@@ -54,7 +59,7 @@ beforeAll(async () => {
|
||||
ff('-i', fx('good.mp4'), '-c', 'copy', '-movflags', '+faststart', fx('good-fs.mp4'));
|
||||
const fs = readFileSync(fx('good-fs.mp4'));
|
||||
writeFileSync(fx('truncated-fs.mp4'), fs.subarray(0, Math.floor(fs.length * 0.6)));
|
||||
});
|
||||
}, 30000);
|
||||
afterAll(() => rmSync(root, { recursive: true, force: true }));
|
||||
|
||||
describe('validateMedia', () => {
|
||||
@@ -396,6 +401,44 @@ describe('media cache jobs', () => {
|
||||
expect(lines.some((l) => /sample predicts/.test(l))).toBe(false);
|
||||
}, 90000);
|
||||
|
||||
test('still-picture lane: one looped frame in H.264, far smaller, still verifies', async () => {
|
||||
await clearDb();
|
||||
const lines = [];
|
||||
const { cache } = makeCache({ fixture: 'still40.mp4', duration: 40, log: { info: (m) => lines.push(m), warn: (m) => lines.push(m) },
|
||||
transcode: { enabled: true, codec: 'hevc', crf: 28, preset: 'ultrafast', threads: 2 } });
|
||||
await cache.init();
|
||||
const first = await cache.ensureCached('stilAAAAAA1', { priority: HIGH });
|
||||
expect(await until(async () => (await dbmod.getMedia('stilAAAAAA1')).optimized === OPT_REV, 60000)).toBe(true);
|
||||
const after = await dbmod.getMedia('stilAAAAAA1');
|
||||
expect(after.gen).toBeGreaterThan(first.gen);
|
||||
expect(after.vcodec).toBe('h264');
|
||||
expect(after.size).toBeLessThan(first.size * 0.6);
|
||||
expect(lines.some((l) => /\(still\)/.test(l))).toBe(true);
|
||||
expect(await cache.verify('stilAAAAAA1')).toBe(true);
|
||||
}, 90000);
|
||||
|
||||
test('a moving copy already done by the HEVC lane is not re-encoded for rev 3', async () => {
|
||||
await clearDb();
|
||||
const seed = makeCache({ fixture: 'fat64.mp4', duration: 64,
|
||||
transcode: { enabled: true, codec: 'hevc', crf: 28, preset: 'ultrafast', threads: 2 } });
|
||||
await seed.cache.init();
|
||||
await seed.cache.ensureCached('rev2AAAAAA1', { priority: HIGH });
|
||||
await until(async () => (await dbmod.getMedia('rev2AAAAAA1')).optimized === OPT_REV, 60000);
|
||||
// Pretend it was evaluated by rev 2 and kept: rev 3 must only run the still check.
|
||||
await dbmod.upsertMedia('rev2AAAAAA1', { optimized: 2 });
|
||||
const before = await dbmod.getMedia('rev2AAAAAA1');
|
||||
// A fresh instance over the same dir + DB, as after a deploy.
|
||||
const again = createMediaCache({
|
||||
dir: seed.dir, db: { getMedia: dbmod.getMedia, upsertMedia: dbmod.upsertMedia, deleteMedia: dbmod.deleteMedia,
|
||||
listMedia: dbmod.listMedia, listMediaLru: dbmod.listMediaLru, touchMedia: dbmod.touchMedia, mediaStats: dbmod.mediaStats },
|
||||
log: silentLog, getInfo: async () => ({ duration: 64 }), download: async () => { throw new Error('no download expected'); },
|
||||
transcode: { enabled: true, codec: 'hevc', crf: 28, preset: 'ultrafast', threads: 2 }, freeBytes: () => 100 * 1024 ** 3,
|
||||
});
|
||||
await again.init();
|
||||
expect(await until(async () => (await dbmod.getMedia('rev2AAAAAA1')).optimized === OPT_REV, 30000)).toBe(true);
|
||||
expect((await dbmod.getMedia('rev2AAAAAA1')).gen).toBe(before.gen);
|
||||
}, 120000);
|
||||
|
||||
test('copies evaluated by an older lane revision are re-queued at boot', async () => {
|
||||
await clearDb();
|
||||
const seed = makeCache();
|
||||
|
||||
Reference in New Issue
Block a user