Major Log Refactor & Cleanup
This commit is contained in:
@@ -6,6 +6,11 @@ const { execSync } = require('child_process');
|
||||
let validationCache = null;
|
||||
let cacheForPath = null;
|
||||
|
||||
// Global shared playlist cache
|
||||
let sharedPlaylistFile = null;
|
||||
let sharedPlaylistTrackCount = 0;
|
||||
let sharedPlaylistPath = null;
|
||||
|
||||
/**
|
||||
* Validate an audio file using ffprobe
|
||||
* @param {string} filePath - Path to audio file
|
||||
@@ -23,7 +28,6 @@ function validateAudioFile(filePath) {
|
||||
|
||||
// Check if audio stream exists and has valid properties
|
||||
if (!data.streams || data.streams.length === 0) {
|
||||
console.warn(`⚠️ Skipping ${path.basename(filePath)}: No audio stream found`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -31,27 +35,23 @@ function validateAudioFile(filePath) {
|
||||
|
||||
// Validate codec
|
||||
if (!stream.codec_name) {
|
||||
console.warn(`⚠️ Skipping ${path.basename(filePath)}: Unknown codec`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate sample rate (should be reasonable)
|
||||
const sampleRate = parseInt(stream.sample_rate);
|
||||
if (!sampleRate || sampleRate < 8000 || sampleRate > 192000) {
|
||||
console.warn(`⚠️ Skipping ${path.basename(filePath)}: Invalid sample rate (${sampleRate})`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate channels
|
||||
const channels = parseInt(stream.channels);
|
||||
if (!channels || channels < 1 || channels > 8) {
|
||||
console.warn(`⚠️ Skipping ${path.basename(filePath)}: Invalid channel count (${channels})`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn(`⚠️ Skipping ${path.basename(filePath)}: Validation failed (${error.message})`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -91,28 +91,24 @@ function getAllMusicFiles(musicPath) {
|
||||
|
||||
// Return cached results if available for this path
|
||||
if (validationCache && cacheForPath === musicPath) {
|
||||
console.log(`✅ Using cached validation results: ${validationCache.length} files`);
|
||||
return validationCache;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(musicPath).filter(f => f.endsWith('.ogg'));
|
||||
const filePaths = files.map(f => path.join(musicPath, f));
|
||||
|
||||
console.log(`🎵 Validating ${filePaths.length} audio files (first run, this will be cached)...`);
|
||||
console.log(`Validating ${filePaths.length} audio files...`);
|
||||
|
||||
// Validate each file
|
||||
const validFiles = filePaths.filter(validateAudioFile);
|
||||
|
||||
console.log(`✅ ${validFiles.length}/${filePaths.length} audio files passed validation`);
|
||||
|
||||
if (validFiles.length < filePaths.length) {
|
||||
console.log(`⚠️ ${filePaths.length - validFiles.length} files were skipped due to issues`);
|
||||
console.log(`Warning: ${filePaths.length - validFiles.length} files skipped due to validation errors`);
|
||||
}
|
||||
|
||||
// Cache the results
|
||||
validationCache = validFiles;
|
||||
cacheForPath = musicPath;
|
||||
console.log(`💾 Validation results cached for future requests`);
|
||||
|
||||
return validFiles;
|
||||
} catch (error) {
|
||||
@@ -135,43 +131,95 @@ function shuffleArray(array) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a playlist file for FFmpeg concat demuxer with optimized format
|
||||
* Initialize shared playlist at startup
|
||||
* @param {string} musicPath - Path to music directory
|
||||
* @returns {{playlistFile: string, trackCount: number}|null} Playlist info or null
|
||||
*/
|
||||
function createPlaylist(musicPath) {
|
||||
function initializeSharedPlaylist(musicPath) {
|
||||
const allMusicFiles = getAllMusicFiles(musicPath);
|
||||
console.log(`Found ${allMusicFiles.length} validated music files`);
|
||||
|
||||
if (allMusicFiles.length === 0) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a temporary concat playlist file
|
||||
const playlistFile = path.join('/tmp', `playlist-${Date.now()}.txt`);
|
||||
// Create a persistent shared playlist file
|
||||
const playlistFile = path.join('/tmp', `shared-playlist-${Date.now()}.txt`);
|
||||
|
||||
// Build playlist content - repeat enough times for ~24 hours of playback
|
||||
// Assuming avg 3 min per track, repeat enough to cover a full day
|
||||
const repetitions = Math.max(20, Math.ceil(480 / allMusicFiles.length)); // At least 480 tracks (~24hrs)
|
||||
const repetitions = Math.max(20, Math.ceil(480 / allMusicFiles.length));
|
||||
const playlistLines = [];
|
||||
|
||||
// Use FFmpeg concat demuxer format with improved options for smooth transitions
|
||||
playlistLines.push('ffconcat version 1.0');
|
||||
|
||||
for (let i = 0; i < repetitions; i++) {
|
||||
// Re-shuffle each repetition for more variety
|
||||
const shuffled = shuffleArray([...allMusicFiles]);
|
||||
shuffled.forEach(f => {
|
||||
// Escape single quotes in file paths for concat format
|
||||
const escapedPath = f.replace(/'/g, "'\\''");
|
||||
playlistLines.push(`file '${escapedPath}'`);
|
||||
// Add duration metadata hint for smoother transitions (helps FFmpeg prebuffer)
|
||||
playlistLines.push('# Auto-generated entry');
|
||||
});
|
||||
}
|
||||
|
||||
fs.writeFileSync(playlistFile, playlistLines.join('\n'));
|
||||
console.log(`✅ Created seamless playlist: ${allMusicFiles.length} tracks × ${repetitions} repetitions = ${allMusicFiles.length * repetitions} total tracks`);
|
||||
const totalTracks = allMusicFiles.length * repetitions;
|
||||
|
||||
// Cache the shared playlist
|
||||
sharedPlaylistFile = playlistFile;
|
||||
sharedPlaylistTrackCount = totalTracks;
|
||||
sharedPlaylistPath = musicPath;
|
||||
|
||||
console.log(`Shared playlist created: ${totalTracks} tracks`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a rotated playlist starting at a random position
|
||||
* @param {string} musicPath - Path to music directory
|
||||
* @returns {{playlistFile: string, trackCount: number}|null} Playlist info or null
|
||||
*/
|
||||
function createPlaylist(musicPath) {
|
||||
// Check if we have a shared playlist
|
||||
if (!sharedPlaylistFile || !fs.existsSync(sharedPlaylistFile)) {
|
||||
console.warn('Warning: Shared playlist not found, initializing...');
|
||||
initializeSharedPlaylist(musicPath);
|
||||
if (!sharedPlaylistFile) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const allMusicFiles = getAllMusicFiles(musicPath);
|
||||
if (allMusicFiles.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create a rotated copy of the playlist with random start
|
||||
const playlistFile = path.join('/tmp', `playlist-${Date.now()}-${Math.random().toString(36).substr(2, 9)}.txt`);
|
||||
const randomStart = Math.floor(Math.random() * allMusicFiles.length);
|
||||
|
||||
const repetitions = Math.max(20, Math.ceil(480 / allMusicFiles.length));
|
||||
const playlistLines = [];
|
||||
|
||||
playlistLines.push('ffconcat version 1.0');
|
||||
|
||||
// First repetition with rotation
|
||||
const firstShuffle = shuffleArray([...allMusicFiles]);
|
||||
for (let i = 0; i < firstShuffle.length; i++) {
|
||||
const idx = (randomStart + i) % firstShuffle.length;
|
||||
const escapedPath = firstShuffle[idx].replace(/'/g, "'\\''");
|
||||
playlistLines.push(`file '${escapedPath}'`);
|
||||
playlistLines.push('# Auto-generated entry');
|
||||
}
|
||||
|
||||
// Remaining repetitions
|
||||
for (let i = 1; i < repetitions; i++) {
|
||||
const shuffled = shuffleArray([...allMusicFiles]);
|
||||
shuffled.forEach(f => {
|
||||
const escapedPath = f.replace(/'/g, "'\\''");
|
||||
playlistLines.push(`file '${escapedPath}'`);
|
||||
playlistLines.push('# Auto-generated entry');
|
||||
});
|
||||
}
|
||||
|
||||
fs.writeFileSync(playlistFile, playlistLines.join('\n'));
|
||||
console.log(`Stream playlist created, starting at position ${randomStart}/${allMusicFiles.length}`);
|
||||
|
||||
return {
|
||||
playlistFile,
|
||||
@@ -183,5 +231,6 @@ module.exports = {
|
||||
getRandomMusicFile,
|
||||
getAllMusicFiles,
|
||||
shuffleArray,
|
||||
createPlaylist
|
||||
createPlaylist,
|
||||
initializeSharedPlaylist
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user