Implement video preview feature with centered modal, MKV file support, and improved user experience
This commit is contained in:
parent
8c5ce29754
commit
4ba3b629a1
@ -45,6 +45,8 @@ MovieMapper is a desktop application designed for organizing and managing movie
|
|||||||
- **File Untagging**: Click on already tagged files to remove the tag
|
- **File Untagging**: Click on already tagged files to remove the tag
|
||||||
- **File Organization**: Move files to "extras" folder for better organization
|
- **File Organization**: Move files to "extras" folder for better organization
|
||||||
- **Problematic File Handling**: Identifies and flags files that cause issues during metadata extraction
|
- **Problematic File Handling**: Identifies and flags files that cause issues during metadata extraction
|
||||||
|
- **Video Preview**: Click the 🎬 icon next to any file to preview it in a centered modal window
|
||||||
|
- **MKV File Support**: MKV files show a warning and provide option to open in default media player
|
||||||
- **Warning Sign Display**: Files with issues are indicated with a warning sign (⚠️) in the UI but this does not affect the actual file name
|
- **Warning Sign Display**: Files with issues are indicated with a warning sign (⚠️) in the UI but this does not affect the actual file name
|
||||||
|
|
||||||
### 4. Technical Features
|
### 4. Technical Features
|
||||||
|
|||||||
33
main.js
33
main.js
@ -495,6 +495,39 @@ ipcMain.handle('move-to-extras', async (event, filePath) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// IPC handler for opening file in default player
|
||||||
|
ipcMain.handle('open-file-in-player', async (event, filePath) => {
|
||||||
|
try {
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
|
||||||
|
// Open file in default player based on OS
|
||||||
|
let command;
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
// macOS
|
||||||
|
command = `open "${filePath}"`;
|
||||||
|
} else if (process.platform === 'win32') {
|
||||||
|
// Windows
|
||||||
|
command = `start "" "${filePath}"`;
|
||||||
|
} else {
|
||||||
|
// Linux
|
||||||
|
command = `xdg-open "${filePath}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
exec(command, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
console.error('Error opening file:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
console.log('File opened successfully:', filePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true, message: 'File opened in default player' };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error opening file in player:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
// This method will be called when Electron has finished
|
||||||
// initialization and is ready to create browser windows.
|
// initialization and is ready to create browser windows.
|
||||||
app.whenReady().then(createWindow);
|
app.whenReady().then(createWindow);
|
||||||
|
|||||||
204
renderer.js
204
renderer.js
@ -89,6 +89,7 @@ function displayFiles(files) {
|
|||||||
<div class="file-tags">
|
<div class="file-tags">
|
||||||
<span class="tag-icon extra-tag" data-file-path="${file.path}" title="Mark as Extra">🏷️</span>
|
<span class="tag-icon extra-tag" data-file-path="${file.path}" title="Mark as Extra">🏷️</span>
|
||||||
<span class="tag-icon commentary-tag" data-file-path="${file.path}" title="Add Commentary">💬</span>
|
<span class="tag-icon commentary-tag" data-file-path="${file.path}" title="Add Commentary">💬</span>
|
||||||
|
<span class="video-preview-btn" data-file-path="${file.path}" title="Preview Video">🎬</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -136,6 +137,14 @@ function displayFiles(files) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add click handler for preview button
|
||||||
|
const previewBtn = fileItem.querySelector('.video-preview-btn');
|
||||||
|
previewBtn.addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation(); // Prevent event bubbling
|
||||||
|
const filePath = this.dataset.filePath;
|
||||||
|
openVideoPreview(filePath);
|
||||||
|
});
|
||||||
|
|
||||||
fileListEl.appendChild(fileItem);
|
fileListEl.appendChild(fileItem);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -629,5 +638,198 @@ function debounce(func, wait) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Modal element for video preview
|
||||||
|
let videoPreviewModal = null;
|
||||||
|
|
||||||
// Initialize the application
|
// Initialize the application
|
||||||
console.log('Movie Mapper application initialized');
|
console.log('Movie Mapper application initialized');
|
||||||
|
|
||||||
|
// Function to open video preview modal
|
||||||
|
function openVideoPreview(filePath) {
|
||||||
|
console.log('Opening video preview for:', filePath);
|
||||||
|
|
||||||
|
// Create modal if it doesn't exist
|
||||||
|
if (!videoPreviewModal) {
|
||||||
|
createVideoPreviewModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the modal
|
||||||
|
videoPreviewModal.style.display = 'block';
|
||||||
|
|
||||||
|
// Load video content
|
||||||
|
loadVideoPreview(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to create video preview modal
|
||||||
|
function createVideoPreviewModal() {
|
||||||
|
// Create modal container
|
||||||
|
videoPreviewModal = document.createElement('div');
|
||||||
|
videoPreviewModal.id = 'video-preview-modal';
|
||||||
|
videoPreviewModal.style.cssText = `
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.9);
|
||||||
|
z-index: 1000;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
overflow: auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Create modal content
|
||||||
|
const modalContent = document.createElement('div');
|
||||||
|
modalContent.style.cssText = `
|
||||||
|
position: relative;
|
||||||
|
max-width: 90%;
|
||||||
|
max-height: 90%;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Create close button
|
||||||
|
const closeButton = document.createElement('span');
|
||||||
|
closeButton.innerHTML = '×';
|
||||||
|
closeButton.style.cssText = `
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 15px;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #aaa;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s;
|
||||||
|
`;
|
||||||
|
|
||||||
|
closeButton.addEventListener('mouseenter', function() {
|
||||||
|
this.style.color = '#000';
|
||||||
|
});
|
||||||
|
|
||||||
|
closeButton.addEventListener('click', function() {
|
||||||
|
videoPreviewModal.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create video container
|
||||||
|
const videoContainer = document.createElement('div');
|
||||||
|
videoContainer.id = 'video-preview-container';
|
||||||
|
videoContainer.style.cssText = `
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Create file info display
|
||||||
|
const fileInfo = document.createElement('div');
|
||||||
|
fileInfo.id = 'video-preview-file-info';
|
||||||
|
fileInfo.style.cssText = `
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Create loading indicator
|
||||||
|
const loadingIndicator = document.createElement('div');
|
||||||
|
loadingIndicator.id = 'video-preview-loading';
|
||||||
|
loadingIndicator.textContent = 'Loading video preview...';
|
||||||
|
loadingIndicator.style.cssText = `
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Assemble modal
|
||||||
|
modalContent.appendChild(closeButton);
|
||||||
|
modalContent.appendChild(fileInfo);
|
||||||
|
modalContent.appendChild(videoContainer);
|
||||||
|
modalContent.appendChild(loadingIndicator);
|
||||||
|
videoPreviewModal.appendChild(modalContent);
|
||||||
|
|
||||||
|
// Add click outside to close
|
||||||
|
videoPreviewModal.addEventListener('click', function(e) {
|
||||||
|
if (e.target === videoPreviewModal) {
|
||||||
|
videoPreviewModal.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add to body
|
||||||
|
document.body.appendChild(videoPreviewModal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to load video preview
|
||||||
|
async function loadVideoPreview(filePath) {
|
||||||
|
const fileInfo = document.getElementById('video-preview-file-info');
|
||||||
|
const videoContainer = document.getElementById('video-preview-container');
|
||||||
|
const loadingIndicator = document.getElementById('video-preview-loading');
|
||||||
|
|
||||||
|
// Show loading
|
||||||
|
loadingIndicator.style.display = 'block';
|
||||||
|
videoContainer.innerHTML = '';
|
||||||
|
fileInfo.innerHTML = `<strong>Loading:</strong> ${filePath.split('/').pop()}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if file exists
|
||||||
|
const fs = require('fs');
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
throw new Error('File not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file stats for info
|
||||||
|
const stats = fs.statSync(filePath);
|
||||||
|
const fileName = filePath.split('/').pop();
|
||||||
|
|
||||||
|
// Update file info
|
||||||
|
fileInfo.innerHTML = `
|
||||||
|
<strong>File:</strong> ${fileName}<br>
|
||||||
|
<strong>Size:</strong> ${(stats.size / (1024 * 1024)).toFixed(2)} MB<br>
|
||||||
|
<strong>Path:</strong> ${filePath}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// For MKV files, we'll open in default player instead of trying to preview
|
||||||
|
const ext = filePath.toLowerCase().split('.').pop();
|
||||||
|
if (ext === 'mkv') {
|
||||||
|
// For MKV files, show message and provide option to open in default player
|
||||||
|
videoContainer.innerHTML = `
|
||||||
|
<div style="text-align: center; padding: 20px;">
|
||||||
|
<p style="font-size: 16px; color: #666;">
|
||||||
|
<strong>Warning:</strong> MKV files are not supported for in-app preview.
|
||||||
|
</p>
|
||||||
|
<p style="font-size: 14px; color: #888; margin: 10px 0;">
|
||||||
|
This file will open in your default media player.
|
||||||
|
</p>
|
||||||
|
<button id="open-in-player-btn" style="padding: 10px 20px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer;">
|
||||||
|
Open in Default Player
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById('open-in-player-btn').addEventListener('click', function() {
|
||||||
|
// Send request to main process to open file in default player
|
||||||
|
ipcRenderer.invoke('open-file-in-player', filePath);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// For other formats, try to create a video player
|
||||||
|
videoContainer.innerHTML = `
|
||||||
|
<video id="preview-video" controls style="width: 100%; max-width: 800px; height: auto; margin: 0 auto; display: block;">
|
||||||
|
<source src="${filePath}" type="video/mp4">
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Hide loading
|
||||||
|
loadingIndicator.style.display = 'none';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading video preview:', error);
|
||||||
|
fileInfo.innerHTML = `<strong>Error:</strong> Could not load preview for ${filePath}`;
|
||||||
|
videoContainer.innerHTML = '<p style="color: #dc3545; text-align: center;">Error loading video preview</p>';
|
||||||
|
loadingIndicator.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user