mirror of
https://github.com/hectorqin/reader.git
synced 2026-11-13 06:20:54 +08:00
Support local and OpenList libraries, metadata, independent media navigation, themes, and audio/video playback. Preserve approved v2 prototypes and current documentation; archive superseded review artifacts and keep generated validation output outside version control.
40 lines
3.5 KiB
TypeScript
40 lines
3.5 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtemp,mkdir,readFile,rm } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { promisify } from 'node:util';
|
|
import { execFile } from 'node:child_process';
|
|
import { Db } from '../src/db/index.ts';
|
|
import { MediaLibraries } from '../src/media/libraries.ts';
|
|
import { MediaScanner } from '../src/media/scanner.ts';
|
|
import { MediaArtwork } from '../src/media/artwork.ts';
|
|
|
|
for(const [extension,codec] of [['mp3','libmp3lame'],['flac','flac'],['m4a','aac'],['m4b','aac']] as const) test(`real ${extension} embedded artwork survives scanning and authenticated cover retrieval`, {skip:!process.env.MEDIA_TEST_FFMPEG||!process.env.MEDIA_FFPROBE_PATH},async t=>{
|
|
const root=await mkdtemp(join(tmpdir(),'embedded-artwork-')),media=join(root,'media');await mkdir(media);
|
|
const db=new Db(':memory:'),libraries=new MediaLibraries(db),scanner=new MediaScanner(db,libraries);
|
|
t.after(async()=>{await scanner.close();db.close();await rm(root,{recursive:true,force:true});});
|
|
const previous=process.env.MEDIA_FFMPEG_PATH;process.env.MEDIA_FFMPEG_PATH=process.env.MEDIA_TEST_FFMPEG;
|
|
t.after(()=>{if(previous===undefined)delete process.env.MEDIA_FFMPEG_PATH;else process.env.MEDIA_FFMPEG_PATH=previous;});
|
|
const run=promisify(execFile),image=join(root,'cover.png'),file=join(media,`song.${extension}`);
|
|
await run(process.env.MEDIA_TEST_FFMPEG!,['-nostdin','-loglevel','error','-f','lavfi','-i','color=c=red:s=32x32','-frames:v','1',image],{windowsHide:true,timeout:30000});
|
|
await run(process.env.MEDIA_TEST_FFMPEG!,['-nostdin','-loglevel','error','-f','lavfi','-i','sine=duration=1','-i',image,'-map','0:a','-map','1:v','-c:a',codec,'-c:v','copy','-disposition:v','attached_pic',file],{windowsHide:true,timeout:30000});
|
|
const before=await readFile(file),actor={id:'admin',role:'admin'} as const;
|
|
const lib=await libraries.create(actor,{name:'music',kind:extension==='m4b'?'audiobook':'music',root:media,access:'all'});
|
|
const job=scanner.start(actor,lib.id);await scanner.wait(lib.id);assert.equal(scanner.job(actor,job.id).state,'complete');
|
|
const album=scanner.catalog.list(actor,lib.id,{kind:extension==='m4b'?'audiobook':'album'}).items[0]!;
|
|
assert.equal(typeof album.metadata.embeddedCoverAssetId,'string',JSON.stringify(db.all('SELECT probe_status,technical_json FROM media_assets')));
|
|
const artwork=new MediaArtwork(db,libraries);
|
|
const result=await artwork.cover(actor,album.id);assert.equal(result.contentType,'image/png');assert.deepEqual(result.bytes,await readFile(image));
|
|
assert.deepEqual((await artwork.cover(actor,album.id)).bytes,result.bytes);assert.deepEqual(await readFile(file),before);
|
|
const asset=db.get<{id:string;technical_json:string}>('SELECT id,technical_json FROM media_assets')!;
|
|
const legacy=JSON.parse(asset.technical_json);for(const stream of legacy.streams)delete stream.attachedPicture;
|
|
db.run('UPDATE media_assets SET technical_json=? WHERE id=?',JSON.stringify(legacy),asset.id);
|
|
db.run("UPDATE media_items SET metadata_json=json_remove(metadata_json,'$.embeddedCoverAssetId')");
|
|
scanner.start(actor,lib.id);await scanner.wait(lib.id);
|
|
assert.equal(scanner.catalog.detail(actor,album.id).metadata.embeddedCoverAssetId,asset.id);
|
|
assert.deepEqual((await artwork.cover(actor,album.id)).bytes,result.bytes);
|
|
db.run('UPDATE media_libraries SET access=? WHERE id=?','restricted',lib.id);
|
|
await assert.rejects(artwork.cover({id:'member',role:'member'},album.id),{statusCode:404});
|
|
});
|