feat: add audio conversion script and enhance audio token handling for .opus files
All checks were successful
Build and Push Docker Image / docker (push) Successful in 21s
All checks were successful
Build and Push Docker Image / docker (push) Successful in 21s
This commit is contained in:
@@ -1,12 +1,23 @@
|
||||
import path from 'path';
|
||||
import { TOKEN_TTL_MS_DEFAULT, putToken, getToken } from './audio/tokenStore.js';
|
||||
import { resolveSafePath, fileExists, statFile, getMimeType } from './audio/pathUtils.js';
|
||||
import { headFile, streamFile } from './audio/streaming.js';
|
||||
import { getCoverForFile } from './audio/coverService.js';
|
||||
|
||||
export function createAudioToken(name, ttlMs = TOKEN_TTL_MS_DEFAULT) {
|
||||
const resolved = resolveSafePath(name);
|
||||
let resolved = resolveSafePath(name);
|
||||
if (!resolved) throw new Error('Invalid path');
|
||||
if (!fileExists(resolved)) throw new Error('Not found');
|
||||
|
||||
// Prefer .opus sibling for streaming, to save bandwidth
|
||||
const ext = path.extname(resolved).toLowerCase();
|
||||
if (ext !== '.opus') {
|
||||
const opusCandidate = resolved.slice(0, -ext.length) + '.opus';
|
||||
if (fileExists(opusCandidate)) {
|
||||
resolved = opusCandidate;
|
||||
}
|
||||
}
|
||||
|
||||
const stat = statFile(resolved);
|
||||
const type = getMimeType(resolved, 'audio/mpeg');
|
||||
return putToken({ path: resolved, mime: type, size: stat.size }, ttlMs);
|
||||
|
||||
@@ -29,5 +29,8 @@ export function statFile(p) {
|
||||
}
|
||||
|
||||
export function getMimeType(p, fallback = 'audio/mpeg') {
|
||||
return mime.getType(p) || fallback;
|
||||
const t = mime.getType(p) || fallback;
|
||||
// Some clients expect audio/ogg for .opus in Ogg container
|
||||
if (/\.opus$/i.test(p)) return 'audio/ogg';
|
||||
return t;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,23 @@ import { loadYearsIndex } from './years.js';
|
||||
|
||||
export async function listTracks() {
|
||||
const years = loadYearsIndex();
|
||||
const files = fs.readdirSync(DATA_DIR).filter((f) => /\.(mp3|wav|m4a|ogg)$/i.test(f));
|
||||
const files = fs
|
||||
.readdirSync(DATA_DIR)
|
||||
.filter((f) => /\.(mp3|wav|m4a|ogg|opus)$/i.test(f))
|
||||
.filter((f) => {
|
||||
// If both base.ext and base.opus exist, list only once (prefer .opus)
|
||||
const ext = path.extname(f).toLowerCase();
|
||||
if (ext === '.opus') return true;
|
||||
const opusTwin = f.replace(/\.[^.]+$/i, '.opus');
|
||||
return !fs.existsSync(path.join(DATA_DIR, opusTwin));
|
||||
});
|
||||
const tracks = await Promise.all(
|
||||
files.map(async (f) => {
|
||||
const fp = path.join(DATA_DIR, f);
|
||||
// Prefer .opus for playback if exists
|
||||
const ext = path.extname(f).toLowerCase();
|
||||
const opusName = ext === '.opus' ? f : f.replace(/\.[^.]+$/i, '.opus');
|
||||
const chosen = fs.existsSync(path.join(DATA_DIR, opusName)) ? opusName : f;
|
||||
const fp = path.join(DATA_DIR, chosen);
|
||||
let year = null;
|
||||
let title = path.parse(f).name;
|
||||
let artist = '';
|
||||
@@ -19,8 +32,8 @@ export async function listTracks() {
|
||||
artist = meta.common.artist || artist;
|
||||
year = meta.common.year || null;
|
||||
} catch {}
|
||||
const y = years[f]?.year ?? year;
|
||||
return { id: f, file: f, title, artist, year: y };
|
||||
const y = years[f]?.year ?? years[chosen]?.year ?? year;
|
||||
return { id: chosen, file: chosen, title, artist, year: y };
|
||||
})
|
||||
);
|
||||
return tracks;
|
||||
|
||||
Reference in New Issue
Block a user