feat(export): honor custom playlist order overlay when exporting Rekordbox XML (Entries + TRACK sequence)
This commit is contained in:
parent
9249a5a4a7
commit
5659dde540
@ -2,6 +2,17 @@ import { create } from "xmlbuilder";
|
|||||||
import { Song } from "../models/Song.js";
|
import { Song } from "../models/Song.js";
|
||||||
import { Playlist } from "../models/Playlist.js";
|
import { Playlist } from "../models/Playlist.js";
|
||||||
|
|
||||||
|
// Compute effective playlist order: custom overlay (order) + remaining base tracks
|
||||||
|
const getEffectiveTrackIds = (node: any): string[] => {
|
||||||
|
const base: string[] = Array.isArray(node?.tracks) ? node.tracks : [];
|
||||||
|
const overlay: string[] = Array.isArray(node?.order) ? node.order : [];
|
||||||
|
if (overlay.length === 0) return base;
|
||||||
|
const setBase = new Set(base);
|
||||||
|
const orderedKnown = overlay.filter((id: string) => setBase.has(id));
|
||||||
|
const missing = base.filter((id: string) => !overlay.includes(id));
|
||||||
|
return [...orderedKnown, ...missing];
|
||||||
|
};
|
||||||
|
|
||||||
const buildXmlNode = (node: any): any => {
|
const buildXmlNode = (node: any): any => {
|
||||||
const xmlNode: any = {
|
const xmlNode: any = {
|
||||||
'@Type': node.type === 'folder' ? '0' : '1',
|
'@Type': node.type === 'folder' ? '0' : '1',
|
||||||
@ -14,13 +25,12 @@ const buildXmlNode = (node: any): any => {
|
|||||||
xmlNode.NODE = node.children.map((child: any) => buildXmlNode(child));
|
xmlNode.NODE = node.children.map((child: any) => buildXmlNode(child));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For playlists, always include KeyType and Entries
|
// For playlists, include KeyType and Entries based on effective order
|
||||||
|
const effectiveIds = getEffectiveTrackIds(node);
|
||||||
xmlNode['@KeyType'] = '0';
|
xmlNode['@KeyType'] = '0';
|
||||||
// Set Entries to the actual number of tracks
|
xmlNode['@Entries'] = effectiveIds.length;
|
||||||
xmlNode['@Entries'] = (node.tracks || []).length;
|
if (effectiveIds.length > 0) {
|
||||||
// Include TRACK elements if there are tracks
|
xmlNode.TRACK = effectiveIds.map((trackId: string) => ({
|
||||||
if (node.tracks && node.tracks.length > 0) {
|
|
||||||
xmlNode.TRACK = node.tracks.map((trackId: string) => ({
|
|
||||||
'@Key': trackId
|
'@Key': trackId
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -110,15 +120,13 @@ const streamPlaylistNodeFull = async (res: any, node: any) => {
|
|||||||
|
|
||||||
res.write('\n </NODE>');
|
res.write('\n </NODE>');
|
||||||
} else {
|
} else {
|
||||||
const trackCount = node.tracks ? node.tracks.length : 0;
|
const effectiveIds = getEffectiveTrackIds(node);
|
||||||
res.write(`\n <NODE Name="${escapeXml(node.name)}" Type="${nodeType}" KeyType="0" Entries="${trackCount}">`);
|
res.write(`\n <NODE Name="${escapeXml(node.name)}" Type="${nodeType}" KeyType="0" Entries="${effectiveIds.length}">`);
|
||||||
|
if (effectiveIds.length > 0) {
|
||||||
if (node.tracks && node.tracks.length > 0) {
|
for (const trackId of effectiveIds) {
|
||||||
for (const trackId of node.tracks) {
|
|
||||||
res.write(`\n <TRACK Key="${trackId}"/>`);
|
res.write(`\n <TRACK Key="${trackId}"/>`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.write('\n </NODE>');
|
res.write('\n </NODE>');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -138,15 +146,13 @@ const streamPlaylistNodeCompact = async (res: any, node: any) => {
|
|||||||
|
|
||||||
res.write('</NODE>');
|
res.write('</NODE>');
|
||||||
} else {
|
} else {
|
||||||
const trackCount = node.tracks ? node.tracks.length : 0;
|
const effectiveIds = getEffectiveTrackIds(node);
|
||||||
res.write(`<NODE Name="${escapeXml(node.name)}" Type="${nodeType}" Count="${trackCount}">`);
|
res.write(`<NODE Name="${escapeXml(node.name)}" Type="${nodeType}" Count="${effectiveIds.length}">`);
|
||||||
|
if (effectiveIds.length > 0) {
|
||||||
if (node.tracks && node.tracks.length > 0) {
|
for (const trackId of effectiveIds) {
|
||||||
for (const trackId of node.tracks) {
|
|
||||||
res.write(`<TRACK Key="${trackId}"/>`);
|
res.write(`<TRACK Key="${trackId}"/>`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.write('</NODE>');
|
res.write('</NODE>');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -167,15 +173,13 @@ const streamPlaylistNode = async (res: any, node: any, indent: number) => {
|
|||||||
|
|
||||||
res.write(`${spaces}</NODE>\n`);
|
res.write(`${spaces}</NODE>\n`);
|
||||||
} else {
|
} else {
|
||||||
const trackCount = node.tracks ? node.tracks.length : 0;
|
const effectiveIds = getEffectiveTrackIds(node);
|
||||||
res.write(`${spaces}<NODE Type="${nodeType}" Name="${escapeXml(node.name)}" KeyType="0" Entries="${trackCount}">\n`);
|
res.write(`${spaces}<NODE Type="${nodeType}" Name="${escapeXml(node.name)}" KeyType="0" Entries="${effectiveIds.length}">\n`);
|
||||||
|
if (effectiveIds.length > 0) {
|
||||||
if (node.tracks && node.tracks.length > 0) {
|
for (const trackId of effectiveIds) {
|
||||||
for (const trackId of node.tracks) {
|
|
||||||
res.write(`${spaces} <TRACK Key="${trackId}"/>\n`);
|
res.write(`${spaces} <TRACK Key="${trackId}"/>\n`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.write(`${spaces}</NODE>\n`);
|
res.write(`${spaces}</NODE>\n`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user