diff --git a/index.html b/index.html
new file mode 100644
index 0000000..c035e1a
--- /dev/null
+++ b/index.html
@@ -0,0 +1,192 @@
+
+
+
+
+
+ Movie Mapper
+
+
+
+
+
+
+
+
+
+
Click "Select Directory" to start browsing media files.
+
+
+
+
+
+
+
+
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..ba3ec0f
--- /dev/null
+++ b/main.js
@@ -0,0 +1,173 @@
+const { app, BrowserWindow, dialog, ipcMain } = require('electron');
+const path = require('path');
+const { scanDirectory, extractFileMetadata } = require('./utils/fileUtils');
+
+// Handle creating/removing shortcuts on Windows when installing/uninstalling.
+try {
+ if (require('electron-squirrel-startup')) {
+ app.quit();
+ }
+} catch (error) {
+ // electron-squirrel-startup not available on non-Windows platforms
+ console.log('electron-squirrel-startup not available (expected on non-Windows platforms)');
+}
+
+let mainWindow;
+
+// Create the browser window.
+function createWindow() {
+ mainWindow = new BrowserWindow({
+ width: 1200,
+ height: 800,
+ webPreferences: {
+ nodeIntegration: true,
+ contextIsolation: false,
+ enableRemoteModule: true,
+ },
+ });
+
+ // and load the index.html of the app.
+ mainWindow.loadFile('index.html');
+
+ // Open the DevTools.
+ // mainWindow.webContents.openDevTools();
+}
+
+// IPC handler for selecting directory
+ipcMain.handle('select-directory', async () => {
+ try {
+ const result = await dialog.showOpenDialog({
+ properties: ['openDirectory']
+ });
+
+ if (result.canceled) {
+ return { success: false, error: 'No directory selected' };
+ }
+
+ return {
+ success: true,
+ directory: result.filePaths[0]
+ };
+ } catch (error) {
+ return { success: false, error: error.message };
+ }
+});
+
+// IPC handler for scanning directory
+ipcMain.handle('scan-directory', async (event, directoryPath) => {
+ try {
+ if (!directoryPath) {
+ return { success: false, error: 'No directory path provided' };
+ }
+
+ const files = await scanDirectory(directoryPath);
+ return { success: true, files: files };
+ } catch (error) {
+ return { success: false, error: error.message };
+ }
+});
+
+// Enhanced logging for debugging problematic files
+ipcMain.handle('log-file-info', async (event, filePath) => {
+ try {
+ const fs = require('fs');
+ const stats = fs.statSync(filePath);
+ return {
+ success: true,
+ fileInfo: {
+ path: filePath,
+ size: stats.size,
+ modified: stats.mtime,
+ isFile: stats.isFile(),
+ isDirectory: stats.isDirectory()
+ }
+ };
+ } catch (error) {
+ return { success: false, error: error.message };
+ }
+});
+
+// IPC handler for renaming file
+ipcMain.handle('rename-file', async (event, { oldPath, newName }) => {
+ try {
+ const fs = require('fs');
+ const newPath = path.join(path.dirname(oldPath), newName);
+
+ // Check if file exists
+ if (!fs.existsSync(oldPath)) {
+ return { success: false, error: 'Original file does not exist' };
+ }
+
+ // Check if new name is different
+ if (oldPath === newPath) {
+ return { success: true, message: 'File name unchanged' };
+ }
+
+ // Rename the file
+ fs.renameSync(oldPath, newPath);
+
+ return { success: true, message: 'File renamed successfully' };
+ } catch (error) {
+ return { success: false, error: error.message };
+ }
+});
+
+// IPC handler for searching TVDB (placeholder)
+ipcMain.handle('search-tvdb', async (event, query) => {
+ try {
+ // This is a placeholder - in a real implementation, this would call TheTVDB API
+ // For now, we'll return mock data to demonstrate the UI functionality
+ const mockResults = [
+ { id: 1, seriesName: 'Breaking Bad' },
+ { id: 2, seriesName: 'Game of Thrones' },
+ { id: 3, seriesName: 'The Office' },
+ { id: 4, seriesName: 'Friends' },
+ { id: 5, seriesName: 'Stranger Things' }
+ ];
+
+ // Filter results based on query
+ const filteredResults = mockResults.filter(show =>
+ show.seriesName.toLowerCase().includes(query.toLowerCase())
+ );
+
+ return { success: true, results: filteredResults };
+ } catch (error) {
+ return { success: false, error: error.message };
+ }
+});
+
+// Test function to verify API connectivity
+ipcMain.handle('test-tvdb-api', async () => {
+ try {
+ // This is a placeholder test - in a real implementation, this would test actual API connectivity
+ // For now, we'll simulate a successful API test
+ return {
+ success: true,
+ message: 'TVDB API test successful',
+ timestamp: new Date().toISOString()
+ };
+ } catch (error) {
+ return { success: false, error: error.message };
+ }
+});
+
+// This method will be called when Electron has finished
+// initialization and is ready to create browser windows.
+app.whenReady().then(createWindow);
+
+// Quit when all windows are closed, except on macOS. There, it's common
+// for applications and their menu bar to stay active until the user quits
+// explicitly with Cmd + Q.
+app.on('window-all-closed', () => {
+ if (process.platform !== 'darwin') {
+ app.quit();
+ }
+});
+
+app.on('activate', () => {
+ // On OS X it's common to re-create a window in the app when the
+ // dock icon is clicked and there are no other windows open.
+ if (BrowserWindow.getAllWindows().length === 0) {
+ createWindow();
+ }
+});
\ No newline at end of file
diff --git a/renderer.js b/renderer.js
index 4b06c42..88fced5 100644
--- a/renderer.js
+++ b/renderer.js
@@ -72,8 +72,11 @@ function displayFiles(files) {
const duration = file.duration || '00:00';
const quality = 'unknown'; // Placeholder for quality
+ // Add label for problematic files
+ const problemLabel = file.isProblematic ? '⚠️ ' : '';
+
fileItem.innerHTML = `
- ${file.name}
+ ${problemLabel}${file.name}
${duration}
${quality}
`;
@@ -173,6 +176,35 @@ async function searchShows() {
}
}
+// Test TVDB API connectivity
+async function testTVDBAPI() {
+ try {
+ const result = await ipcRenderer.invoke('test-tvdb-api');
+ console.log('TVDB API Test:', result);
+ if (result.success) {
+ console.log('TVDB API is working:', result.message);
+ } else {
+ console.error('TVDB API test failed:', result.error);
+ }
+ } catch (error) {
+ console.error('Error testing TVDB API:', error);
+ }
+}
+
+// Debug function to log problematic file info
+async function debugProblematicFile(filePath) {
+ try {
+ const result = await ipcRenderer.invoke('log-file-info', filePath);
+ if (result.success) {
+ console.log('File info:', result.fileInfo);
+ } else {
+ console.error('Failed to get file info:', result.error);
+ }
+ } catch (error) {
+ console.error('Error debugging file:', error);
+ }
+}
+
// Display search results
function displaySearchResults(results) {
searchResultsEl.innerHTML = '';
diff --git a/utils/fileUtils.js b/utils/fileUtils.js
index f70bc92..434ab65 100644
--- a/utils/fileUtils.js
+++ b/utils/fileUtils.js
@@ -36,20 +36,24 @@ async function scanDirectory(directoryPath) {
mediaFiles.push(...subDirFiles);
} else if (isMediaFile(filePath)) {
// Extract duration for media files
- let duration = '00:00';
- try {
- duration = await extractFileDuration(filePath);
- } catch (error) {
- console.warn(`Failed to extract duration for ${filePath}:`, error.message);
- }
-
- mediaFiles.push({
+ let fileData = {
path: filePath,
name: file,
size: stat.size,
modified: stat.mtime,
- duration: duration
- });
+ duration: '00:00',
+ isProblematic: false
+ };
+
+ try {
+ const durationResult = await extractFileDuration(filePath);
+ fileData.duration = durationResult.duration;
+ fileData.isProblematic = durationResult.isProblematic;
+ } catch (error) {
+ console.warn(`Failed to extract duration for ${filePath}:`, error.message);
+ }
+
+ mediaFiles.push(fileData);
}
} catch (fileError) {
// Skip files/directories that cause permission errors
@@ -73,13 +77,24 @@ async function extractFileDuration(filePath) {
return new Promise((resolve, reject) => {
ffmpeg.ffprobe(filePath, (err, metadata) => {
if (err) {
- reject(err);
+ console.warn(`ffprobe error for ${filePath}:`, err.message);
+ // Try alternative approach - attempt to get duration from file stats or use fallback
+ try {
+ const fs = require('fs');
+ const stats = fs.statSync(filePath);
+ console.log(`File size for ${filePath}: ${stats.size} bytes`);
+ // For now, return 00:00 as fallback for problematic files
+ resolve({ duration: '00:00', isProblematic: true });
+ } catch (statsError) {
+ console.warn(`Could not get file stats for ${filePath}:`, statsError.message);
+ resolve({ duration: '00:00', isProblematic: true });
+ }
return;
}
const duration = metadata.format.duration;
if (!duration) {
- resolve('00:00');
+ resolve({ duration: '00:00', isProblematic: false });
return;
}
@@ -87,7 +102,7 @@ async function extractFileDuration(filePath) {
const minutes = Math.floor(duration / 60);
const seconds = Math.floor(duration % 60);
const formattedDuration = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
- resolve(formattedDuration);
+ resolve({ duration: formattedDuration, isProblematic: false });
});
});
}