chore: remove squid.wtf (Qobuz) backend — yt-dlp only
Deletes backend/lib/squid.js; strips all references from downloader.js, config.js, novasonic-server.js, docker-compose.yml, .env.example, README, and CHANGELOG. Default DOWNLOAD_BACKEND is now ytdlp. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
925c173c15
commit
f1bdd811ca
8 files changed
+15
-343
No files matched your search
+1
-11
@@ -11,17 +11,7 @@ AUDIO_FMT=mp3 # mp3 | flac | opus | best (used by yt-dlp)
|
||||
AUDIO_QUALITY=0 # 0 = best quality (used by yt-dlp)
|
||||
MAX_PARALLEL=2
|
||||
|
||||
# Download backend: ytdlp | squid | auto
|
||||
# ytdlp - always use yt-dlp
|
||||
# squid - always use squid.wtf (Qobuz)
|
||||
# auto - try squid.wtf first, fall back to yt-dlp if not found (default)
|
||||
DOWNLOAD_BACKEND=auto
|
||||
|
||||
# Quality for squid.wtf / Qobuz downloads
|
||||
# mp3 - MP3 320 kbps
|
||||
# flac - FLAC 16-bit / 44.1 kHz
|
||||
# hi-res - FLAC 24-bit / 96 kHz
|
||||
SQUID_QUALITY=mp3
|
||||
DOWNLOAD_BACKEND=ytdlp
|
||||
|
||||
# Ticketmaster Events (optional — free API key)
|
||||
# 1. Sign up at https://developer.ticketmaster.com/
|
||||
|
||||
@@ -62,6 +62,9 @@ Versioning: `MAJOR.MINOR.PATCH` — minor = new feature, patch = fix/improvement
|
||||
- **Android** — Artist fast-scroll index bar: draggable letter strip on the right side of the Artists list with letter bubble while dragging
|
||||
- **Web** — Playlist edit modal: "Cover entfernen" button removed
|
||||
|
||||
### Removed
|
||||
- **Backend** — squid.wtf (Qobuz) download backend removed; downloads now exclusively use yt-dlp. `DOWNLOAD_BACKEND` and `SQUID_QUALITY` env vars are no longer read.
|
||||
|
||||
### Fixed
|
||||
- **Android** — In-app notification bell: notifications were never delivered after navigating away from the Importer screen (poll ran in `viewModelScope`, cancelled on navigation); polling now uses `NotificationStore.startPoll()` with an app-lifetime scope
|
||||
- **Android** — Home hero card: pool now matches web — union of `frequent(5) ∪ recentlyPlayed(5)` deduped, random pick; label is "MEISTGESPIELT" or "ZULETZT GEHÖRT" (was hardcoded "AKTUELL")
|
||||
|
||||
@@ -37,8 +37,8 @@ Built with Vite + vanilla ES modules (web) and Jetpack Compose + Media3/ExoPlaye
|
||||
- iTunes / Last.fm — charts, global search, 30-second previews, similar artists
|
||||
- Song Importer — paste a Spotify, YouTube, YouTube Music, or SoundCloud link to download
|
||||
|
||||
### Downloads (yt-dlp backend)
|
||||
- Download tracks, albums, or entire artists from YouTube Music / SoundCloud / Spotify
|
||||
### Downloads
|
||||
- Download tracks, albums, or entire artists from YouTube Music / SoundCloud / Spotify via yt-dlp
|
||||
- iTunes metadata enrichment — album art, year, track number, genre
|
||||
- Duplicate detection, standard folder/filename structure
|
||||
|
||||
@@ -50,7 +50,7 @@ Built with Vite + vanilla ES modules (web) and Jetpack Compose + Media3/ExoPlaye
|
||||
- Create, rename, and delete playlists
|
||||
- Add tracks via "Add to Playlist" context menu
|
||||
- Remove tracks from a playlist (edit mode on Android; inline on Web)
|
||||
- Cover image upload (Web)
|
||||
- Cover image upload (Web + Android)
|
||||
|
||||
### Audiobookshelf Integration
|
||||
- Browse libraries and audiobooks on both Web and Android
|
||||
@@ -67,9 +67,10 @@ Built with Vite + vanilla ES modules (web) and Jetpack Compose + Media3/ExoPlaye
|
||||
|
||||
### App — Android (native Jetpack Compose)
|
||||
- Media3 / ExoPlayer backend
|
||||
- OS notification with playback controls (chapter name shown for audiobooks)
|
||||
- OS notification with playback controls
|
||||
- ABS audiobook notification: chapter name shown as title, book title as artist; extra buttons — Kapitel zurück | 30s zurück | Play/Pause | 30s vor | Kapitel vor (compact + expanded)
|
||||
- Last played song shown in mini player bar on restart; tap to resume
|
||||
- Full playlist management: create, rename, delete, remove tracks
|
||||
- Full playlist management: create, rename (with description), delete, remove tracks, cover upload
|
||||
|
||||
---
|
||||
|
||||
@@ -112,7 +113,6 @@ NovaSonic/
|
||||
│ ├── jobs.js # In-memory download job store
|
||||
│ ├── metadata.js # iTunes lookup, artwork, filename helpers
|
||||
│ ├── ytdlp.js # yt-dlp download + ffmpeg metadata embedding
|
||||
│ ├── squid.js # Qobuz (squid.wtf) download backend
|
||||
│ ├── downloader.js # Unified downloadTrack / downloadAlbumTracks
|
||||
│ └── romaji.js # Japanese → romaji search index
|
||||
├── android/ # Native Android app (Jetpack Compose + Media3)
|
||||
@@ -162,9 +162,6 @@ AUDIO_FMT=mp3 # mp3 | flac | opus | best
|
||||
AUDIO_QUALITY=0 # 0 = best (yt-dlp)
|
||||
MAX_PARALLEL=2
|
||||
|
||||
DOWNLOAD_BACKEND=auto # ytdlp | squid | auto
|
||||
SQUID_QUALITY=mp3 # mp3 | flac | hi-res
|
||||
|
||||
TICKETMASTER_API_KEY= # optional — free key from developer.ticketmaster.com
|
||||
```
|
||||
|
||||
@@ -203,8 +200,6 @@ Frontend on port 8080, backend on port 3700. Route both through your reverse pro
|
||||
| `AUDIO_FMT` | `mp3` | Download format (`mp3`, `flac`, `opus`, `best`) |
|
||||
| `AUDIO_QUALITY` | `0` | yt-dlp audio quality (0 = best) |
|
||||
| `MAX_PARALLEL` | `2` | Max concurrent download jobs |
|
||||
| `DOWNLOAD_BACKEND` | `auto` | `ytdlp` / `squid` / `auto` |
|
||||
| `SQUID_QUALITY` | `mp3` | Qobuz quality: `mp3`, `flac`, `hi-res` |
|
||||
| `TICKETMASTER_API_KEY` | — | Free key from [developer.ticketmaster.com](https://developer.ticketmaster.com) |
|
||||
|
||||
---
|
||||
|
||||
@@ -6,10 +6,7 @@ const CONFIG = {
|
||||
audioFmt: process.env.AUDIO_FMT || 'mp3',
|
||||
audioQuality: process.env.AUDIO_QUALITY || '0',
|
||||
maxParallel: parseInt(process.env.MAX_PARALLEL || '2', 10),
|
||||
// 'ytdlp' | 'squid' | 'auto' (squid first, yt-dlp fallback)
|
||||
downloadBackend: (process.env.DOWNLOAD_BACKEND || 'auto').toLowerCase(),
|
||||
// squid.wtf quality: 'mp3' | 'flac' | 'hi-res'
|
||||
squidQuality: (process.env.SQUID_QUALITY || 'mp3').toLowerCase(),
|
||||
downloadBackend: (process.env.DOWNLOAD_BACKEND || 'ytdlp').toLowerCase(),
|
||||
ticketmasterKey: process.env.TICKETMASTER_API_KEY || '',
|
||||
};
|
||||
|
||||
|
||||
@@ -1,24 +1,9 @@
|
||||
const CONFIG = require('./config');
|
||||
const { log, updateJob } = require('./jobs');
|
||||
const ytdlp = require('./ytdlp');
|
||||
const squid = require('./squid');
|
||||
const ytdlp = require('./ytdlp');
|
||||
const CONFIG = require('./config');
|
||||
|
||||
// Route a single track download to the correct backend, with optional fallback.
|
||||
// Download a single track via yt-dlp.
|
||||
async function downloadTrack(opts) {
|
||||
const backend = CONFIG.downloadBackend;
|
||||
|
||||
if (backend === 'ytdlp') return ytdlp.downloadTrack(opts);
|
||||
if (backend === 'squid') return squid.downloadTrack(opts);
|
||||
|
||||
// 'auto': squid first, yt-dlp as fallback
|
||||
try {
|
||||
const result = await squid.downloadTrack(opts);
|
||||
if (result !== null) return result;
|
||||
// null = not found on Qobuz — fall through to yt-dlp
|
||||
log(opts.jobId, '⚠️ squid.wtf: nicht gefunden — Fallback zu yt-dlp');
|
||||
} catch (e) {
|
||||
log(opts.jobId, `⚠️ squid.wtf Fehler — Fallback zu yt-dlp: ${e.message}`);
|
||||
}
|
||||
return ytdlp.downloadTrack(opts);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,292 +0,0 @@
|
||||
const crypto = require('crypto');
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { spawn } = require('child_process');
|
||||
const CONFIG = require('./config');
|
||||
const { log } = require('./jobs');
|
||||
const {
|
||||
sanitize, navidromeArtist,
|
||||
downloadArtwork, fetchLyrics, findExistingTrack,
|
||||
} = require('./metadata');
|
||||
const { embedMetadata } = require('./ytdlp');
|
||||
const { romajiForStorage } = require('./romaji');
|
||||
|
||||
const SQUID_BASE = 'https://qobuz.squid.wtf';
|
||||
|
||||
// Qobuz quality codes: mp3=5 (320kbps), flac=6 (16/44), hi-res=7 (24/96)
|
||||
const QUALITY_CODE = { mp3: '5', flac: '6', 'hi-res': '7' };
|
||||
|
||||
function squidExt() {
|
||||
return CONFIG.squidQuality === 'mp3' ? 'mp3' : 'flac';
|
||||
}
|
||||
|
||||
// ─── ALTCHA cookie cache ─────────────────────────────────────────────────────
|
||||
let _captchaCookie = null;
|
||||
let _captchaAt = 0;
|
||||
const CAPTCHA_TTL = 27 * 60 * 1000; // refresh 1 min before server's 28-min expiry
|
||||
|
||||
// Standard ALTCHA PoW solver: find number such that SHA-256(salt + number) == challenge
|
||||
async function solveAltcha(challenge) {
|
||||
const { algorithm = 'SHA-256', challenge: target, salt, maxnumber = 1_000_000 } = challenge;
|
||||
if (algorithm !== 'SHA-256') throw new Error(`ALTCHA: unsupported algorithm ${algorithm}`);
|
||||
const startMs = Date.now();
|
||||
|
||||
for (let number = 0; number <= maxnumber; number++) {
|
||||
const hash = crypto.createHash('sha256').update(salt + String(number)).digest('hex');
|
||||
if (hash === target) {
|
||||
return { number, time: Date.now() - startMs };
|
||||
}
|
||||
if (number % 10_000 === 0) await new Promise(r => setImmediate(r));
|
||||
}
|
||||
throw new Error('ALTCHA: no solution found within maxnumber');
|
||||
}
|
||||
|
||||
function httpsGet(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const mod = url.startsWith('https') ? https : http;
|
||||
mod.get(url, { headers: { 'User-Agent': 'NovaSonic/1.0' } }, res => {
|
||||
let d = '';
|
||||
res.on('data', c => d += c);
|
||||
res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: d }));
|
||||
}).on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
function httpsPost(url, body, extraHeaders) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const payload = JSON.stringify(body);
|
||||
const parsed = new URL(url);
|
||||
const opts = {
|
||||
hostname: parsed.hostname,
|
||||
port: parsed.port || 443,
|
||||
path: parsed.pathname + parsed.search,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(payload),
|
||||
'User-Agent': 'NovaSonic/1.0',
|
||||
...extraHeaders,
|
||||
},
|
||||
};
|
||||
const req = https.request(opts, res => {
|
||||
let d = '';
|
||||
res.on('data', c => d += c);
|
||||
res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: d }));
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.write(payload);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function getAltchaCookie() {
|
||||
if (_captchaCookie && Date.now() - _captchaAt < CAPTCHA_TTL) return _captchaCookie;
|
||||
|
||||
// 1. Fetch challenge — standard ALTCHA returns { algorithm, challenge, salt, signature, maxnumber }
|
||||
const chRes = await httpsGet(`${SQUID_BASE}/api/altcha/challenge`);
|
||||
if (chRes.status !== 200) throw new Error(`ALTCHA challenge HTTP ${chRes.status}: ${chRes.body.slice(0,200)}`);
|
||||
const chBody = JSON.parse(chRes.body);
|
||||
const challenge = chBody.parameters ?? chBody; // unwrap { parameters: {...} } if present
|
||||
log('altcha', `🔐 ALTCHA challenge: alg=${challenge.algorithm} max=${challenge.maxnumber}`);
|
||||
|
||||
// 2. Solve PoW
|
||||
const solution = await solveAltcha(challenge);
|
||||
log('altcha', `🔐 ALTCHA gelöst in ${solution.time}ms (number=${solution.number})`);
|
||||
|
||||
// 3. Verify — standard ALTCHA payload: base64(JSON { algorithm, challenge, number, salt, signature })
|
||||
const payloadObj = {
|
||||
algorithm: challenge.algorithm || 'SHA-256',
|
||||
challenge: challenge.challenge,
|
||||
number: solution.number,
|
||||
salt: challenge.salt,
|
||||
signature: challenge.signature,
|
||||
};
|
||||
const payload = Buffer.from(JSON.stringify(payloadObj)).toString('base64');
|
||||
const vRes = await httpsPost(`${SQUID_BASE}/api/altcha/verify`, { payload });
|
||||
if (vRes.status !== 200) throw new Error(`ALTCHA verify HTTP ${vRes.status}: ${vRes.body.slice(0,200)}`);
|
||||
|
||||
// Parse Set-Cookie: captcha_verified_at=<value>; ...
|
||||
const setCookie = [vRes.headers['set-cookie']].flat().filter(Boolean);
|
||||
const cookiePair = setCookie.map(c => c.split(';')[0]).find(c => c.startsWith('captcha_verified_at='));
|
||||
if (!cookiePair) throw new Error('ALTCHA verify: no cookie in response');
|
||||
|
||||
_captchaCookie = cookiePair;
|
||||
_captchaAt = Date.now();
|
||||
return _captchaCookie;
|
||||
}
|
||||
|
||||
async function squidApiGet(endpoint) {
|
||||
const cookie = await getAltchaCookie();
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = `${SQUID_BASE}${endpoint}`;
|
||||
const parsed = new URL(url);
|
||||
const opts = {
|
||||
hostname: parsed.hostname,
|
||||
port: parsed.port || 443,
|
||||
path: parsed.pathname + parsed.search,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Cookie': cookie,
|
||||
'Token-Country': 'US',
|
||||
'User-Agent': 'NovaSonic/1.0',
|
||||
},
|
||||
};
|
||||
const req = https.request(opts, res => {
|
||||
let d = '';
|
||||
res.on('data', c => d += c);
|
||||
res.on('end', () => resolve({ status: res.statusCode, body: d }));
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// Search Qobuz via squid.wtf, return best matching track object or null
|
||||
async function squidSearch(artist, title, durationMs) {
|
||||
const query = encodeURIComponent(`${artist} ${title}`);
|
||||
const res = await squidApiGet(`/api/get-music?q=${query}&offset=0`);
|
||||
if (res.status !== 200) throw new Error(`squid search HTTP ${res.status}`);
|
||||
|
||||
const json = JSON.parse(res.body);
|
||||
// Handle both {data:{tracks:{items:[...]}}} and {tracks:{items:[...]}} wrappers
|
||||
const items = json?.data?.tracks?.items ?? json?.tracks?.items ?? [];
|
||||
if (!items.length) return null;
|
||||
|
||||
const norm = s => (s || '').toLowerCase().trim();
|
||||
const normT = norm(title);
|
||||
const normA = norm(artist).split(/[,\s]/)[0];
|
||||
|
||||
// Score candidates: prefer exact title match + matching artist + duration proximity
|
||||
const scored = items.map(t => {
|
||||
const tTitle = norm(t.title);
|
||||
const tArtist = norm(t.performer?.name || '');
|
||||
const tDuration = (t.duration || 0) * 1000; // Qobuz uses seconds
|
||||
|
||||
let score = 0;
|
||||
if (tTitle === normT) score += 100;
|
||||
else if (tTitle.includes(normT)) score += 50;
|
||||
if (tArtist.startsWith(normA)) score += 30;
|
||||
if (durationMs && tDuration) {
|
||||
const diff = Math.abs(tDuration - durationMs);
|
||||
if (diff < 3000) score += 20; // within 3 s
|
||||
else if (diff < 10000) score += 10; // within 10 s
|
||||
else if (diff > 30000) score -= 20; // way off
|
||||
}
|
||||
return { track: t, score };
|
||||
});
|
||||
|
||||
scored.sort((a, b) => b.score - a.score);
|
||||
const best = scored[0];
|
||||
// Reject if score is too low (no reasonable match)
|
||||
if (best.score < 30) return null;
|
||||
return best.track;
|
||||
}
|
||||
|
||||
// Stream a URL to destPath (handles redirects via Location header)
|
||||
async function downloadUrlToFile(url, destPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tryFetch = (u, hops) => {
|
||||
if (hops > 5) { reject(new Error('Too many redirects')); return; }
|
||||
const mod = u.startsWith('https') ? https : http;
|
||||
const parsed = new URL(u);
|
||||
const opts = {
|
||||
hostname: parsed.hostname,
|
||||
port: parsed.port || (u.startsWith('https') ? 443 : 80),
|
||||
path: parsed.pathname + parsed.search,
|
||||
method: 'GET',
|
||||
headers: { 'User-Agent': 'NovaSonic/1.0' },
|
||||
};
|
||||
const req = mod.request(opts, res => {
|
||||
if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307) {
|
||||
res.resume();
|
||||
tryFetch(res.headers.location, hops + 1);
|
||||
return;
|
||||
}
|
||||
if (res.statusCode !== 200) {
|
||||
res.resume();
|
||||
reject(new Error(`Download HTTP ${res.statusCode}`));
|
||||
return;
|
||||
}
|
||||
const file = fs.createWriteStream(destPath);
|
||||
res.pipe(file);
|
||||
file.on('finish', () => file.close(resolve));
|
||||
file.on('error', reject);
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.end();
|
||||
};
|
||||
tryFetch(url, 0);
|
||||
});
|
||||
}
|
||||
|
||||
// Download a single track via squid.wtf / Qobuz.
|
||||
// Returns { success: true, dir, skipped? } on success, null if track not found on Qobuz.
|
||||
async function downloadTrack({ jobId, artist, rawArtist, albumArtist, title, album, year, trackNum, totalTracks, discNum, discTotal, compilation, genre, artworkUrl, explicit, outputDir, durationMs }) {
|
||||
const mainArtist = albumArtist || artist;
|
||||
// Use romaji for folder/filename — same as ytdlp path so paths are consistent
|
||||
const mainArtistRomaji = romajiForStorage(mainArtist) || mainArtist;
|
||||
const titleRomaji = romajiForStorage(title) || title;
|
||||
const albumRomaji = album ? (romajiForStorage(album) || album) : null;
|
||||
const safeArtist = sanitize(mainArtistRomaji.split(/[,;&]/)[0].trim());
|
||||
const safeAlbum = sanitize(albumRomaji || 'Singles');
|
||||
const dir = path.join(outputDir || CONFIG.musicPath, safeArtist, safeAlbum);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
|
||||
const safeTitle = sanitize(titleRomaji);
|
||||
const trackPrefix = trackNum ? String(trackNum).padStart(2, '0') + ' - ' : '';
|
||||
const ext = squidExt();
|
||||
|
||||
const existing = findExistingTrack(dir, trackPrefix, safeTitle);
|
||||
if (existing) {
|
||||
log(jobId, `⏭ Bereits vorhanden: ${path.basename(existing)} — übersprungen`);
|
||||
return { success: true, dir, skipped: true };
|
||||
}
|
||||
|
||||
log(jobId, `🦑 squid.wtf: suche "${artist} – ${title}"`);
|
||||
|
||||
// Solve ALTCHA if needed (cached for 27 min)
|
||||
await getAltchaCookie();
|
||||
|
||||
// Find track on Qobuz
|
||||
const searchArtist = mainArtist.replace(/[^\p{L}\p{N}\s,&\-]/gu, '').trim();
|
||||
const track = await squidSearch(searchArtist, title, durationMs);
|
||||
if (!track) {
|
||||
log(jobId, `⚠️ squid.wtf: "${title}" nicht auf Qobuz gefunden`);
|
||||
return null; // signals caller to fall back to yt-dlp
|
||||
}
|
||||
|
||||
// Get download URL
|
||||
const quality = QUALITY_CODE[CONFIG.squidQuality] || '5';
|
||||
const dlRes = await squidApiGet(`/api/download-music?track_id=${track.id}&quality=${quality}`);
|
||||
if (dlRes.status !== 200) throw new Error(`squid download-music HTTP ${dlRes.status}`);
|
||||
|
||||
const dlJson = JSON.parse(dlRes.body);
|
||||
const streamUrl = dlJson?.data?.url ?? dlJson?.url;
|
||||
if (!streamUrl) throw new Error('squid: no download URL in response');
|
||||
|
||||
// Download to a temp file, then embed metadata
|
||||
const audioFile = path.join(dir, `${trackPrefix}${safeTitle}.${ext}`);
|
||||
const tmpFile = audioFile + '.squidtmp';
|
||||
log(jobId, `⬇️ Lade herunter (Qobuz ${CONFIG.squidQuality.toUpperCase()})…`);
|
||||
try {
|
||||
await downloadUrlToFile(streamUrl, tmpFile);
|
||||
fs.renameSync(tmpFile, audioFile);
|
||||
} catch (e) {
|
||||
try { fs.unlinkSync(tmpFile); } catch {}
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Use Qobuz artwork if no better URL was passed in
|
||||
const finalArtwork = artworkUrl || track.album?.image?.large || track.album?.image?.small || '';
|
||||
|
||||
return embedMetadata({
|
||||
jobId, audioFile, dir, trackPrefix, safeTitle,
|
||||
rawArtist, artist, mainArtist, album, year, trackNum, totalTracks,
|
||||
discNum, discTotal, compilation, genre,
|
||||
artworkUrl: finalArtwork, explicit, title,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { downloadTrack };
|
||||
@@ -386,7 +386,6 @@ app.get('/api/health', (req, res) => {
|
||||
musicPath: CONFIG.musicPath,
|
||||
audioFormat: CONFIG.audioFmt,
|
||||
downloadBackend: CONFIG.downloadBackend,
|
||||
squidQuality: CONFIG.squidQuality,
|
||||
activeJobs: activeDownloads,
|
||||
queueLength: downloadQueue.length,
|
||||
spotify: { configured: true },
|
||||
@@ -1428,10 +1427,6 @@ app.listen(CONFIG.port, () => {
|
||||
console.log(`\n🎵 NovaSonic Backend running on port ${CONFIG.port}`);
|
||||
console.log(` Music folder : ${CONFIG.musicPath}`);
|
||||
console.log(` Audio format : ${CONFIG.audioFmt} (quality ${CONFIG.audioQuality})`);
|
||||
console.log(` Download backend : ${CONFIG.downloadBackend}`);
|
||||
if (CONFIG.downloadBackend !== 'ytdlp') {
|
||||
console.log(` Squid quality : ${CONFIG.squidQuality}`);
|
||||
}
|
||||
console.log(` yt-dlp : ${ytdlpVersion}`);
|
||||
console.log('');
|
||||
});
|
||||
+1
-2
@@ -30,8 +30,7 @@ services:
|
||||
- AUDIO_FMT=${AUDIO_FMT:-mp3}
|
||||
- AUDIO_QUALITY=${AUDIO_QUALITY:-0}
|
||||
- MAX_PARALLEL=${MAX_PARALLEL:-2}
|
||||
- DOWNLOAD_BACKEND=${DOWNLOAD_BACKEND:-auto}
|
||||
- SQUID_QUALITY=${SQUID_QUALITY:-mp3}
|
||||
- DOWNLOAD_BACKEND=${DOWNLOAD_BACKEND:-ytdlp}
|
||||
- TICKETMASTER_API_KEY=${TICKETMASTER_API_KEY}
|
||||
command: >
|
||||
sh -c "
|
||||
|
||||
Reference in new issue
Block a user