From be050b0057eb99c16870c486d9ebfd830b89e77a Mon Sep 17 00:00:00 2001 From: Jarian Cottingham Date: Wed, 18 Feb 2026 21:32:35 -0600 Subject: [PATCH] Added quality column feature. Files now display quality information like 1080p30fps or 720p60fps. Enhanced error handling for problematic files with visual labeling and quality extraction. --- index.html | 1 + renderer.js | 2 +- utils/fileUtils.js | 104 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index c035e1a..30b95f3 100644 --- a/index.html +++ b/index.html @@ -131,6 +131,7 @@ .file-duration, .file-quality { margin: 0 10px; color: #666; + min-width: 80px; /* Ensure consistent column width */ } .problematic-label { diff --git a/renderer.js b/renderer.js index 88fced5..410f623 100644 --- a/renderer.js +++ b/renderer.js @@ -70,7 +70,7 @@ function displayFiles(files) { // Use actual duration from file metadata const duration = file.duration || '00:00'; - const quality = 'unknown'; // Placeholder for quality + const quality = file.quality || 'unknown'; // Add label for problematic files const problemLabel = file.isProblematic ? '⚠️ ' : ''; diff --git a/utils/fileUtils.js b/utils/fileUtils.js index 434ab65..a41b21b 100644 --- a/utils/fileUtils.js +++ b/utils/fileUtils.js @@ -35,14 +35,15 @@ async function scanDirectory(directoryPath) { const subDirFiles = await scanDirectory(filePath); mediaFiles.push(...subDirFiles); } else if (isMediaFile(filePath)) { - // Extract duration for media files + // Extract duration and quality for media files let fileData = { path: filePath, name: file, size: stat.size, modified: stat.mtime, duration: '00:00', - isProblematic: false + isProblematic: false, + quality: 'unknown' }; try { @@ -53,6 +54,13 @@ async function scanDirectory(directoryPath) { console.warn(`Failed to extract duration for ${filePath}:`, error.message); } + try { + const quality = await extractVideoQuality(filePath); + fileData.quality = quality; + } catch (error) { + console.warn(`Failed to extract quality for ${filePath}:`, error.message); + } + mediaFiles.push(fileData); } } catch (fileError) { @@ -107,23 +115,106 @@ async function extractFileDuration(filePath) { }); } +/** + * Extract video quality information using ffmpeg + * @param {string} filePath - Path to the media file + * @returns {Promise} - Quality information (e.g., "1080p30fps") + */ +async function extractVideoQuality(filePath) { + return new Promise((resolve, reject) => { + ffmpeg.ffprobe(filePath, (err, metadata) => { + if (err) { + console.warn(`ffprobe error for quality extraction ${filePath}:`, err.message); + resolve('unknown'); + return; + } + + try { + // Find the video stream + const videoStream = metadata.streams.find(stream => stream.codec_type === 'video'); + + if (!videoStream) { + resolve('unknown'); + return; + } + + // Get resolution + const width = videoStream.width || 0; + const height = videoStream.height || 0; + + // Determine quality based on height + let quality = 'unknown'; + if (height >= 2160) { + quality = '4K'; + } else if (height >= 1440) { + quality = '1440p'; + } else if (height >= 1080) { + quality = '1080p'; + } else if (height >= 720) { + quality = '720p'; + } else if (height >= 480) { + quality = '480p'; + } else { + quality = 'unknown'; + } + + // Get frame rate + let fps = ''; + if (videoStream.r_frame_rate) { + // Parse frame rate like "30/1" or "25/1" + const fpsParts = videoStream.r_frame_rate.split('/'); + if (fpsParts.length === 2) { + const fpsValue = Math.round(parseInt(fpsParts[0]) / parseInt(fpsParts[1])); + fps = `${fpsValue}fps`; + } else { + const fpsValue = Math.round(parseFloat(videoStream.r_frame_rate)); + fps = `${fpsValue}fps`; + } + } + + // Format the quality string + if (quality !== 'unknown' && fps) { + resolve(`${quality}${fps}`); + } else if (quality !== 'unknown') { + resolve(quality); + } else if (fps) { + resolve(fps); + } else { + resolve('unknown'); + } + } catch (error) { + console.warn(`Error extracting quality for ${filePath}:`, error.message); + resolve('unknown'); + } + }); + }); +} + /** * Extract file metadata (duration and quality) * @param {string} filePath - Path to the media file * @returns {Promise} - File metadata */ async function extractFileMetadata(filePath) { - // Extract duration using ffmpeg + // Extract duration and quality using ffmpeg let duration = '00:00'; + let quality = 'unknown'; + try { duration = await extractFileDuration(filePath); } catch (error) { console.warn(`Failed to extract duration for ${filePath}:`, error.message); } + try { + quality = await extractVideoQuality(filePath); + } catch (error) { + console.warn(`Failed to extract quality for ${filePath}:`, error.message); + } + return { duration: duration, - quality: 'unknown' + quality: quality }; } @@ -131,5 +222,6 @@ module.exports = { scanDirectory, extractFileMetadata, isMediaFile, - extractFileDuration -}; \ No newline at end of file + extractFileDuration, + extractVideoQuality +};