attempts at move to extra
This commit is contained in:
parent
12035cc4d8
commit
d3aac3261d
92
index.html
92
index.html
@ -193,6 +193,98 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Add some additional styling to better align the views */
|
||||||
|
.file-list-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-info {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #e7f3ff;
|
||||||
|
border: 1px solid #b3d9ff;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-info strong {
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item-with-episode {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-match-indicator {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
font-size: 10px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item-with-episode:hover .episode-match-indicator {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tag styling */
|
||||||
|
.file-tags {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item:hover .file-tags {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: all 0.2s;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-icon:hover {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra-tag {
|
||||||
|
color: #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commentary-tag {
|
||||||
|
color: #17a2b8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script src="renderer.js"></script>
|
<script src="renderer.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
41
main.js
41
main.js
@ -454,6 +454,47 @@ ipcMain.handle('test-tvdb-api', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// IPC handler for moving file to extras folder
|
||||||
|
ipcMain.handle('move-to-extras', async (event, filePath) => {
|
||||||
|
try {
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Check if file exists
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
return { success: false, error: 'File does not exist' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the directory of the file
|
||||||
|
const fileDir = path.dirname(filePath);
|
||||||
|
const extrasDir = path.join(fileDir, 'extras');
|
||||||
|
|
||||||
|
// Create extras directory if it doesn't exist
|
||||||
|
if (!fs.existsSync(extrasDir)) {
|
||||||
|
fs.mkdirSync(extrasDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the filename
|
||||||
|
const fileName = path.basename(filePath);
|
||||||
|
|
||||||
|
// Construct the destination path
|
||||||
|
const destPath = path.join(extrasDir, fileName);
|
||||||
|
|
||||||
|
// Move the file
|
||||||
|
fs.renameSync(filePath, destPath);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'File moved to extras folder successfully',
|
||||||
|
sourcePath: filePath,
|
||||||
|
destinationPath: destPath
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error moving file to extras:', 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);
|
||||||
|
|||||||
215
renderer.js
215
renderer.js
@ -94,6 +94,13 @@ function displayFiles(files) {
|
|||||||
makeEditable(e.target);
|
makeEditable(e.target);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add context menu event listener to the file item itself
|
||||||
|
fileItem.addEventListener('contextmenu', function(e) {
|
||||||
|
console.log('File item contextmenu event triggered');
|
||||||
|
e.preventDefault();
|
||||||
|
// This will be handled by the global listener
|
||||||
|
});
|
||||||
|
|
||||||
fileListEl.appendChild(fileItem);
|
fileListEl.appendChild(fileItem);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -160,6 +167,106 @@ function makeEditable(element) {
|
|||||||
element.addEventListener('blur', saveEdit);
|
element.addEventListener('blur', saveEdit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add context menu functionality for files
|
||||||
|
function addFileContextMenu() {
|
||||||
|
// Add context menu listener to file items
|
||||||
|
document.addEventListener('contextmenu', function(e) {
|
||||||
|
console.log('Context menu event fired on:', e.target);
|
||||||
|
console.log('Target class list:', e.target.classList);
|
||||||
|
|
||||||
|
// Check if the right-click was on a file name
|
||||||
|
if (e.target.classList.contains('file-name')) {
|
||||||
|
console.log('Right-click detected on file name');
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const filePath = e.target.dataset.filePath;
|
||||||
|
const fileName = e.target.textContent.trim();
|
||||||
|
|
||||||
|
// Create context menu
|
||||||
|
const contextMenu = document.createElement('div');
|
||||||
|
contextMenu.id = 'file-context-menu';
|
||||||
|
contextMenu.style.position = 'absolute';
|
||||||
|
contextMenu.style.left = e.pageX + 'px';
|
||||||
|
contextMenu.style.top = e.pageY + 'px';
|
||||||
|
contextMenu.style.backgroundColor = 'white';
|
||||||
|
contextMenu.style.border = '1px solid #ddd';
|
||||||
|
contextMenu.style.borderRadius = '4px';
|
||||||
|
contextMenu.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
|
||||||
|
contextMenu.style.zIndex = '1000';
|
||||||
|
contextMenu.style.padding = '5px 0';
|
||||||
|
contextMenu.style.minWidth = '150px';
|
||||||
|
|
||||||
|
// Add "Move to Extras" option
|
||||||
|
const moveToExtras = document.createElement('div');
|
||||||
|
moveToExtras.textContent = 'Move to Extras';
|
||||||
|
moveToExtras.style.padding = '8px 16px';
|
||||||
|
moveToExtras.style.cursor = 'pointer';
|
||||||
|
moveToExtras.style.fontFamily = 'Arial, sans-serif';
|
||||||
|
moveToExtras.style.fontSize = '14px';
|
||||||
|
|
||||||
|
moveToExtras.addEventListener('mouseenter', function() {
|
||||||
|
this.style.backgroundColor = '#f0f0f0';
|
||||||
|
});
|
||||||
|
|
||||||
|
moveToExtras.addEventListener('mouseleave', function() {
|
||||||
|
this.style.backgroundColor = 'transparent';
|
||||||
|
});
|
||||||
|
|
||||||
|
moveToExtras.addEventListener('click', async function() {
|
||||||
|
try {
|
||||||
|
console.log('Attempting to move file to extras:', filePath);
|
||||||
|
const result = await ipcRenderer.invoke('move-to-extras', filePath);
|
||||||
|
if (result.success) {
|
||||||
|
console.log('File moved successfully');
|
||||||
|
// Remove the file from the UI
|
||||||
|
const fileItem = e.target.closest('.file-item');
|
||||||
|
if (fileItem) {
|
||||||
|
fileItem.remove();
|
||||||
|
}
|
||||||
|
alert(`File moved to extras: ${fileName}`);
|
||||||
|
} else {
|
||||||
|
console.error('Failed to move file:', result.error);
|
||||||
|
alert('Failed to move file to extras: ' + result.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error moving file:', error);
|
||||||
|
alert('Error moving file to extras: ' + error.message);
|
||||||
|
}
|
||||||
|
// Remove context menu
|
||||||
|
document.getElementById('file-context-menu')?.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
contextMenu.appendChild(moveToExtras);
|
||||||
|
|
||||||
|
// Add click outside to close menu
|
||||||
|
function closeMenu(e) {
|
||||||
|
if (!contextMenu.contains(e.target)) {
|
||||||
|
document.removeEventListener('click', closeMenu);
|
||||||
|
contextMenu.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('click', closeMenu);
|
||||||
|
|
||||||
|
document.body.appendChild(contextMenu);
|
||||||
|
|
||||||
|
// Also add a click outside listener to the document
|
||||||
|
document.addEventListener('mousedown', function closeOnOutsideClick(e) {
|
||||||
|
if (!contextMenu.contains(e.target)) {
|
||||||
|
document.removeEventListener('mousedown', closeOnOutsideClick);
|
||||||
|
contextMenu.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize context menu when the app loads
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('DOM loaded - adding context menu');
|
||||||
|
addFileContextMenu();
|
||||||
|
});
|
||||||
|
|
||||||
// Search shows function
|
// Search shows function
|
||||||
async function searchShows() {
|
async function searchShows() {
|
||||||
const query = searchInput.value.trim();
|
const query = searchInput.value.trim();
|
||||||
@ -435,6 +542,114 @@ function displayEpisodes(episodes) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
seasonsContainer.appendChild(episodesTable);
|
seasonsContainer.appendChild(episodesTable);
|
||||||
|
|
||||||
|
// Also update the main file list to show episode matching information
|
||||||
|
updateFileListWithEpisodeInfo(episodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced function to create a more integrated view
|
||||||
|
function createIntegratedEpisodeFileView(episodes) {
|
||||||
|
// This function would create a more integrated view showing both files and episodes
|
||||||
|
// For now, we're enhancing the existing functionality to better align the views
|
||||||
|
|
||||||
|
// Add a section that shows how files might align with episodes
|
||||||
|
const fileListContainer = document.getElementById('file-list');
|
||||||
|
|
||||||
|
// Create a section showing the relationship
|
||||||
|
const relationshipSection = document.createElement('div');
|
||||||
|
relationshipSection.style.marginTop = '15px';
|
||||||
|
relationshipSection.style.padding = '12px';
|
||||||
|
relationshipSection.style.backgroundColor = '#fff8e1';
|
||||||
|
relationshipSection.style.border = '1px solid #ffd54f';
|
||||||
|
relationshipSection.style.borderRadius = '6px';
|
||||||
|
relationshipSection.innerHTML = `
|
||||||
|
<h4 style="margin-top: 0; color: #ff9800;">File-Episode Relationship</h4>
|
||||||
|
<p style="font-size: 14px; margin: 5px 0;">
|
||||||
|
<strong>Matching Strategy:</strong> Files are matched to episodes based on naming patterns.
|
||||||
|
</p>
|
||||||
|
<p style="font-size: 14px; margin: 5px 0;">
|
||||||
|
<strong>Example:</strong> "Show.S01E01.Title.mp4" matches Episode 1 of Season 1.
|
||||||
|
</p>
|
||||||
|
<p style="font-size: 14px; margin: 5px 0;">
|
||||||
|
<strong>Status:</strong> ${currentFiles.length} files in directory, ${episodes.length} episodes available.
|
||||||
|
</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Insert this section after the episode info
|
||||||
|
const episodeInfo = fileListContainer.querySelector('.episode-info');
|
||||||
|
if (episodeInfo) {
|
||||||
|
fileListContainer.insertBefore(relationshipSection, episodeInfo.nextSibling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update file list to show episode matching information
|
||||||
|
function updateFileListWithEpisodeInfo(episodes) {
|
||||||
|
// When we have episodes, we want to show basic episode information
|
||||||
|
if (currentFiles && currentFiles.length > 0 && episodes && episodes.length > 0) {
|
||||||
|
console.log('Updating file list with episode info for', episodes.length, 'episodes');
|
||||||
|
|
||||||
|
// Create a simple note about episode availability
|
||||||
|
const fileListContainer = document.getElementById('file-list');
|
||||||
|
|
||||||
|
// Add a simple note about episodes
|
||||||
|
const episodeNote = document.createElement('div');
|
||||||
|
episodeNote.style.marginTop = '15px';
|
||||||
|
episodeNote.style.padding = '10px';
|
||||||
|
episodeNote.style.backgroundColor = '#e7f3ff';
|
||||||
|
episodeNote.style.border = '1px solid #b3d9ff';
|
||||||
|
episodeNote.style.borderRadius = '5px';
|
||||||
|
episodeNote.style.fontSize = '14px';
|
||||||
|
episodeNote.innerHTML = `
|
||||||
|
<strong>Episode Information:</strong>
|
||||||
|
${episodes.length} episodes available for this show.
|
||||||
|
<br><small>Episode details are displayed in the sidebar.</small>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add this note to the file list container
|
||||||
|
if (fileListContainer.firstChild) {
|
||||||
|
fileListContainer.insertBefore(episodeNote, fileListContainer.firstChild);
|
||||||
|
} else {
|
||||||
|
fileListContainer.appendChild(episodeNote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced file display that can show episode matching info
|
||||||
|
function displayFiles(files) {
|
||||||
|
fileListEl.innerHTML = '';
|
||||||
|
|
||||||
|
if (files.length === 0) {
|
||||||
|
fileListEl.innerHTML = '<p>No media files found in this directory.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
files.forEach(file => {
|
||||||
|
const fileItem = document.createElement('div');
|
||||||
|
fileItem.className = 'file-item';
|
||||||
|
|
||||||
|
// Use actual duration from file metadata
|
||||||
|
const duration = file.duration || '00:00';
|
||||||
|
const quality = file.quality || 'unknown';
|
||||||
|
const fps = file.fps || 'unknown';
|
||||||
|
|
||||||
|
// Add label for problematic files
|
||||||
|
const problemLabel = file.isProblematic ? '<span class="problematic-label">⚠️</span> ' : '';
|
||||||
|
|
||||||
|
fileItem.innerHTML = `
|
||||||
|
<div class="file-name" data-file-path="${file.path}">${problemLabel}${file.name}</div>
|
||||||
|
<div class="file-duration">${duration}</div>
|
||||||
|
<div class="file-quality">${quality}</div>
|
||||||
|
<div class="file-fps">${fps}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add click event to make file name editable
|
||||||
|
const fileNameElement = fileItem.querySelector('.file-name');
|
||||||
|
fileNameElement.addEventListener('click', function(e) {
|
||||||
|
makeEditable(e.target);
|
||||||
|
});
|
||||||
|
|
||||||
|
fileListEl.appendChild(fileItem);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debounce function for search input
|
// Debounce function for search input
|
||||||
|
|||||||
99
test-api.js
Normal file
99
test-api.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// Simple test script to verify TVDB API functionality
|
||||||
|
const axios = require('axios');
|
||||||
|
const dotenv = require('dotenv');
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
async function testTVDBAPI() {
|
||||||
|
try {
|
||||||
|
console.log('Testing TVDB API connectivity...');
|
||||||
|
|
||||||
|
// Get API key from environment
|
||||||
|
const apiKey = process.env.TVDB_API_KEY || process.env.API_KEY;
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error('TVDB_API_KEY not found in environment variables');
|
||||||
|
}
|
||||||
|
|
||||||
|
// First get a token
|
||||||
|
const loginResponse = await axios.post('https://api4.thetvdb.com/v4/login', {
|
||||||
|
apikey: apiKey
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Login successful:', loginResponse.data.status);
|
||||||
|
|
||||||
|
if (loginResponse.data && loginResponse.data.data && loginResponse.data.data.token) {
|
||||||
|
const token = loginResponse.data.data.token;
|
||||||
|
console.log('Got token:', token.substring(0, 50) + '...');
|
||||||
|
|
||||||
|
// Test search with the token
|
||||||
|
const searchResponse = await axios.get('https://api4.thetvdb.com/v4/search', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
query: 'IGPX'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Search response:', searchResponse.data);
|
||||||
|
|
||||||
|
if (searchResponse.data && searchResponse.data.data) {
|
||||||
|
console.log('Found shows:', searchResponse.data.data.length);
|
||||||
|
searchResponse.data.data.forEach(show => {
|
||||||
|
console.log(`- ${show.name} (ID: ${show.id})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test getting show details with extended info
|
||||||
|
const showId = '73011'; // IGPX Immortal Grand Prix ID
|
||||||
|
const showResponse = await axios.get(`https://api4.thetvdb.com/v4/series/${showId}/extended`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Show details response:', showResponse.data);
|
||||||
|
|
||||||
|
// Test getting episodes for the show
|
||||||
|
const episodesResponse = await axios.get(`https://api4.thetvdb.com/v4/series/${showId}/episodes`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
page: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Episodes response structure:', {
|
||||||
|
data: episodesResponse.data.data ? 'has data' : 'no data',
|
||||||
|
total: episodesResponse.data.data?.total,
|
||||||
|
episodes: episodesResponse.data.data?.episodes ? episodesResponse.data.data.episodes.length : 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (episodesResponse.data && episodesResponse.data.data && episodesResponse.data.data.episodes) {
|
||||||
|
console.log('First few episodes:');
|
||||||
|
episodesResponse.data.data.episodes.slice(0, 5).forEach(episode => {
|
||||||
|
console.log(`- Episode ${episode.number}: ${episode.name} (Season ${episode.seasonNumber})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API Test Error:', error.message);
|
||||||
|
console.error('Error details:', error.response?.data || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testTVDBAPI();
|
||||||
Loading…
x
Reference in New Issue
Block a user