Add Begin Mapping feature with Jellyfin naming, fix tvdbid regex
This commit is contained in:
parent
f9aee85e4f
commit
14c1bac5e8
34
index.html
34
index.html
@ -365,6 +365,38 @@
|
|||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Begin Mapping Button */
|
||||||
|
.begin-mapping-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px 24px;
|
||||||
|
margin-top: 20px;
|
||||||
|
background: linear-gradient(135deg, #28a745, #20c997);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px rgba(40, 167, 69, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.begin-mapping-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(40, 167, 69, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.begin-mapping-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.begin-mapping-btn:disabled {
|
||||||
|
background: #555;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Floating Action Button */
|
/* Floating Action Button */
|
||||||
.floating-circle {
|
.floating-circle {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -454,6 +486,8 @@
|
|||||||
<p>Click "Select Directory" to start browsing media files.</p>
|
<p>Click "Select Directory" to start browsing media files.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button id="begin-mapping-btn" class="begin-mapping-btn">Begin Mapping</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
94
main.js
94
main.js
@ -275,6 +275,100 @@ ipcMain.handle('move-file-to-folder', async (event, { filePath, folderName }) =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// IPC handler for beginning the mapping process
|
||||||
|
ipcMain.handle('begin-mapping', async (event, { directory, files, tvdbId }) => {
|
||||||
|
writeLog(`[BEGIN-MAPPING] Starting mapping for ${files.length} files in ${directory}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Get season folder name (direct parent of files)
|
||||||
|
const seasonFolder = path.basename(directory);
|
||||||
|
|
||||||
|
// Get show folder (parent of season folder)
|
||||||
|
const showFolderPath = path.dirname(directory);
|
||||||
|
const showFolderName = path.basename(showFolderPath);
|
||||||
|
|
||||||
|
// Extract season number from folder name (e.g., "Season 01" -> 1, "S01" -> 1)
|
||||||
|
const seasonMatch = seasonFolder.match(/(?:season\s*|s)(\d+)/i);
|
||||||
|
const seasonNumber = seasonMatch ? parseInt(seasonMatch[1]) : 1;
|
||||||
|
|
||||||
|
writeLog(`[BEGIN-MAPPING] Show folder: ${showFolderName}, Season: ${seasonNumber}`);
|
||||||
|
|
||||||
|
// Extract clean show name (without tvdbid bracket if present)
|
||||||
|
// Matches [tvdbid-XXXXX] or [tvdbid-series-XXXXX] formats
|
||||||
|
const showName = showFolderName.replace(/\s*\[tvdbid-[^\]]+\]/, '').trim();
|
||||||
|
|
||||||
|
// Rename each file first (using original paths)
|
||||||
|
let successCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
const oldPath = file.filePath;
|
||||||
|
const ext = path.extname(oldPath);
|
||||||
|
|
||||||
|
// Format episode number with leading zero
|
||||||
|
const epNum = String(file.episodeNumber).padStart(2, '0');
|
||||||
|
const seasonNum = String(seasonNumber).padStart(2, '0');
|
||||||
|
|
||||||
|
// Build new filename: "ShowName S01E01 - 1080p.ext"
|
||||||
|
let newFileName = `${showName} S${seasonNum}E${epNum}`;
|
||||||
|
if (file.quality && file.quality !== 'N/A' && file.quality !== '-') {
|
||||||
|
newFileName += ` - ${file.quality}`;
|
||||||
|
}
|
||||||
|
newFileName += ext;
|
||||||
|
|
||||||
|
const newPath = path.join(path.dirname(oldPath), newFileName);
|
||||||
|
|
||||||
|
// Skip if file already has the correct name
|
||||||
|
if (oldPath === newPath) {
|
||||||
|
writeLog(`[BEGIN-MAPPING] File already mapped: ${path.basename(oldPath)}`);
|
||||||
|
successCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if target already exists
|
||||||
|
if (fs.existsSync(newPath)) {
|
||||||
|
writeLog(`[BEGIN-MAPPING] Target exists, skipping: ${newFileName}`);
|
||||||
|
errorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.renameSync(oldPath, newPath);
|
||||||
|
writeLog(`[BEGIN-MAPPING] Renamed: ${path.basename(oldPath)} -> ${newFileName}`);
|
||||||
|
successCount++;
|
||||||
|
} catch (fileErr) {
|
||||||
|
writeLog(`[BEGIN-MAPPING] Error renaming file: ${fileErr.message}`);
|
||||||
|
errorCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now rename show folder at the end (if needed)
|
||||||
|
let newDirectory = directory;
|
||||||
|
if (tvdbId && !showFolderName.includes('[tvdbid-')) {
|
||||||
|
const newShowFolderName = `${showFolderName} [tvdbid-${tvdbId}]`;
|
||||||
|
const newShowFolderPath = path.join(path.dirname(showFolderPath), newShowFolderName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.renameSync(showFolderPath, newShowFolderPath);
|
||||||
|
writeLog(`[BEGIN-MAPPING] Renamed show folder to: ${newShowFolderName}`);
|
||||||
|
newDirectory = path.join(newShowFolderPath, seasonFolder);
|
||||||
|
writeLog(`[BEGIN-MAPPING] New directory path: ${newDirectory}`);
|
||||||
|
} catch (renameErr) {
|
||||||
|
writeLog(`[BEGIN-MAPPING] Could not rename show folder: ${renameErr.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeLog(`[BEGIN-MAPPING] Complete. Success: ${successCount}, Errors: ${errorCount}`);
|
||||||
|
return { success: true, renamed: successCount, errors: errorCount, newDirectory: newDirectory };
|
||||||
|
} catch (error) {
|
||||||
|
writeLog(`[BEGIN-MAPPING] Critical error: ${error.message}`);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Function to test moving files (for debugging purposes)
|
// Function to test moving files (for debugging purposes)
|
||||||
function testMoveFunction() {
|
function testMoveFunction() {
|
||||||
// This is for testing the function directly from main process
|
// This is for testing the function directly from main process
|
||||||
|
|||||||
69
renderer.js
69
renderer.js
@ -27,6 +27,9 @@ searchInput.addEventListener('input', debounce(searchShows, 300));
|
|||||||
// Add click handler for the floating circle (play button) to move all tagged files
|
// Add click handler for the floating circle (play button) to move all tagged files
|
||||||
document.getElementById('tagged-circle').addEventListener('click', moveAllTaggedFiles);
|
document.getElementById('tagged-circle').addEventListener('click', moveAllTaggedFiles);
|
||||||
|
|
||||||
|
// Add click handler for Begin Mapping button
|
||||||
|
document.getElementById('begin-mapping-btn').addEventListener('click', beginMapping);
|
||||||
|
|
||||||
// Listen for scan progress updates from main process
|
// Listen for scan progress updates from main process
|
||||||
ipcRenderer.on('scan-progress', (event, { current, total, fileName }) => {
|
ipcRenderer.on('scan-progress', (event, { current, total, fileName }) => {
|
||||||
updateProgress(current, total, fileName);
|
updateProgress(current, total, fileName);
|
||||||
@ -631,6 +634,72 @@ async function moveAllTaggedFiles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to begin mapping files to Jellyfin naming convention
|
||||||
|
async function beginMapping() {
|
||||||
|
const btn = document.getElementById('begin-mapping-btn');
|
||||||
|
|
||||||
|
// Get all media file items (not folders)
|
||||||
|
const fileItems = document.querySelectorAll('.file-item:not(.folder-item)');
|
||||||
|
|
||||||
|
if (fileItems.length === 0) {
|
||||||
|
alert('No media files to map. Please select a directory first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentDirectory) {
|
||||||
|
alert('No directory selected.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable button during processing
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = 'Mapping...';
|
||||||
|
|
||||||
|
// Collect file data with episode numbers from UI order
|
||||||
|
const filesToMap = [];
|
||||||
|
fileItems.forEach((item, index) => {
|
||||||
|
const fileNameEl = item.querySelector('.file-name');
|
||||||
|
const qualityEl = item.querySelector('.file-quality');
|
||||||
|
const episodeNumEl = item.querySelector('.episode-number');
|
||||||
|
|
||||||
|
if (fileNameEl && fileNameEl.dataset.filePath) {
|
||||||
|
filesToMap.push({
|
||||||
|
filePath: fileNameEl.dataset.filePath,
|
||||||
|
quality: qualityEl ? qualityEl.textContent : '',
|
||||||
|
episodeNumber: episodeNumEl ? parseInt(episodeNumEl.textContent) : (index + 1)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get TVDB ID from current show if selected
|
||||||
|
const tvdbId = currentShow ? currentShow.id : null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await ipcRenderer.invoke('begin-mapping', {
|
||||||
|
directory: currentDirectory,
|
||||||
|
files: filesToMap,
|
||||||
|
tvdbId: tvdbId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
alert('Mapping Complete!');
|
||||||
|
// Update currentDirectory if it changed (show folder was renamed)
|
||||||
|
const newDir = result.newDirectory || currentDirectory;
|
||||||
|
currentDirectory = newDir;
|
||||||
|
// Refresh the file list to show new names
|
||||||
|
openDirectory(newDir);
|
||||||
|
} else {
|
||||||
|
alert('Mapping failed: ' + (result.error || 'Unknown error'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Mapping error:', error);
|
||||||
|
alert('Mapping failed: ' + error.message);
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = 'Begin Mapping';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Function to display created folders in UI
|
// Function to display created folders in UI
|
||||||
function displayCreatedFolder(folderName) {
|
function displayCreatedFolder(folderName) {
|
||||||
// Check if folder entry already exists
|
// Check if folder entry already exists
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user