mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
Add series metadata tagging and tests
Include series and seriesPart metadata when tagging audio files. For m4b output the code uses show and episode_id; for mp3 and flac it writes SERIES and SERIES-PART. Adds unit tests verifying tag output for .m4b, .mp3, and .flac and that tags are omitted when fields are absent.
This commit is contained in:
@@ -118,6 +118,14 @@ export async function tagAudioFileMetadata(
|
||||
args.push('-metadata', `ASIN="${escapeMetadata(metadata.asin)}"`);
|
||||
}
|
||||
|
||||
if (metadata.series) {
|
||||
args.push('-metadata', `SERIES="${escapeMetadata(metadata.series)}"`);
|
||||
}
|
||||
|
||||
if (metadata.seriesPart) {
|
||||
args.push('-metadata', `SERIES-PART="${escapeMetadata(metadata.seriesPart)}"`);
|
||||
}
|
||||
|
||||
// Explicitly specify output format
|
||||
args.push('-f', 'flac');
|
||||
}
|
||||
|
||||
@@ -114,6 +114,72 @@ describe('metadata tagger', () => {
|
||||
await expect(checkFfmpegAvailable()).resolves.toBe(false);
|
||||
});
|
||||
|
||||
describe('series metadata', () => {
|
||||
it('writes show/episode_id for m4b when series/seriesPart provided', async () => {
|
||||
fsMock.access.mockResolvedValue(undefined);
|
||||
mockExecSuccess('done');
|
||||
|
||||
await tagAudioFileMetadata('/tmp/book.m4b', {
|
||||
title: 'Book',
|
||||
author: 'Author',
|
||||
series: 'The Mistborn Saga',
|
||||
seriesPart: '1',
|
||||
});
|
||||
|
||||
const command = execMock.mock.calls[0][0] as string;
|
||||
expect(command).toContain('-metadata show="The Mistborn Saga"');
|
||||
expect(command).toContain('-metadata episode_id="1"');
|
||||
});
|
||||
|
||||
it('writes SERIES/SERIES-PART for mp3 when series/seriesPart provided', async () => {
|
||||
fsMock.access.mockResolvedValue(undefined);
|
||||
mockExecSuccess('done');
|
||||
|
||||
await tagAudioFileMetadata('/tmp/book.mp3', {
|
||||
title: 'Book',
|
||||
author: 'Author',
|
||||
series: 'The Mistborn Saga',
|
||||
seriesPart: '1.5',
|
||||
});
|
||||
|
||||
const command = execMock.mock.calls[0][0] as string;
|
||||
expect(command).toContain('-metadata SERIES="The Mistborn Saga"');
|
||||
expect(command).toContain('-metadata SERIES-PART="1.5"');
|
||||
});
|
||||
|
||||
it('writes SERIES/SERIES-PART for flac when series/seriesPart provided', async () => {
|
||||
fsMock.access.mockResolvedValue(undefined);
|
||||
mockExecSuccess('done');
|
||||
|
||||
await tagAudioFileMetadata('/tmp/book.flac', {
|
||||
title: 'Book',
|
||||
author: 'Author',
|
||||
series: 'The Mistborn Saga',
|
||||
seriesPart: '2',
|
||||
});
|
||||
|
||||
const command = execMock.mock.calls[0][0] as string;
|
||||
expect(command).toContain('-metadata SERIES="The Mistborn Saga"');
|
||||
expect(command).toContain('-metadata SERIES-PART="2"');
|
||||
});
|
||||
|
||||
it('omits series tags when fields are absent', async () => {
|
||||
fsMock.access.mockResolvedValue(undefined);
|
||||
mockExecSuccess('done');
|
||||
|
||||
await tagAudioFileMetadata('/tmp/book.m4b', {
|
||||
title: 'Book',
|
||||
author: 'Author',
|
||||
});
|
||||
|
||||
const command = execMock.mock.calls[0][0] as string;
|
||||
expect(command).not.toContain('show=');
|
||||
expect(command).not.toContain('episode_id=');
|
||||
expect(command).not.toContain('SERIES=');
|
||||
expect(command).not.toContain('SERIES-PART=');
|
||||
});
|
||||
});
|
||||
|
||||
describe('metadata escaping', () => {
|
||||
it('does NOT escape single quotes (they are literal in double-quoted shell strings)', async () => {
|
||||
fsMock.access.mockResolvedValue(undefined);
|
||||
|
||||
Reference in New Issue
Block a user