feat: Support full Rekordbox master collection format - Include all track properties (Composer, Album, Genre, Size, TotalTime, etc.) - Add PRODUCT section with rekordbox version info - Include TEMPO entries for beat grid information - Use proper indentation and formatting like master collection - Support KeyType and Entries attributes for playlists - Handle large libraries (7000+ songs) with streaming export - Full compatibility with Rekordbox master collection format
This commit is contained in:
parent
b6467253a3
commit
1450eaa29b
@ -32,15 +32,18 @@ const buildXmlNode = (node: any): any => {
|
||||
export const streamToXml = async (res: any) => {
|
||||
console.log('Starting streamToXml function...');
|
||||
|
||||
// Write XML header (compact like Rekordbox)
|
||||
res.write('<?xml version="1.0"?>');
|
||||
res.write('<DJ_PLAYLISTS>');
|
||||
// Write XML header with encoding (like master collection)
|
||||
res.write('<?xml version="1.0" encoding="UTF-8"?>');
|
||||
res.write('\n\n<DJ_PLAYLISTS Version="1.0.0">');
|
||||
|
||||
// Add PRODUCT section
|
||||
res.write('\n <PRODUCT Name="rekordbox" Version="7.1.3" Company="AlphaTheta"/>');
|
||||
|
||||
// Start COLLECTION section
|
||||
console.log('Counting songs in database...');
|
||||
const songCount = await Song.countDocuments();
|
||||
console.log(`Found ${songCount} songs in database`);
|
||||
res.write('<COLLECTION>');
|
||||
res.write(`\n <COLLECTION Entries="${songCount}">`);
|
||||
|
||||
// Stream songs in batches to avoid memory issues
|
||||
const batchSize = 100;
|
||||
@ -53,18 +56,25 @@ export const streamToXml = async (res: any) => {
|
||||
.lean();
|
||||
|
||||
for (const song of songs) {
|
||||
// Only include TrackID, Name, and Artist like Rekordbox
|
||||
res.write(`<TRACK TrackID="${song.id}" Name="${escapeXml(song.title || '')}" Artist="${escapeXml(song.artist || '')}"/>`);
|
||||
// Include ALL track attributes like master collection
|
||||
res.write(`\n <TRACK TrackID="${song.id}" Name="${escapeXml(song.title || '')}" Artist="${escapeXml(song.artist || '')}" Composer="${escapeXml(song.composer || '')}" Album="${escapeXml(song.album || '')}" Grouping="${escapeXml(song.grouping || '')}" Genre="${escapeXml(song.genre || '')}" Kind="${escapeXml(song.kind || '')}" Size="${song.size || ''}" TotalTime="${song.totalTime || ''}" DiscNumber="${song.discNumber || ''}" TrackNumber="${song.trackNumber || ''}" Year="${song.year || ''}" AverageBpm="${song.averageBpm || ''}" DateAdded="${song.dateAdded || ''}" BitRate="${song.bitRate || ''}" SampleRate="${song.sampleRate || ''}" Comments="${escapeXml(song.comments || '')}" PlayCount="${song.playCount || ''}" Rating="${song.rating || ''}" Location="${escapeXml(song.location || '')}" Remixer="${escapeXml(song.remixer || '')}" Tonality="${escapeXml(song.tonality || '')}" Label="${escapeXml(song.label || '')}" Mix="${escapeXml(song.mix || '')}">`);
|
||||
|
||||
// Add TEMPO entries if they exist
|
||||
if (song.tempo) {
|
||||
res.write(`\n <TEMPO Inizio="${song.tempo.inizio}" Bpm="${song.tempo.bpm}" Metro="${song.tempo.metro}" Battito="${song.tempo.battito}"/>`);
|
||||
}
|
||||
|
||||
res.write('\n </TRACK>');
|
||||
}
|
||||
|
||||
processedSongs += songs.length;
|
||||
console.log(`Streamed ${processedSongs}/${songCount} songs...`);
|
||||
}
|
||||
|
||||
res.write('</COLLECTION>');
|
||||
res.write('\n </COLLECTION>');
|
||||
|
||||
// Start PLAYLISTS section
|
||||
res.write('<PLAYLISTS>');
|
||||
res.write('\n <PLAYLISTS>');
|
||||
|
||||
// Stream playlists
|
||||
console.log('Fetching playlists from database...');
|
||||
@ -72,19 +82,47 @@ export const streamToXml = async (res: any) => {
|
||||
console.log(`Found ${playlists.length} playlists in database`);
|
||||
|
||||
// Write ROOT node with correct Count
|
||||
res.write(`<NODE Name="ROOT" Type="0" Count="${playlists.length}">`);
|
||||
res.write(`\n <NODE Type="0" Name="ROOT" Count="${playlists.length}">`);
|
||||
|
||||
for (const playlist of playlists) {
|
||||
await streamPlaylistNodeCompact(res, playlist);
|
||||
await streamPlaylistNodeFull(res, playlist);
|
||||
}
|
||||
|
||||
res.write('</NODE>');
|
||||
res.write('</PLAYLISTS>');
|
||||
res.write('</DJ_PLAYLISTS>');
|
||||
res.write('\n </NODE>');
|
||||
res.write('\n </PLAYLISTS>');
|
||||
res.write('\n</DJ_PLAYLISTS>');
|
||||
|
||||
res.end();
|
||||
};
|
||||
|
||||
const streamPlaylistNodeFull = async (res: any, node: any) => {
|
||||
const nodeType = node.type === 'folder' ? '0' : '1';
|
||||
|
||||
if (node.type === 'folder') {
|
||||
const childCount = node.children ? node.children.length : 0;
|
||||
res.write(`\n <NODE Name="${escapeXml(node.name)}" Type="${nodeType}" Count="${childCount}">`);
|
||||
|
||||
if (node.children && node.children.length > 0) {
|
||||
for (const child of node.children) {
|
||||
await streamPlaylistNodeFull(res, child);
|
||||
}
|
||||
}
|
||||
|
||||
res.write('\n </NODE>');
|
||||
} else {
|
||||
const trackCount = node.tracks ? node.tracks.length : 0;
|
||||
res.write(`\n <NODE Name="${escapeXml(node.name)}" Type="${nodeType}" KeyType="0" Entries="${trackCount}">`);
|
||||
|
||||
if (node.tracks && node.tracks.length > 0) {
|
||||
for (const trackId of node.tracks) {
|
||||
res.write(`\n <TRACK Key="${trackId}"/>`);
|
||||
}
|
||||
}
|
||||
|
||||
res.write('\n </NODE>');
|
||||
}
|
||||
};
|
||||
|
||||
const streamPlaylistNodeCompact = async (res: any, node: any) => {
|
||||
const nodeType = node.type === 'folder' ? '0' : '1';
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user