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;
|
||||
}
|
||||
|
||||
/* 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-circle {
|
||||
position: fixed;
|
||||
@ -454,6 +486,8 @@
|
||||
<p>Click "Select Directory" to start browsing media files.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="begin-mapping-btn" class="begin-mapping-btn">Begin Mapping</button>
|
||||
</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 testMoveFunction() {
|
||||
// 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
|
||||
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
|
||||
ipcRenderer.on('scan-progress', (event, { 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 displayCreatedFolder(folderName) {
|
||||
// Check if folder entry already exists
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user