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