MKV opens directly in player, improved search results contrast, updated AGENTS.md
This commit is contained in:
parent
ecf75036ed
commit
83f3ec4ec4
150
AGENTS.md
150
AGENTS.md
@ -1,7 +1,7 @@
|
|||||||
# MovieMapper Agent Guidelines
|
# MovieMapper Agent Guidelines
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
MovieMapper is an Electron-based desktop application for organizing and managing movie and TV show collections. It provides features for browsing media files, searching TV shows using TheTVDB API, and managing file metadata.
|
MovieMapper is an Electron-based desktop application for organizing and managing movie and TV show collections. It provides features for browsing media files, searching TV shows using TheTVDB API, and managing file metadata with Jellyfin-compatible folder organization.
|
||||||
|
|
||||||
## Build/Lint/Test Commands
|
## Build/Lint/Test Commands
|
||||||
|
|
||||||
@ -15,11 +15,38 @@ MovieMapper is an Electron-based desktop application for organizing and managing
|
|||||||
- `node test-api.js` - Run API connectivity test for TheTVDB
|
- `node test-api.js` - Run API connectivity test for TheTVDB
|
||||||
- `node main.js` - Run main process directly for debugging (may require environment setup)
|
- `node main.js` - Run main process directly for debugging (may require environment setup)
|
||||||
|
|
||||||
|
### Syntax Checking
|
||||||
|
- `node --check renderer.js` - Check renderer.js for syntax errors
|
||||||
|
- `node --check main.js` - Check main.js for syntax errors
|
||||||
|
- `node --check utils/fileUtils.js` - Check fileUtils.js for syntax errors
|
||||||
|
|
||||||
### Linting
|
### Linting
|
||||||
- No explicit linting tools configured in package.json
|
- No explicit linting tools configured in package.json
|
||||||
- Code style follows JavaScript/Node.js conventions
|
- Code style follows JavaScript/Node.js conventions
|
||||||
- Use JSDoc comments for documentation
|
- Use JSDoc comments for documentation
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Main Process (`main.js`)
|
||||||
|
- Handles all IPC communication with renderer
|
||||||
|
- File system operations (moving, renaming files)
|
||||||
|
- TheTVDB API integration with token authentication
|
||||||
|
- Audit logging to `.audit` files in directories
|
||||||
|
- Progress reporting via IPC events
|
||||||
|
|
||||||
|
### Renderer Process (`renderer.js`)
|
||||||
|
- UI state management and DOM manipulation
|
||||||
|
- Event listeners for user interactions
|
||||||
|
- Tag management for files (extra, commentary)
|
||||||
|
- Progress spinner display during scanning
|
||||||
|
- Folder navigation and file display
|
||||||
|
|
||||||
|
### File Utilities (`utils/fileUtils.js`)
|
||||||
|
- Non-recursive directory scanning (current folder only)
|
||||||
|
- FFmpeg integration for metadata extraction (duration, quality, fps)
|
||||||
|
- Progress callback support for UI updates
|
||||||
|
- Returns both folders and media files sorted (folders first)
|
||||||
|
|
||||||
## Code Style Guidelines
|
## Code Style Guidelines
|
||||||
|
|
||||||
### Imports
|
### Imports
|
||||||
@ -54,14 +81,21 @@ MovieMapper is an Electron-based desktop application for organizing and managing
|
|||||||
- Use try/catch blocks for async operations
|
- Use try/catch blocks for async operations
|
||||||
- Handle file system errors gracefully
|
- Handle file system errors gracefully
|
||||||
- Log warnings for problematic files but don't crash the application
|
- Log warnings for problematic files but don't crash the application
|
||||||
- Return consistent error structures from async functions
|
- Return consistent error structures: `{ success: true/false, error?: string, ... }`
|
||||||
- Use specific error messages with context information
|
- Use specific error messages with context information
|
||||||
- Implement proper error propagation throughout the application
|
- Implement proper error propagation throughout the application
|
||||||
|
|
||||||
|
### IPC Communication Patterns
|
||||||
|
- Main process handlers use `ipcMain.handle()` for async operations
|
||||||
|
- Renderer invokes with `ipcRenderer.invoke()` returning promises
|
||||||
|
- Progress updates use `mainWindow.webContents.send()` for push events
|
||||||
|
- Renderer listens with `ipcRenderer.on()` for progress events
|
||||||
|
- Always return `{ success: boolean, ... }` objects from IPC handlers
|
||||||
|
|
||||||
### File Structure
|
### File Structure
|
||||||
- `main.js` - Electron main process handling IPC and application lifecycle
|
- `main.js` - Electron main process handling IPC and application lifecycle
|
||||||
- `renderer.js` - Electron renderer process managing UI interactions
|
- `renderer.js` - Electron renderer process managing UI interactions
|
||||||
- `index.html` - Main HTML structure
|
- `index.html` - Main HTML structure and styling (dark theme)
|
||||||
- `utils/fileUtils.js` - File scanning and metadata extraction utilities
|
- `utils/fileUtils.js` - File scanning and metadata extraction utilities
|
||||||
- `.env` - Environment configuration file for API keys
|
- `.env` - Environment configuration file for API keys
|
||||||
- `app-debug.log` - Application debug log file (created at startup)
|
- `app-debug.log` - Application debug log file (created at startup)
|
||||||
@ -70,7 +104,7 @@ MovieMapper is an Electron-based desktop application for organizing and managing
|
|||||||
- TheTVDB v4 API integration using bearer token authentication
|
- TheTVDB v4 API integration using bearer token authentication
|
||||||
- Proper error handling for API calls
|
- Proper error handling for API calls
|
||||||
- Fallback mechanisms when metadata extraction fails
|
- Fallback mechanisms when metadata extraction fails
|
||||||
- Environment variables for API keys
|
- Environment variables for API keys (TVDB_API_KEY)
|
||||||
- Implement authentication token caching for performance
|
- Implement authentication token caching for performance
|
||||||
|
|
||||||
### Code Patterns
|
### Code Patterns
|
||||||
@ -80,13 +114,15 @@ MovieMapper is an Electron-based desktop application for organizing and managing
|
|||||||
- Use descriptive variable names that reflect their purpose
|
- Use descriptive variable names that reflect their purpose
|
||||||
- Implement comprehensive logging for debugging
|
- Implement comprehensive logging for debugging
|
||||||
- Use consistent IPC patterns for communication between main and renderer processes
|
- Use consistent IPC patterns for communication between main and renderer processes
|
||||||
|
- Use `setImmediate` yields when sending IPC messages in loops to allow event loop processing
|
||||||
|
|
||||||
### Debugging
|
### Debugging
|
||||||
- Built-in debugging utilities for problematic files
|
- Built-in debugging utilities for problematic files
|
||||||
- Console logging for development and debugging
|
- Console logging for development and debugging
|
||||||
- Error messages include context information
|
- Error messages include context information
|
||||||
- Implement debug logging with timestamps
|
- Implement debug logging with timestamps via `writeLog()` function
|
||||||
- Use structured logging for better debugging experience
|
- Use structured logging for better debugging experience
|
||||||
|
- Audit logs written to `.audit` files in each directory
|
||||||
|
|
||||||
### Environment Configuration
|
### Environment Configuration
|
||||||
- API keys stored in `.env` file
|
- API keys stored in `.env` file
|
||||||
@ -94,14 +130,104 @@ MovieMapper is an Electron-based desktop application for organizing and managing
|
|||||||
- Provide clear error messages when environment variables are missing
|
- Provide clear error messages when environment variables are missing
|
||||||
- Use `dotenv` package for loading environment variables
|
- Use `dotenv` package for loading environment variables
|
||||||
|
|
||||||
## Special Notes
|
## Key Features Implementation
|
||||||
- The application uses Electron for cross-platform desktop functionality
|
|
||||||
- File scanning uses recursive directory traversal with proper error handling
|
### File Tagging System
|
||||||
- Media file processing uses ffmpeg for metadata extraction
|
- Files can be tagged as "extra" or "commentary"
|
||||||
- IPC handlers are implemented using Electron's ipcMain for communication between processes
|
- Tags stored as data attributes on DOM elements: `data-tagged-extra`, `data-tagged-commentary`
|
||||||
- The application supports moving files to "extra" and "commentary" folders
|
- Visual feedback with color changes and glow effects when tagged
|
||||||
- API connectivity testing is implemented for TheTVDB v4 integration
|
- Play button enables only when file is tagged
|
||||||
|
- Individual play button moves single file
|
||||||
|
- Floating action button (FAB) moves all tagged files at once
|
||||||
|
|
||||||
|
### Folder Navigation
|
||||||
|
- Non-recursive scanning shows only current directory contents
|
||||||
|
- Folders displayed with 📁 icon at top of list
|
||||||
|
- Clicking folder navigates into it and rescans
|
||||||
|
- `openDirectory(path)` function handles navigation
|
||||||
|
|
||||||
|
### Progress Indication
|
||||||
|
- Spinner with file count shown during directory scanning
|
||||||
|
- Progress container hidden when not scanning
|
||||||
|
- IPC event `scan-progress` sends updates from main to renderer
|
||||||
|
- `setImmediate` yields ensure UI updates during processing
|
||||||
|
|
||||||
|
### File Movement
|
||||||
|
- Files tagged "extra" move to "extras" folder (Jellyfin compatible)
|
||||||
|
- Files tagged "commentary" move to "commentary" folder
|
||||||
|
- Target folders created automatically if they don't exist
|
||||||
|
- Audit log entry written for each move operation
|
||||||
|
- Files removed from UI after successful move
|
||||||
|
|
||||||
|
## Recent Changes (Session 2026-02-22)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
1. **Fixed duplicate `moveTaggedFile` function** - Two definitions were overwriting each other
|
||||||
|
2. **Fixed missing click handler for FAB** - Added `moveAllTaggedFiles()` on floating circle click
|
||||||
|
3. **Fixed "extra" vs "extras" folder naming** - Now correctly uses "extras" for Jellyfin
|
||||||
|
4. **Fixed progressBar reference error** - Removed reference after switching to spinner
|
||||||
|
5. **Removed non-existent test-api-btn reference** - Was causing errors on load
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
1. **Folder navigation** - Click folders to navigate into them
|
||||||
|
2. **Non-recursive scanning** - Only shows current directory contents
|
||||||
|
3. **Progress spinner** - Shows file being processed with count
|
||||||
|
4. **Batch file moving** - FAB moves all tagged files at once
|
||||||
|
5. **Visual feedback** - FAB turns yellow while processing
|
||||||
|
|
||||||
|
### UI Improvements
|
||||||
|
1. **Dark theme** - Modern dark color scheme (#1a1a2e, #16213e, #0f3460)
|
||||||
|
2. **Clean layout** - Search/shows on left sidebar, files on right
|
||||||
|
3. **Accent color** - Pink/red accent (#e94560)
|
||||||
|
4. **Better typography** - System font stack
|
||||||
|
5. **Smooth animations** - Hover effects and transitions
|
||||||
|
|
||||||
|
## Jellyfin Compatible Folder Names
|
||||||
|
|
||||||
|
### Extras Folders (place inside movie/show folder)
|
||||||
|
- `behind the scenes` - Behind-the-scenes content
|
||||||
|
- `deleted scenes` - Deleted scenes
|
||||||
|
- `interviews` - Cast/crew interviews
|
||||||
|
- `scenes` - Individual scenes
|
||||||
|
- `samples` - Sample clips
|
||||||
|
- `shorts` - Short films
|
||||||
|
- `featurettes` - Featurettes
|
||||||
|
- `clips` - Clips
|
||||||
|
- `other` - Generic catch-all for unknown extras
|
||||||
|
- `extras` - Generic catch-all for unknown extras (CURRENTLY IMPLEMENTED)
|
||||||
|
- `trailers` - Trailers and previews
|
||||||
|
- `theme-music` - Theme music audio files
|
||||||
|
- `backdrops` - Backdrop videos
|
||||||
|
|
||||||
|
### Special Single-File Names (in same folder as media)
|
||||||
|
- `trailer` - Single trailer file
|
||||||
|
- `sample` - Single sample file
|
||||||
|
- `theme` - Theme song audio file
|
||||||
|
|
||||||
|
### File Suffix Options (append to filename)
|
||||||
|
- `-trailer`, `.trailer`, `_trailer`, ` trailer`
|
||||||
|
- `-sample`, `.sample`, `_sample`, ` sample`
|
||||||
|
- `-scene`, `-clip`, `-interview`
|
||||||
|
- `-behindthescenes`, `-deleted`, `-deletedscene`
|
||||||
|
- `-featurette`, `-short`, `-other`, `-extra`
|
||||||
|
|
||||||
|
## Known Issues & Future Work
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
- Progress spinner updates may be delayed due to IPC buffering
|
||||||
|
- No "go back" / parent directory navigation yet
|
||||||
|
- Tag state is lost on directory change (not persisted)
|
||||||
|
|
||||||
|
### Suggested Improvements
|
||||||
|
1. Add parent directory navigation ("..") or breadcrumb
|
||||||
|
2. Persist tag state to avoid accidental loss
|
||||||
|
3. Add more Jellyfin folder type options (trailers, featurettes, etc.)
|
||||||
|
4. Add file renaming with episode matching from TVDB
|
||||||
|
5. Add batch rename functionality
|
||||||
|
6. Consider adding undo for file moves
|
||||||
|
7. Add confirmation dialog before moving files
|
||||||
|
|
||||||
## Execution Constraints
|
## Execution Constraints
|
||||||
- All commands that might block indefinitely must be run with a timeout
|
- All commands that might block indefinitely must be run with a timeout
|
||||||
- Example: `timeout 30 npm start` to run the application with 30-second timeout
|
- Example: `timeout 30 npm start` to run the application with 30-second timeout
|
||||||
|
- Always run syntax checks after modifying JS files: `node --check <file>`
|
||||||
28
index.html
28
index.html
@ -74,18 +74,40 @@
|
|||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
background-color: #0f3460;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-result-item {
|
.search-result-item {
|
||||||
padding: 10px;
|
padding: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
transition: background-color 0.2s;
|
background-color: #1a1a2e;
|
||||||
|
border: 1px solid #16213e;
|
||||||
|
color: #fff;
|
||||||
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-result-item:hover {
|
.search-result-item:hover {
|
||||||
background-color: #0f3460;
|
background-color: #e94560;
|
||||||
|
border-color: #e94560;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-item .show-name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-item .show-year {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #aaa;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-item:hover .show-year {
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show Details Section */
|
/* Show Details Section */
|
||||||
|
|||||||
8
main.js
8
main.js
@ -211,16 +211,20 @@ ipcMain.handle('move-file-to-folder', async (event, { filePath, folderName }) =>
|
|||||||
return { success: false, error: error };
|
return { success: false, error: error };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate folder name
|
// Validate folder name and map to actual folder names
|
||||||
|
// "extra" maps to "extras" for Jellyfin compatibility
|
||||||
if (folderName !== 'extra' && folderName !== 'commentary') {
|
if (folderName !== 'extra' && folderName !== 'commentary') {
|
||||||
const error = 'Invalid folder name. Must be "extra" or "commentary"';
|
const error = 'Invalid folder name. Must be "extra" or "commentary"';
|
||||||
writeLog(`ERROR: ${error}`);
|
writeLog(`ERROR: ${error}`);
|
||||||
return { success: false, error: error };
|
return { success: false, error: error };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map folder names to actual directory names
|
||||||
|
const actualFolderName = folderName === 'extra' ? 'extras' : 'commentary';
|
||||||
|
|
||||||
// Get directory containing the file
|
// Get directory containing the file
|
||||||
const fileDir = path.dirname(filePath);
|
const fileDir = path.dirname(filePath);
|
||||||
const targetFolder = path.join(fileDir, folderName);
|
const targetFolder = path.join(fileDir, actualFolderName);
|
||||||
|
|
||||||
writeLog(`Moving file from: ${filePath}`);
|
writeLog(`Moving file from: ${filePath}`);
|
||||||
writeLog(`Target folder: ${targetFolder}`);
|
writeLog(`Target folder: ${targetFolder}`);
|
||||||
|
|||||||
55
renderer.js
55
renderer.js
@ -457,6 +457,13 @@ async function moveAllTaggedFiles() {
|
|||||||
|
|
||||||
console.log(`[RENDERER] Moving ${taggedItems.length} tagged files`);
|
console.log(`[RENDERER] Moving ${taggedItems.length} tagged files`);
|
||||||
|
|
||||||
|
// Visual feedback - change circle color while processing
|
||||||
|
const taggedCircle = document.getElementById('tagged-circle');
|
||||||
|
if (taggedCircle) {
|
||||||
|
taggedCircle.style.backgroundColor = '#ffc107';
|
||||||
|
taggedCircle.style.pointerEvents = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
for (const item of taggedItems) {
|
for (const item of taggedItems) {
|
||||||
const filePath = item.querySelector('.file-name').dataset.filePath;
|
const filePath = item.querySelector('.file-name').dataset.filePath;
|
||||||
@ -471,6 +478,12 @@ async function moveAllTaggedFiles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset circle appearance
|
||||||
|
if (taggedCircle) {
|
||||||
|
taggedCircle.style.backgroundColor = '';
|
||||||
|
taggedCircle.style.pointerEvents = '';
|
||||||
|
}
|
||||||
|
|
||||||
// Update the tagged count after all moves
|
// Update the tagged count after all moves
|
||||||
updateTaggedCount();
|
updateTaggedCount();
|
||||||
|
|
||||||
@ -480,7 +493,7 @@ async function moveAllTaggedFiles() {
|
|||||||
|
|
||||||
if (failed > 0) {
|
if (failed > 0) {
|
||||||
alert(`Moved ${successful} files. ${failed} files failed to move.`);
|
alert(`Moved ${successful} files. ${failed} files failed to move.`);
|
||||||
} else {
|
} else if (successful > 0) {
|
||||||
console.log(`[RENDERER] Successfully moved all ${successful} files`);
|
console.log(`[RENDERER] Successfully moved all ${successful} files`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -504,7 +517,7 @@ function displayCreatedFolder(folderName) {
|
|||||||
folderItem.className = 'folder-item';
|
folderItem.className = 'folder-item';
|
||||||
folderItem.setAttribute('data-folder-name', folderName);
|
folderItem.setAttribute('data-folder-name', folderName);
|
||||||
|
|
||||||
const displayName = folderName === 'extra' ? 'Extras' : 'Commentary';
|
const displayName = folderName === 'extra' ? 'extras' : 'commentary';
|
||||||
const folderIcon = folderName === 'extra' ? '📁' : '💬';
|
const folderIcon = folderName === 'extra' ? '📁' : '💬';
|
||||||
const folderColor = folderName === 'extra' ? '#FFD700' : '#17a2b8';
|
const folderColor = folderName === 'extra' ? '#FFD700' : '#17a2b8';
|
||||||
|
|
||||||
@ -692,7 +705,7 @@ function displaySearchResults(results) {
|
|||||||
searchResultsEl.innerHTML = '';
|
searchResultsEl.innerHTML = '';
|
||||||
|
|
||||||
if (results.length === 0) {
|
if (results.length === 0) {
|
||||||
searchResultsEl.innerHTML = '<p>No shows found.</p>';
|
searchResultsEl.innerHTML = '<p style="color: #888; text-align: center; padding: 10px;">No shows found.</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -700,13 +713,15 @@ function displaySearchResults(results) {
|
|||||||
const resultItem = document.createElement('div');
|
const resultItem = document.createElement('div');
|
||||||
resultItem.className = 'search-result-item';
|
resultItem.className = 'search-result-item';
|
||||||
|
|
||||||
// Create a more detailed display
|
// Create a more detailed display with separate elements
|
||||||
let resultText = show.seriesName || show.name;
|
const showName = show.seriesName || show.name;
|
||||||
if (show.firstAired) {
|
const showYear = show.firstAired ? show.firstAired.split('-')[0] : '';
|
||||||
resultText += ` (${show.firstAired})`;
|
|
||||||
}
|
resultItem.innerHTML = `
|
||||||
|
<div class="show-name">${showName}</div>
|
||||||
|
${showYear ? `<div class="show-year">${showYear}</div>` : ''}
|
||||||
|
`;
|
||||||
|
|
||||||
resultItem.textContent = resultText;
|
|
||||||
resultItem.addEventListener('click', () => selectShow(show));
|
resultItem.addEventListener('click', () => selectShow(show));
|
||||||
searchResultsEl.appendChild(resultItem);
|
searchResultsEl.appendChild(resultItem);
|
||||||
});
|
});
|
||||||
@ -1137,28 +1152,12 @@ async function loadVideoPreview(filePath) {
|
|||||||
<strong>Path:</strong> ${filePath}
|
<strong>Path:</strong> ${filePath}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// For MKV files, we'll open in default player instead of trying to preview
|
// For MKV files, open directly in default player
|
||||||
const ext = filePath.toLowerCase().split('.').pop();
|
const ext = filePath.toLowerCase().split('.').pop();
|
||||||
if (ext === 'mkv') {
|
if (ext === 'mkv') {
|
||||||
// For MKV files, show message and provide option to open in default player
|
// Close the modal and open in default player directly
|
||||||
videoContainer.innerHTML = `
|
videoPreviewModal.style.display = 'none';
|
||||||
<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);
|
ipcRenderer.invoke('open-file-in-player', filePath);
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// For other formats, try to create a video player
|
// For other formats, try to create a video player
|
||||||
videoContainer.innerHTML = `
|
videoContainer.innerHTML = `
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user