Restart repository history from target event
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { getFlacMetadata, parseLabel } from '@/lib/utils/audio';
|
||||
import { AnalysisTrack, ApiResponse, AlbumResponse } from '@/types';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { albumPath, releaseType } = await request.json();
|
||||
|
||||
if (!albumPath || !(await fs.pathExists(albumPath))) {
|
||||
return NextResponse.json({ error: 'Invalid album path' }, { status: 400 });
|
||||
}
|
||||
|
||||
const files = await fs.readdir(albumPath);
|
||||
const flacFiles = files.filter((file) => file.toLowerCase().endsWith('.flac'));
|
||||
|
||||
if (flacFiles.length === 0) {
|
||||
return NextResponse.json({ error: 'No FLAC files found' }, { status: 400 });
|
||||
}
|
||||
|
||||
const tracks: AnalysisTrack[] = await Promise.all(
|
||||
flacFiles.map(async (file) => {
|
||||
const filePath = path.join(albumPath, file);
|
||||
const metadata = await getFlacMetadata(filePath);
|
||||
return {
|
||||
filename: file,
|
||||
path: filePath,
|
||||
...metadata,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Sort tracks
|
||||
tracks.sort((a, b) => {
|
||||
if ('error' in a || 'error' in b) return 0;
|
||||
const trackA = a.common?.track?.no ?? 0;
|
||||
const trackB = b.common?.track?.no ?? 0;
|
||||
if (trackA && trackB) return trackA - trackB;
|
||||
return a.filename.localeCompare(b.filename);
|
||||
});
|
||||
|
||||
// Analyze quality for description
|
||||
let isLossless = true;
|
||||
let isHiRes = true;
|
||||
let mixed = false;
|
||||
let qualityDetails = '';
|
||||
const sampleRates = new Set();
|
||||
const bitDepths = new Set();
|
||||
|
||||
tracks.forEach((track) => {
|
||||
if ('error' in track) return;
|
||||
const sr = track.format?.sampleRate;
|
||||
const bd = track.format?.bitsPerSample;
|
||||
if (sr) sampleRates.add(sr);
|
||||
if (bd) bitDepths.add(bd);
|
||||
if (sr !== 44100 || bd !== 16) isLossless = false;
|
||||
if (!sr || sr < 48000 || !bd || bd < 24) isHiRes = false;
|
||||
});
|
||||
|
||||
if (sampleRates.size > 1 || bitDepths.size > 1) {
|
||||
mixed = true;
|
||||
qualityDetails = `\n\nNOTE: Mixed formats. Sample Rates: ${Array.from(sampleRates).join(', ')} Hz. Bit Depths: ${Array.from(bitDepths).join(', ')} bit.`;
|
||||
|
||||
const mixedDetails = tracks.map(t => {
|
||||
if ('error' in t) return `${t.filename}: Error`;
|
||||
return `Track ${t.common?.track?.no || '?'}: ${t.format?.bitsPerSample}bit / ${t.format?.sampleRate}Hz`;
|
||||
}).join('\n');
|
||||
|
||||
qualityDetails += '\n' + mixedDetails;
|
||||
}
|
||||
|
||||
const firstTrack = tracks.find((t): t is Extract<AnalysisTrack, { error?: never }> => !('error' in t));
|
||||
if (!firstTrack) {
|
||||
return NextResponse.json({ error: 'No valid tracks found' }, { status: 400 });
|
||||
}
|
||||
|
||||
const albumName = firstTrack.common?.album || 'Unknown Album';
|
||||
const year = firstTrack.common?.year || (firstTrack.common?.date ? parseInt(firstTrack.common.date.split('-')[0]) : 2025);
|
||||
|
||||
let label = parseLabel(firstTrack.common?.copyright || '');
|
||||
|
||||
const upc = firstTrack.native?.vorbis?.find(tag => tag.id === 'UPC')?.value || firstTrack.common?.barcode || '';
|
||||
|
||||
// Artists
|
||||
// Artists Logic
|
||||
|
||||
// Helper to split artists
|
||||
const splitArtists = (artistString: string) => {
|
||||
return artistString.split(/,\s+|\s+\/\s+|;\s+/).map((a: string) => a.trim()).filter((a: string) => a.length > 0);
|
||||
};
|
||||
|
||||
// Collect all artists and their frequencies
|
||||
const artistCounts = new Map<string, number>();
|
||||
const allUniqueArtists = new Set<string>();
|
||||
|
||||
tracks.forEach(t => {
|
||||
if ('error' in t) return;
|
||||
let trackArtists: string[] = [];
|
||||
// music-metadata might return artists as an array, but sometimes elements are comma-separated strings
|
||||
if (t.common?.artists) {
|
||||
t.common.artists.forEach((a: string) => trackArtists.push(...splitArtists(a)));
|
||||
} else if (t.common?.artist) {
|
||||
trackArtists = splitArtists(t.common.artist);
|
||||
}
|
||||
|
||||
trackArtists.forEach(a => {
|
||||
allUniqueArtists.add(a);
|
||||
artistCounts.set(a, (artistCounts.get(a) || 0) + 1);
|
||||
});
|
||||
});
|
||||
|
||||
let mainArtists: { name: string }[] = [];
|
||||
const withArtists: { name: string }[] = [];
|
||||
|
||||
if (releaseType === 7) {
|
||||
// Compilation: All artists in mainArtists, no withArtists
|
||||
allUniqueArtists.forEach(a => {
|
||||
if (a !== 'Various Artists') {
|
||||
mainArtists.push({ name: a });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Standard Logic
|
||||
// Determine Main Artist String
|
||||
let mainArtistName = firstTrack.common?.albumartist || 'Various Artists';
|
||||
|
||||
// If album artist is Various Artists (or missing), find the most common artist
|
||||
if (mainArtistName === 'Various Artists' || !mainArtistName) {
|
||||
let maxCount = 0;
|
||||
let mostFrequent = '';
|
||||
artistCounts.forEach((count, artist) => {
|
||||
if (count > maxCount) {
|
||||
maxCount = count;
|
||||
mostFrequent = artist;
|
||||
}
|
||||
});
|
||||
if (mostFrequent) {
|
||||
mainArtistName = mostFrequent;
|
||||
}
|
||||
}
|
||||
|
||||
// Split main artist into components for the array
|
||||
// Use the same split logic as above
|
||||
const mainArtistComponents = splitArtists(mainArtistName);
|
||||
mainArtists = mainArtistComponents.map(name => ({ name }));
|
||||
|
||||
// Determine With Artists
|
||||
allUniqueArtists.forEach(a => {
|
||||
// Exclude all main artist components and "Various Artists" from "with" list
|
||||
if (!mainArtistComponents.includes(a) && a !== 'Various Artists') {
|
||||
withArtists.push({ name: a });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Tracklist
|
||||
const tracklist = tracks.map(t => {
|
||||
if ('error' in t) return `${t.filename} (Error)`;
|
||||
return `${t.common?.track?.no || '00'}. ${t.common?.title || t.filename}`;
|
||||
}).join('\n');
|
||||
|
||||
// Description
|
||||
let description = "Sourced from TIDAL";
|
||||
if (mixed) {
|
||||
description += qualityDetails;
|
||||
}
|
||||
|
||||
// Encoding
|
||||
let encoding = "Lossless";
|
||||
const maxBitDepth = Math.max(...Array.from(bitDepths).map(Number));
|
||||
if (maxBitDepth >= 24) {
|
||||
encoding = "24bit Lossless";
|
||||
}
|
||||
|
||||
const response = {
|
||||
status: "success",
|
||||
response: {
|
||||
source: "Crossfade",
|
||||
group: {
|
||||
wikiBody: tracklist,
|
||||
bbBody: tracklist,
|
||||
name: albumName,
|
||||
year: year,
|
||||
categoryId: 1,
|
||||
releaseType: releaseType || 1, // Default to Album (1)
|
||||
categoryName: "Music",
|
||||
musicInfo: {
|
||||
composers: [],
|
||||
dj: [],
|
||||
artists: mainArtists,
|
||||
with: withArtists,
|
||||
conductor: [],
|
||||
remixedBy: [],
|
||||
producer: []
|
||||
},
|
||||
recordLabel: label,
|
||||
catalogueNumber: upc,
|
||||
tags: [] // Empty for now, hard to guess
|
||||
},
|
||||
torrent: {
|
||||
media: "WEB",
|
||||
format: "FLAC",
|
||||
encoding: encoding,
|
||||
//remastered: true,
|
||||
remasterYear: year,
|
||||
remasterRecordLabel: label,
|
||||
remasterCatalogueNumber: upc,
|
||||
description: description,
|
||||
//fileCount: tracks.length,
|
||||
//filePath: `${mainArtistName} - ${albumName} (${year}) [FLAC]`
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return NextResponse.json(response);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error generating RED template:', error);
|
||||
return NextResponse.json({ error: 'Failed to generate template' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user