CinePlex/js/m3u-generator.js

223 lines
11 KiB
JavaScript
Raw Normal View History

2025-07-26 18:53:03 +02:00
import { getServers } from './db.js';
import { fetchLibraries as fetchPlexLibraries, fetchLibraryContents as fetchPlexLibraryContents } from './plex.js';
import { fetchLibraryViews as fetchJellyfinLibraries, fetchItemsFromLibrary as fetchJellyfinLibraryContents } from './jellyfin.js';
2025-07-26 18:53:03 +02:00
import { showNotification, _ } from './utils.js';
document.addEventListener('DOMContentLoaded', () => {
const m3uServerSelect = document.getElementById('m3u-server-select');
const m3uLibrariesContainer = document.getElementById('m3u-libraries-container');
const m3uLibrariesLoader = document.getElementById('m3u-libraries-loader');
const m3uLibrariesStep = document.getElementById('m3u-libraries-step');
const m3uConfigPanel = document.querySelector('.m3u-config-panel');
const m3uInfoPanel = document.querySelector('.m3u-info-panel');
const downloadM3uBtn = document.getElementById('download-m3u-btn');
let isInitialized = false;
// GSAP animations
gsap.set([m3uConfigPanel, m3uInfoPanel], { autoAlpha: 0, y: 50 });
async function initializeM3uGenerator() {
if (isInitialized) return;
isInitialized = true;
gsap.to(m3uConfigPanel, { autoAlpha: 1, y: 0, duration: 0.8, ease: "power3.out" });
gsap.to(m3uInfoPanel, { autoAlpha: 1, y: 0, duration: 0.8, ease: "power3.out", delay: 0.2 });
await loadServers();
}
async function loadServers() {
2025-07-26 18:53:03 +02:00
try {
const servers = await getServers();
m3uServerSelect.innerHTML = `<option value="">${_('selectAServer')}</option>`;
servers.forEach(server => {
const option = document.createElement('option');
option.value = server.id;
option.textContent = server.name;
option.dataset.type = server.type;
2025-07-26 18:53:03 +02:00
m3uServerSelect.appendChild(option);
});
} catch (error) {
console.error('Error loading servers for M3U generator:', error);
}
}
m3uServerSelect.addEventListener('change', async () => {
const selectedOption = m3uServerSelect.options[m3uServerSelect.selectedIndex];
const serverId = selectedOption.value;
const serverType = selectedOption.dataset.type;
2025-07-26 18:53:03 +02:00
m3uLibrariesContainer.innerHTML = '';
downloadM3uBtn.disabled = true;
if (!serverId) {
gsap.to(m3uLibrariesStep, { autoAlpha: 0, height: 0, duration: 0.3, onComplete: () => m3uLibrariesStep.style.display = 'none' });
return;
}
m3uLibrariesStep.style.display = 'block';
gsap.fromTo(m3uLibrariesStep, { autoAlpha: 0, height: 0 }, { autoAlpha: 1, height: 'auto', duration: 0.5, ease: "power3.out" });
m3uLibrariesContainer.innerHTML = ''; // Clear previous libraries
m3uLibrariesLoader.style.display = 'block'; // Show loader
downloadM3uBtn.disabled = true;
try {
const servers = await getServers();
const server = servers.find(s => s.id == serverId);
if (!server) return;
let libraries = [];
if (serverType === 'plex') {
libraries = await fetchPlexLibraries(server.accessToken, server.publicUrl, server.localUrl);
} else if (serverType === 'jellyfin') {
const jellyfinLibraries = await fetchJellyfinLibraries(server.publicUrl, server.userId, server.accessToken);
if(jellyfinLibraries.success) {
libraries = jellyfinLibraries.views.map(lib => ({
key: lib.Id,
title: lib.Name,
type: lib.CollectionType === 'movies' ? 'movie' : (lib.CollectionType === 'tvshows' ? 'show' : 'music')
}));
}
}
2025-07-26 18:53:03 +02:00
const checkboxes = [];
libraries.forEach(library => {
if (library.type === 'movie' || library.type === 'show' || library.type === 'music') {
const checkbox = document.createElement('div');
checkbox.classList.add('form-check');
checkbox.innerHTML = `
<input class="form-check-input" type="checkbox" value="${library.key}" id="library-${library.key}" data-type="${library.type}">
<label class="form-check-label" for="library-${library.key}">
<i class="fas ${library.type === 'movie' ? 'fa-film' : (library.type === 'show' ? 'fa-tv' : 'fa-music')} me-2"></i>
${library.title}
</label>
`;
m3uLibrariesContainer.appendChild(checkbox);
checkboxes.push(checkbox);
// Make the entire form-check div clickable
checkbox.addEventListener('click', () => {
const input = checkbox.querySelector('.form-check-input');
input.checked = !input.checked;
// Update download button state
downloadM3uBtn.disabled = m3uLibrariesContainer.querySelectorAll('input:checked').length === 0;
});
}
});
gsap.from(checkboxes, { opacity: 0, y: 20, stagger: 0.05, duration: 0.3 });
downloadM3uBtn.disabled = m3uLibrariesContainer.querySelectorAll('input:checked').length === 0;
} catch (error) {
console.error('Error fetching libraries:', error);
showNotification('Error fetching libraries.', 'error');
} finally {
m3uLibrariesLoader.style.display = 'none'; // Hide loader
}
});
downloadM3uBtn.addEventListener('click', async () => {
const selectedOption = m3uServerSelect.options[m3uServerSelect.selectedIndex];
const serverId = selectedOption.value;
const serverType = selectedOption.dataset.type;
2025-07-26 18:53:03 +02:00
const selectedLibraries = Array.from(m3uLibrariesContainer.querySelectorAll('input:checked')).map(input => input.value);
if (!serverId || selectedLibraries.length === 0) {
showNotification('Please select a server and at least one library.', 'error');
return;
}
downloadM3uBtn.disabled = true;
downloadM3uBtn.innerHTML = `<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Generating...`;
try {
const servers = await getServers();
const server = servers.find(s => s.id == serverId);
if (!server) return;
let m3uContent = '#EXTM3U\n';
2025-07-27 08:47:55 +02:00
let hasErrors = false;
2025-07-26 18:53:03 +02:00
for (const libraryKey of selectedLibraries) {
2025-07-27 08:47:55 +02:00
try {
const libraryElement = m3uLibrariesContainer.querySelector(`#library-${libraryKey}`);
const libraryType = libraryElement.getAttribute('data-type');
const libraryTitle = libraryElement ? libraryElement.nextElementSibling.textContent.trim() : '';
let items = [];
if (serverType === 'plex') {
items = await fetchPlexLibraryContents(server.accessToken, server.publicUrl, server.localUrl, libraryKey, libraryType, 60000);
} else if (serverType === 'jellyfin') {
const jellyfinItems = await fetchJellyfinLibraryContents(server.publicUrl, server.userId, server.accessToken, { Id: libraryKey });
if(jellyfinItems.success) {
items = jellyfinItems.items.map(item => ({
title: item.title,
duration: item.duration ? item.duration / 10000 : -1,
seriesTitle: item.seriesTitle,
seasonNumber: item.seasonNumber,
thumb: item.thumb,
url: `${server.publicUrl}/Videos/${item.id}/stream?static=true&api_key=${server.accessToken}`
}));
}
}
2025-07-27 08:47:55 +02:00
items.forEach(item => {
const duration = item.duration ? Math.round(item.duration / 1000) : -1;
const groupTitle = item.seriesTitle && item.seasonNumber
? `group-title="${item.seriesTitle} - Season ${item.seasonNumber}"`
: `group-title="${libraryTitle}"`;
const tvgLogo = item.thumb ? `tvg-logo="${item.thumb}"` : '';
m3uContent += `#EXTINF:${duration} ${tvgLogo} ${groupTitle},${item.title}\n`;
m3uContent += `${item.url}\n`;
});
} catch (error) {
hasErrors = true;
console.error(`Error processing library ${libraryKey}:`, error);
showNotification(`Error processing library ${libraryKey}. Skipping.`, 'warning');
}
}
if (m3uContent.split('\n').length <= 2 && hasErrors) {
throw new Error("All selected libraries failed to process.");
2025-07-26 18:53:03 +02:00
}
const blob = new Blob([m3uContent], { type: 'audio/x-mpegurl;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'playlist.m3u';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
2025-07-27 08:47:55 +02:00
if (hasErrors) {
showNotification('M3U generated with some errors. Some libraries may be missing.', 'warning');
} else {
showNotification('M3U playlist downloaded successfully.', 'success');
}
2025-07-26 18:53:03 +02:00
} catch (error) {
console.error('Error generating M3U file:', error);
showNotification('Error generating M3U file.', 'error');
} finally {
downloadM3uBtn.disabled = false;
downloadM3uBtn.innerHTML = `<i class="fas fa-download"></i> __MSG_downloadM3u__`;
}
});
const navLink = document.getElementById('nav-m3u-generator');
if (navLink) {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.attributeName === 'class' && mutation.target.classList.contains('active')) {
initializeM3uGenerator();
}
});
});
observer.observe(navLink, { attributes: true });
// Initial check in case it's already active
if (navLink.classList.contains('active')) {
initializeM3uGenerator();
}
}
document.addEventListener('actualizacion', loadServers);
2025-07-26 18:53:03 +02:00
});