mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 04:40:09 +00:00
Add ASIN support to file organization and metadata
This update enhances audiobook file organization by including the ASIN in folder names and embedding it as a custom metadata tag in audio files (M4B/M4A/MP3). Documentation is updated to reflect the new folder naming convention and metadata tagging. Additionally, local login and registration can now be disabled via an environment variable, and the interactive torrent search modal allows custom search titles for all modes.
This commit is contained in:
@@ -216,7 +216,7 @@ export function useInteractiveSearch() {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const searchTorrents = async (requestId: string) => {
|
||||
const searchTorrents = async (requestId: string, customTitle?: string) => {
|
||||
if (!accessToken) {
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
@@ -227,6 +227,10 @@ export function useInteractiveSearch() {
|
||||
try {
|
||||
const response = await fetchWithAuth(`/api/requests/${requestId}/interactive-search`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: customTitle ? JSON.stringify({ customTitle }) : undefined,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
@@ -54,6 +54,7 @@ export async function processOrganizeFiles(payload: OrganizeFilesPayload): Promi
|
||||
author: audiobook.author,
|
||||
narrator: audiobook.narrator || undefined,
|
||||
coverArtUrl: audiobook.coverArtUrl || undefined,
|
||||
asin: audiobook.audibleAsin || undefined,
|
||||
},
|
||||
jobId ? { jobId, context: 'FileOrganizer' } : undefined
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface AudiobookMetadata {
|
||||
narrator?: string;
|
||||
year?: number;
|
||||
coverArtUrl?: string;
|
||||
asin?: string;
|
||||
}
|
||||
|
||||
export interface OrganizationResult {
|
||||
@@ -110,6 +111,7 @@ export class FileOrganizer {
|
||||
author: audiobook.author,
|
||||
narrator: audiobook.narrator,
|
||||
year: audiobook.year,
|
||||
asin: audiobook.asin,
|
||||
});
|
||||
|
||||
const successCount = taggingResults.filter((r) => r.success).length;
|
||||
@@ -153,7 +155,8 @@ export class FileOrganizer {
|
||||
this.mediaDir,
|
||||
audiobook.author,
|
||||
audiobook.title,
|
||||
audiobook.year
|
||||
audiobook.year,
|
||||
audiobook.asin
|
||||
);
|
||||
|
||||
await logger?.info(`Target path: ${targetPath}`);
|
||||
@@ -359,16 +362,28 @@ export class FileOrganizer {
|
||||
|
||||
/**
|
||||
* Build target path with sanitized names
|
||||
* Format: Author/Title (Year) ASIN or Author/Title ASIN or Author/Title (Year)
|
||||
*/
|
||||
private buildTargetPath(
|
||||
baseDir: string,
|
||||
author: string,
|
||||
title: string,
|
||||
year?: number
|
||||
year?: number,
|
||||
asin?: string
|
||||
): string {
|
||||
const authorClean = this.sanitizePath(author);
|
||||
const titleClean = this.sanitizePath(title);
|
||||
const folderName = year ? `${titleClean} (${year})` : titleClean;
|
||||
|
||||
// Build folder name with optional year and ASIN
|
||||
let folderName = titleClean;
|
||||
|
||||
if (year) {
|
||||
folderName = `${folderName} (${year})`;
|
||||
}
|
||||
|
||||
if (asin) {
|
||||
folderName = `${folderName} ${asin}`;
|
||||
}
|
||||
|
||||
return path.join(baseDir, authorClean, folderName);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface MetadataTaggingOptions {
|
||||
author: string;
|
||||
narrator?: string;
|
||||
year?: number;
|
||||
asin?: string;
|
||||
}
|
||||
|
||||
export interface TaggingResult {
|
||||
@@ -76,6 +77,11 @@ export async function tagAudioFileMetadata(
|
||||
args.push('-metadata', `date="${metadata.year}"`);
|
||||
}
|
||||
|
||||
if (metadata.asin) {
|
||||
// Use custom iTunes tag format for M4B/M4A/MP4 files
|
||||
args.push('-metadata', `----:com.apple.iTunes:ASIN="${escapeMetadata(metadata.asin)}"`);
|
||||
}
|
||||
|
||||
// Explicitly specify output format (fixes .tmp extension issue)
|
||||
args.push('-f', 'mp4');
|
||||
}
|
||||
@@ -97,6 +103,11 @@ export async function tagAudioFileMetadata(
|
||||
args.push('-metadata', `date="${metadata.year}"`);
|
||||
}
|
||||
|
||||
if (metadata.asin) {
|
||||
// Use TXXX frame for custom ID3v2 tags in MP3 files
|
||||
args.push('-metadata', `ASIN="${escapeMetadata(metadata.asin)}"`);
|
||||
}
|
||||
|
||||
// Explicitly specify output format (fixes .tmp extension issue)
|
||||
args.push('-f', 'mp3');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user