revert: restore original yt-dlp search — remove broken ytmsearch/ytsearch5 changes
Revertscc7fc80,59562fe,595fb2b,626a755. The new search logic caused wrong songs, [Unknown Album] folders and duplicate downloads. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
626a755688
commit
8e9521f889
3 files changed
+12
-47
No files matched your search
+11
-44
@@ -161,10 +161,9 @@ async function lookupItunesTrackMeta(artist, title) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Download a single track: yt-dlp audio + ffmpeg clean metadata + iTunes cover embedded
|
// Download a single track: yt-dlp audio + ffmpeg clean metadata + iTunes cover embedded
|
||||||
async function downloadTrack({ jobId, artist, albumArtist, title, album, year, trackNum, totalTracks, discNum, discTotal, durationMs, compilation, genre, artworkUrl, explicit, outputDir }) {
|
async function downloadTrack({ jobId, artist, albumArtist, title, album, year, trackNum, totalTracks, discNum, discTotal, compilation, genre, artworkUrl, explicit, outputDir }) {
|
||||||
const mainArtist = albumArtist || artist;
|
const mainArtist = albumArtist || artist;
|
||||||
// use only the first artist for the folder path (e.g. "Artist1, Artist2" → "Artist1")
|
const safeArtist = sanitize(mainArtist);
|
||||||
const safeArtist = sanitize(mainArtist.split(',')[0].trim());
|
|
||||||
const safeAlbum = sanitize(album || 'Singles');
|
const safeAlbum = sanitize(album || 'Singles');
|
||||||
const dir = path.join(outputDir || CONFIG.musicPath, safeArtist, safeAlbum);
|
const dir = path.join(outputDir || CONFIG.musicPath, safeArtist, safeAlbum);
|
||||||
fs.mkdirSync(dir, { recursive: true });
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
@@ -179,26 +178,19 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t
|
|||||||
}
|
}
|
||||||
|
|
||||||
const outputTemplate = path.join(dir, `${trackPrefix}${safeTitle}.%(ext)s`);
|
const outputTemplate = path.join(dir, `${trackPrefix}${safeTitle}.%(ext)s`);
|
||||||
const query = `${mainArtist} - ${title}`;
|
const query = `${mainArtist} - ${title} official audio`;
|
||||||
|
log(jobId, `🔍 Suche: "${query}"`);
|
||||||
|
|
||||||
// Build match-filter: always exclude live streams; also constrain duration when known (±30s low, +45s high)
|
// Step 1: yt-dlp — audio only, no thumbnail (we embed iTunes artwork ourselves)
|
||||||
const durationSec = durationMs ? Math.round(durationMs / 1000) : null;
|
await new Promise((resolve, reject) => {
|
||||||
const matchFilter = [
|
|
||||||
'!is_live',
|
|
||||||
durationSec && `duration > ${Math.max(0, durationSec - 30)}`,
|
|
||||||
durationSec && `duration < ${durationSec + 45}`,
|
|
||||||
].filter(Boolean).join(' & ');
|
|
||||||
|
|
||||||
const runYtdlp = (searchExpr) => new Promise((resolve, reject) => {
|
|
||||||
const args = [
|
const args = [
|
||||||
searchExpr,
|
`ytsearch1:${query}`,
|
||||||
'--extract-audio',
|
'--extract-audio',
|
||||||
'--audio-format', CONFIG.audioFmt,
|
'--audio-format', CONFIG.audioFmt,
|
||||||
'--audio-quality', CONFIG.audioQuality,
|
'--audio-quality', CONFIG.audioQuality,
|
||||||
'-o', outputTemplate,
|
'-o', outputTemplate,
|
||||||
'--no-playlist',
|
'--no-playlist',
|
||||||
'--match-filter', matchFilter,
|
'--playlist-items', '1',
|
||||||
'--max-downloads', '1',
|
|
||||||
'--no-warnings',
|
'--no-warnings',
|
||||||
'--newline',
|
'--newline',
|
||||||
];
|
];
|
||||||
@@ -218,33 +210,11 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t
|
|||||||
.forEach(l => log(jobId, l.trim()));
|
.forEach(l => log(jobId, l.trim()));
|
||||||
});
|
});
|
||||||
proc.on('error', reject);
|
proc.on('error', reject);
|
||||||
// code 101 = --max-downloads limit reached (= success); 0 = completed normally
|
|
||||||
proc.on('close', code =>
|
proc.on('close', code =>
|
||||||
(code === 0 || code === 101) ? resolve() : reject(new Error(`yt-dlp code ${code}: ${stderr.slice(-300)}`))
|
code === 0 ? resolve() : reject(new Error(`yt-dlp code ${code}: ${stderr.slice(-300)}`))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const audioFileCount = () =>
|
|
||||||
fs.readdirSync(dir).filter(f => AUDIO_EXTS.some(e => f.endsWith(`.${e}`))).length;
|
|
||||||
|
|
||||||
// Step 1: YouTube Music — music-specific index, far fewer false positives
|
|
||||||
// Falls silently if this yt-dlp version doesn't support ytmsearch (older Alpine package)
|
|
||||||
const countBefore = audioFileCount();
|
|
||||||
try {
|
|
||||||
log(jobId, `🎵 YouTube Music: "${query}"`);
|
|
||||||
await runYtdlp(`ytmsearch:${query}`);
|
|
||||||
} catch (e) {
|
|
||||||
if (!e.message.includes('Unsupported url scheme')) {
|
|
||||||
log(jobId, `⚠️ YouTube Music Fehler: ${e.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Fallback to regular YouTube if no file was produced
|
|
||||||
if (audioFileCount() === countBefore) {
|
|
||||||
log(jobId, `🔍 YouTube: "${query}"`);
|
|
||||||
await runYtdlp(`ytsearch5:${query}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: find the audio file yt-dlp wrote (newest audio file in dir)
|
// Step 2: find the audio file yt-dlp wrote (newest audio file in dir)
|
||||||
const audioFile = fs.readdirSync(dir)
|
const audioFile = fs.readdirSync(dir)
|
||||||
.filter(f => AUDIO_EXTS.some(e => f.endsWith(`.${e}`)))
|
.filter(f => AUDIO_EXTS.some(e => f.endsWith(`.${e}`)))
|
||||||
@@ -348,7 +318,6 @@ async function downloadAlbumTracks({ jobId, artist, albumTitle, tracks, artworkU
|
|||||||
totalTracks: total,
|
totalTracks: total,
|
||||||
discNum: track.discNumber || undefined,
|
discNum: track.discNumber || undefined,
|
||||||
discTotal: track.discCount || undefined,
|
discTotal: track.discCount || undefined,
|
||||||
durationMs: track.trackTimeMillis || undefined,
|
|
||||||
compilation: track.collectionType === 'Compilation' || undefined,
|
compilation: track.collectionType === 'Compilation' || undefined,
|
||||||
genre: track.primaryGenreName || undefined,
|
genre: track.primaryGenreName || undefined,
|
||||||
artworkUrl: artworkUrl || track.artworkUrl100,
|
artworkUrl: artworkUrl || track.artworkUrl100,
|
||||||
@@ -596,8 +565,7 @@ app.post('/api/spotify/resolve', async (req, res) => {
|
|||||||
|
|
||||||
app.post('/api/download', async (req, res) => {
|
app.post('/api/download', async (req, res) => {
|
||||||
const { type, artist, title, album, artworkUrl, collectionId, jobId: clientJobId, explicit,
|
const { type, artist, title, album, artworkUrl, collectionId, jobId: clientJobId, explicit,
|
||||||
trackNum: bodyTrackNum, totalTracks: bodyTotalTracks, discNum: bodyDiscNum, discTotal: bodyDiscTotal,
|
trackNum: bodyTrackNum, totalTracks: bodyTotalTracks, discNum: bodyDiscNum, discTotal: bodyDiscTotal } = req.body;
|
||||||
durationMs: bodyDurationMs } = req.body;
|
|
||||||
if (!artist || !title) return res.status(400).json({ error: 'artist and title required' });
|
if (!artist || !title) return res.status(400).json({ error: 'artist and title required' });
|
||||||
|
|
||||||
const jobId = clientJobId || ('job_' + Date.now());
|
const jobId = clientJobId || ('job_' + Date.now());
|
||||||
@@ -622,7 +590,6 @@ app.post('/api/download', async (req, res) => {
|
|||||||
totalTracks: sp.trackCount || bodyTotalTracks || undefined,
|
totalTracks: sp.trackCount || bodyTotalTracks || undefined,
|
||||||
discNum: sp.discNumber || bodyDiscNum || undefined,
|
discNum: sp.discNumber || bodyDiscNum || undefined,
|
||||||
discTotal: sp.discCount || bodyDiscTotal || undefined,
|
discTotal: sp.discCount || bodyDiscTotal || undefined,
|
||||||
durationMs: sp.trackTimeMillis || bodyDurationMs || undefined,
|
|
||||||
artworkUrl: spArt, explicit: sp.trackExplicitness === 'explicit' || !!explicit };
|
artworkUrl: spArt, explicit: sp.trackExplicitness === 'explicit' || !!explicit };
|
||||||
if (!meta.album) {
|
if (!meta.album) {
|
||||||
// Use first artist only for iTunes search (multi-artist strings like "Kløn, Renzyx" confuse iTunes)
|
// Use first artist only for iTunes search (multi-artist strings like "Kløn, Renzyx" confuse iTunes)
|
||||||
@@ -634,7 +601,7 @@ app.post('/api/download', async (req, res) => {
|
|||||||
} else {
|
} else {
|
||||||
log(jobId, `📋 Album aus Spotify: "${meta.album}"`);
|
log(jobId, `📋 Album aus Spotify: "${meta.album}"`);
|
||||||
}
|
}
|
||||||
const result = await downloadTrack({ jobId, artist, albumArtist: artist.split(',')[0].trim(), title, ...meta });
|
const result = await downloadTrack({ jobId, artist, title, ...meta });
|
||||||
if (result?.skipped) {
|
if (result?.skipped) {
|
||||||
updateJob(jobId, { status: 'done', progress: 100, message: 'Bereits vorhanden – übersprungen', skipped: true });
|
updateJob(jobId, { status: 'done', progress: 100, message: 'Bereits vorhanden – übersprungen', skipped: true });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+1
-2
@@ -30,8 +30,7 @@ services:
|
|||||||
- MAX_PARALLEL=${MAX_PARALLEL:-2}
|
- MAX_PARALLEL=${MAX_PARALLEL:-2}
|
||||||
command: >
|
command: >
|
||||||
sh -c "
|
sh -c "
|
||||||
apk add --no-cache ffmpeg python3 py3-pip &&
|
apk add --no-cache yt-dlp ffmpeg python3 &&
|
||||||
pip3 install -U yt-dlp --break-system-packages &&
|
|
||||||
npm install express cors &&
|
npm install express cors &&
|
||||||
node novasonic-server.js
|
node novasonic-server.js
|
||||||
"
|
"
|
||||||
@@ -298,7 +298,6 @@ export async function executeDownload() {
|
|||||||
totalTracks: S.downloadTarget.trackCount || undefined,
|
totalTracks: S.downloadTarget.trackCount || undefined,
|
||||||
discNum: S.downloadTarget.discNumber || undefined,
|
discNum: S.downloadTarget.discNumber || undefined,
|
||||||
discTotal: S.downloadTarget.discCount || undefined,
|
discTotal: S.downloadTarget.discCount || undefined,
|
||||||
durationMs: S.downloadTarget.trackTimeMillis || undefined,
|
|
||||||
};
|
};
|
||||||
addJob(jobId, payload.title, payload.artist, payload.type);
|
addJob(jobId, payload.title, payload.artist, payload.type);
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in new issue
Block a user