Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Playlist Migrator | Move Spotify to YouTube</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .gradient-bg { | |
| background: linear-gradient(135deg, #1DB954 0%, #FF0000 100%); | |
| } | |
| .spotify-btn { | |
| background-color: #1DB954; | |
| transition: all 0.3s ease; | |
| } | |
| .spotify-btn:hover { | |
| background-color: #1ed760; | |
| transform: translateY(-2px); | |
| } | |
| .youtube-btn { | |
| background-color: #FF0000; | |
| transition: all 0.3s ease; | |
| } | |
| .youtube-btn:hover { | |
| background-color: #ff3333; | |
| transform: translateY(-2px); | |
| } | |
| .migrate-btn { | |
| background: linear-gradient(135deg, #1DB954 0%, #FF0000 100%); | |
| transition: all 0.3s ease; | |
| } | |
| .migrate-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 20px rgba(0,0,0,0.2); | |
| } | |
| .playlist-card { | |
| transition: all 0.3s ease; | |
| } | |
| .playlist-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 25px rgba(0,0,0,0.1); | |
| } | |
| .progress-bar { | |
| height: 6px; | |
| background-color: #e0e0e0; | |
| border-radius: 3px; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| border-radius: 3px; | |
| background: linear-gradient(90deg, #1DB954 0%, #FF0000 100%); | |
| transition: width 0.4s ease; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <!-- Header --> | |
| <header class="gradient-bg text-white shadow-lg"> | |
| <div class="container mx-auto px-4 py-6"> | |
| <div class="flex justify-between items-center"> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fab fa-spotify text-3xl"></i> | |
| <i class="fas fa-exchange-alt text-xl"></i> | |
| <i class="fab fa-youtube text-3xl"></i> | |
| <h1 class="text-2xl font-bold">Playlist Migrator</h1> | |
| </div> | |
| <button class="bg-white text-gray-800 px-4 py-2 rounded-full font-semibold hover:bg-gray-100 transition"> | |
| <i class="fas fa-question-circle mr-2"></i>Help | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="container mx-auto px-4 py-8"> | |
| <div class="max-w-4xl mx-auto"> | |
| <!-- Intro Section --> | |
| <section class="bg-white rounded-xl shadow-md p-6 mb-8"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-4">Migrate Your Spotify Playlists to YouTube</h2> | |
| <p class="text-gray-600 mb-6"> | |
| Easily transfer your favorite Spotify playlists to YouTube Music. Our migrator preserves your playlist | |
| names, descriptions, and tracks with the highest possible match accuracy. | |
| </p> | |
| <div class="flex flex-wrap gap-4"> | |
| <div class="flex-1 min-w-[200px] bg-green-50 p-4 rounded-lg"> | |
| <div class="flex items-center mb-2"> | |
| <i class="fab fa-spotify text-green-500 text-2xl mr-2"></i> | |
| <h3 class="font-semibold">Spotify Features</h3> | |
| </div> | |
| <ul class="text-sm text-gray-700 space-y-1"> | |
| <li><i class="fas fa-check text-green-500 mr-2"></i>Connect your Spotify account</li> | |
| <li><i class="fas fa-check text-green-500 mr-2"></i>View all your playlists</li> | |
| <li><i class="fas fa-check text-green-500 mr-2"></i>Select tracks to migrate</li> | |
| </ul> | |
| </div> | |
| <div class="flex-1 min-w-[200px] bg-red-50 p-4 rounded-lg"> | |
| <div class="flex items-center mb-2"> | |
| <i class="fab fa-youtube text-red-500 text-2xl mr-2"></i> | |
| <h3 class="font-semibold">YouTube Features</h3> | |
| </div> | |
| <ul class="text-sm text-gray-700 space-y-1"> | |
| <li><i class="fas fa-check text-red-500 mr-2"></i>Connect your YouTube account</li> | |
| <li><i class="fas fa-check text-red-500 mr-2"></i>Create new playlists</li> | |
| <li><i class="fas fa-check text-red-500 mr-2"></i>Add matched tracks</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Connection Section --> | |
| <section class="bg-white rounded-xl shadow-md p-6 mb-8"> | |
| <h2 class="text-xl font-bold text-gray-800 mb-6">Connect Your Accounts</h2> | |
| <div class="grid md:grid-cols-2 gap-6"> | |
| <!-- Spotify Connection --> | |
| <div class="border border-gray-200 rounded-lg p-4"> | |
| <div class="flex items-center mb-4"> | |
| <i class="fab fa-spotify text-green-500 text-3xl mr-3"></i> | |
| <div> | |
| <h3 class="font-semibold">Spotify</h3> | |
| <p class="text-sm text-gray-500">Connect to view your playlists</p> | |
| </div> | |
| </div> | |
| <div id="spotify-status" class="mb-4"> | |
| <div class="flex items-center text-sm text-gray-600"> | |
| <div class="w-3 h-3 rounded-full bg-gray-300 mr-2"></div> | |
| <span>Not connected</span> | |
| </div> | |
| </div> | |
| <button id="connect-spotify" class="spotify-btn text-white w-full py-3 rounded-lg font-semibold flex items-center justify-center"> | |
| <i class="fab fa-spotify mr-2"></i> Connect Spotify | |
| </button> | |
| </div> | |
| <!-- YouTube Connection --> | |
| <div class="border border-gray-200 rounded-lg p-4"> | |
| <div class="flex items-center mb-4"> | |
| <i class="fab fa-youtube text-red-500 text-3xl mr-3"></i> | |
| <div> | |
| <h3 class="font-semibold">YouTube</h3> | |
| <p class="text-sm text-gray-500">Connect to create playlists</p> | |
| </div> | |
| </div> | |
| <div id="youtube-status" class="mb-4"> | |
| <div class="flex items-center text-sm text-gray-600"> | |
| <div class="w-3 h-3 rounded-full bg-gray-300 mr-2"></div> | |
| <span>Not connected</span> | |
| </div> | |
| </div> | |
| <button id="connect-youtube" class="youtube-btn text-white w-full py-3 rounded-lg font-semibold flex items-center justify-center"> | |
| <i class="fab fa-youtube mr-2"></i> Connect YouTube | |
| </button> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Playlist Selection (Hidden Initially) --> | |
| <section id="playlist-section" class="hidden bg-white rounded-xl shadow-md p-6 mb-8"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-xl font-bold text-gray-800">Your Spotify Playlists</h2> | |
| <div class="relative"> | |
| <input type="text" placeholder="Search playlists..." class="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500"> | |
| <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> | |
| </div> | |
| </div> | |
| <div id="playlist-container" class="grid md:grid-cols-2 gap-4 mb-6"> | |
| <!-- Playlist cards will be inserted here by JavaScript --> | |
| </div> | |
| <div class="flex justify-between items-center"> | |
| <div class="text-sm text-gray-500"> | |
| <span id="selected-count">0</span> of <span id="total-count">0</span> playlists selected | |
| </div> | |
| <button id="select-all" class="text-green-600 font-medium hover:text-green-700"> | |
| <i class="fas fa-check-circle mr-1"></i> Select All | |
| </button> | |
| </div> | |
| </section> | |
| <!-- Migration Section (Hidden Initially) --> | |
| <section id="migration-section" class="hidden bg-white rounded-xl shadow-md p-6"> | |
| <h2 class="text-xl font-bold text-gray-800 mb-6">Ready to Migrate</h2> | |
| <div class="mb-6"> | |
| <div class="flex justify-between mb-1"> | |
| <span class="font-medium">Migration Progress</span> | |
| <span id="progress-percent" class="font-medium">0%</span> | |
| </div> | |
| <div class="progress-bar"> | |
| <div id="progress-fill" class="progress-fill" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <div id="migration-details" class="mb-6"> | |
| <div class="grid md:grid-cols-3 gap-4"> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <div class="text-gray-500 text-sm mb-1">Playlists</div> | |
| <div id="playlist-count" class="text-2xl font-bold">0</div> | |
| </div> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <div class="text-gray-500 text-sm mb-1">Tracks</div> | |
| <div id="track-count" class="text-2xl font-bold">0</div> | |
| </div> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <div class="text-gray-500 text-sm mb-1">Estimated Time</div> | |
| <div id="time-estimate" class="text-2xl font-bold">~2 min</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex flex-col sm:flex-row gap-4"> | |
| <button id="start-migration" class="migrate-btn text-white flex-1 py-3 rounded-lg font-bold flex items-center justify-center"> | |
| <i class="fas fa-exchange-alt mr-2"></i> Start Migration | |
| </button> | |
| <button id="save-for-later" class="border border-gray-300 flex-1 py-3 rounded-lg font-medium flex items-center justify-center hover:bg-gray-50"> | |
| <i class="far fa-save mr-2"></i> Save for Later | |
| </button> | |
| </div> | |
| </section> | |
| <!-- Migration Results (Hidden Initially) --> | |
| <section id="results-section" class="hidden bg-white rounded-xl shadow-md p-6 mt-8"> | |
| <div class="text-center"> | |
| <div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4"> | |
| <i class="fas fa-check text-green-500 text-2xl"></i> | |
| </div> | |
| <h2 class="text-xl font-bold text-gray-800 mb-2">Migration Complete!</h2> | |
| <p class="text-gray-600 mb-6">Your playlists have been successfully transferred to YouTube Music.</p> | |
| <div class="grid md:grid-cols-3 gap-4 mb-8"> | |
| <div class="bg-green-50 p-4 rounded-lg"> | |
| <div class="text-green-500 text-sm mb-1">Successful</div> | |
| <div id="success-count" class="text-2xl font-bold">12</div> | |
| </div> | |
| <div class="bg-yellow-50 p-4 rounded-lg"> | |
| <div class="text-yellow-500 text-sm mb-1">Partial Matches</div> | |
| <div id="partial-count" class="text-2xl font-bold">3</div> | |
| </div> | |
| <div class="bg-red-50 p-4 rounded-lg"> | |
| <div class="text-red-500 text-sm mb-1">Not Found</div> | |
| <div id="failed-count" class="text-2xl font-bold">1</div> | |
| </div> | |
| </div> | |
| <button id="view-playlists" class="bg-green-600 text-white px-6 py-3 rounded-lg font-semibold hover:bg-green-700 transition mb-4"> | |
| <i class="fab fa-youtube mr-2"></i> View on YouTube | |
| </button> | |
| <div class="text-sm text-gray-500"> | |
| <button class="text-blue-600 hover:underline mr-4"> | |
| <i class="fas fa-download mr-1"></i> Download Report | |
| </button> | |
| <button class="text-blue-600 hover:underline"> | |
| <i class="fas fa-redo mr-1"></i> Migrate Another | |
| </button> | |
| </div> | |
| </div> | |
| </section> | |
| </div> | |
| </main> | |
| <!-- Footer --> | |
| <footer class="bg-gray-800 text-white py-8"> | |
| <div class="container mx-auto px-4"> | |
| <div class="flex flex-col md:flex-row justify-between items-center"> | |
| <div class="mb-4 md:mb-0"> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fab fa-spotify text-2xl"></i> | |
| <i class="fas fa-exchange-alt text-lg"></i> | |
| <i class="fab fa-youtube text-2xl"></i> | |
| <span class="font-bold">Playlist Migrator</span> | |
| </div> | |
| <p class="text-gray-400 text-sm mt-2">The easiest way to move your music between platforms</p> | |
| </div> | |
| <div class="flex space-x-6"> | |
| <a href="#" class="hover:text-green-400 transition"><i class="fab fa-github text-xl"></i></a> | |
| <a href="#" class="hover:text-green-400 transition"><i class="fab fa-twitter text-xl"></i></a> | |
| <a href="#" class="hover:text-green-400 transition"><i class="fab fa-discord text-xl"></i></a> | |
| </div> | |
| </div> | |
| <div class="border-t border-gray-700 mt-6 pt-6 text-sm text-gray-400"> | |
| <div class="flex flex-col md:flex-row justify-between items-center"> | |
| <div class="mb-4 md:mb-0"> | |
| © 2023 Playlist Migrator. Not affiliated with Spotify or YouTube. | |
| </div> | |
| <div class="flex space-x-4"> | |
| <a href="#" class="hover:text-white transition">Privacy Policy</a> | |
| <a href="#" class="hover:text-white transition">Terms of Service</a> | |
| <a href="#" class="hover:text-white transition">Contact</a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </footer> | |
| <script> | |
| // Sample data for demonstration | |
| const samplePlaylists = [ | |
| { | |
| id: '1', | |
| name: 'Workout Mix', | |
| description: 'High energy tracks for my workouts', | |
| image: 'https://misc.scdn.co/liked-songs/liked-songs-64.png', | |
| tracks: 32, | |
| owner: 'You' | |
| }, | |
| { | |
| id: '2', | |
| name: 'Chill Vibes', | |
| description: 'Relaxing music for evenings', | |
| image: 'https://i.scdn.co/image/ab67706c0000bebbc0d4f8172c9d486d5c8769d8', | |
| tracks: 45, | |
| owner: 'You' | |
| }, | |
| { | |
| id: '3', | |
| name: 'Road Trip', | |
| description: 'Perfect for long drives', | |
| image: 'https://i.scdn.co/image/ab67706c0000bebbc0d4f8172c9d486d5c8769d8', | |
| tracks: 28, | |
| owner: 'You' | |
| }, | |
| { | |
| id: '4', | |
| name: 'Party Hits', | |
| description: 'All the latest party tracks', | |
| image: 'https://misc.scdn.co/liked-songs/liked-songs-64.png', | |
| tracks: 50, | |
| owner: 'You' | |
| } | |
| ]; | |
| // DOM Elements | |
| const connectSpotifyBtn = document.getElementById('connect-spotify'); | |
| const connectYoutubeBtn = document.getElementById('connect-youtube'); | |
| const spotifyStatus = document.getElementById('spotify-status'); | |
| const youtubeStatus = document.getElementById('youtube-status'); | |
| const playlistSection = document.getElementById('playlist-section'); | |
| const playlistContainer = document.getElementById('playlist-container'); | |
| const migrationSection = document.getElementById('migration-section'); | |
| const resultsSection = document.getElementById('results-section'); | |
| const progressFill = document.getElementById('progress-fill'); | |
| const progressPercent = document.getElementById('progress-percent'); | |
| const startMigrationBtn = document.getElementById('start-migration'); | |
| const selectAllBtn = document.getElementById('select-all'); | |
| const selectedCount = document.getElementById('selected-count'); | |
| const totalCount = document.getElementById('total-count'); | |
| const playlistCount = document.getElementById('playlist-count'); | |
| const trackCount = document.getElementById('track-count'); | |
| // State | |
| let spotifyConnected = false; | |
| let youtubeConnected = false; | |
| let selectedPlaylists = []; | |
| // Event Listeners | |
| connectSpotifyBtn.addEventListener('click', connectSpotify); | |
| connectYoutubeBtn.addEventListener('click', connectYouTube); | |
| selectAllBtn.addEventListener('click', toggleSelectAll); | |
| startMigrationBtn.addEventListener('click', startMigration); | |
| // Functions | |
| function connectSpotify() { | |
| // Simulate Spotify connection | |
| spotifyConnected = true; | |
| connectSpotifyBtn.innerHTML = '<i class="fas fa-check mr-2"></i> Connected'; | |
| connectSpotifyBtn.classList.remove('spotify-btn'); | |
| connectSpotifyBtn.classList.add('bg-gray-200', 'text-gray-800'); | |
| connectSpotifyBtn.disabled = true; | |
| spotifyStatus.innerHTML = ` | |
| <div class="flex items-center text-sm text-green-600"> | |
| <div class="w-3 h-3 rounded-full bg-green-500 mr-2"></div> | |
| <span>Connected as <span class="font-medium">user123</span></span> | |
| </div> | |
| `; | |
| // Load playlists after connection | |
| loadPlaylists(); | |
| checkConnections(); | |
| } | |
| function connectYouTube() { | |
| // Simulate YouTube connection | |
| youtubeConnected = true; | |
| connectYoutubeBtn.innerHTML = '<i class="fas fa-check mr-2"></i> Connected'; | |
| connectYoutubeBtn.classList.remove('youtube-btn'); | |
| connectYoutubeBtn.classList.add('bg-gray-200', 'text-gray-800'); | |
| connectYoutubeBtn.disabled = true; | |
| youtubeStatus.innerHTML = ` | |
| <div class="flex items-center text-sm text-green-600"> | |
| <div class="w-3 h-3 rounded-full bg-green-500 mr-2"></div> | |
| <span>Connected as <span class="font-medium">user123@gmail.com</span></span> | |
| </div> | |
| `; | |
| checkConnections(); | |
| } | |
| function checkConnections() { | |
| if (spotifyConnected && youtubeConnected) { | |
| // Both connected | |
| playlistSection.classList.remove('hidden'); | |
| } | |
| } | |
| function loadPlaylists() { | |
| playlistContainer.innerHTML = ''; | |
| totalCount.textContent = samplePlaylists.length; | |
| samplePlaylists.forEach(playlist => { | |
| const playlistCard = document.createElement('div'); | |
| playlistCard.className = 'playlist-card bg-white border border-gray-200 rounded-lg overflow-hidden hover:shadow-md cursor-pointer'; | |
| playlistCard.innerHTML = ` | |
| <div class="p-4 flex items-start"> | |
| <img src="${playlist.image}" alt="${playlist.name}" class="w-16 h-16 rounded mr-4"> | |
| <div class="flex-1"> | |
| <h3 class="font-semibold text-gray-800">${playlist.name}</h3> | |
| <p class="text-sm text-gray-500 mb-1">${playlist.description}</p> | |
| <div class="flex items-center text-xs text-gray-400"> | |
| <span>${playlist.tracks} tracks</span> | |
| <span class="mx-2">•</span> | |
| <span>${playlist.owner}</span> | |
| </div> | |
| </div> | |
| <div class="checkbox-container"> | |
| <input type="checkbox" id="playlist-${playlist.id}" class="hidden playlist-checkbox"> | |
| <label for="playlist-${playlist.id}" class="w-6 h-6 border-2 border-gray-300 rounded-full flex items-center justify-center cursor-pointer"> | |
| <i class="fas fa-check text-white text-xs"></i> | |
| </label> | |
| </div> | |
| </div> | |
| `; | |
| playlistContainer.appendChild(playlistCard); | |
| // Add event listener to checkbox | |
| const checkbox = playlistCard.querySelector('.playlist-checkbox'); | |
| checkbox.addEventListener('change', function() { | |
| updateSelectedPlaylists(this, playlist); | |
| }); | |
| // Add click event to the whole card | |
| playlistCard.addEventListener('click', function(e) { | |
| // Don't toggle if clicking on the checkbox | |
| if (!e.target.closest('.checkbox-container')) { | |
| const checkbox = this.querySelector('.playlist-checkbox'); | |
| checkbox.checked = !checkbox.checked; | |
| checkbox.dispatchEvent(new Event('change')); | |
| } | |
| }); | |
| }); | |
| } | |
| function updateSelectedPlaylists(checkbox, playlist) { | |
| const label = checkbox.nextElementSibling; | |
| if (checkbox.checked) { | |
| label.classList.add('bg-green-500', 'border-green-500'); | |
| selectedPlaylists.push(playlist); | |
| } else { | |
| label.classList.remove('bg-green-500', 'border-green-500'); | |
| selectedPlaylists = selectedPlaylists.filter(p => p.id !== playlist.id); | |
| } | |
| selectedCount.textContent = selectedPlaylists.length; | |
| // Show/hide migration section | |
| if (selectedPlaylists.length > 0) { | |
| migrationSection.classList.remove('hidden'); | |
| updateMigrationDetails(); | |
| } else { | |
| migrationSection.classList.add('hidden'); | |
| } | |
| } | |
| function toggleSelectAll() { | |
| const checkboxes = document.querySelectorAll('.playlist-checkbox'); | |
| const isAllSelected = selectedPlaylists.length === samplePlaylists.length; | |
| checkboxes.forEach(checkbox => { | |
| if (!isAllSelected) { | |
| checkbox.checked = true; | |
| } else { | |
| checkbox.checked = false; | |
| } | |
| checkbox.dispatchEvent(new Event('change')); | |
| }); | |
| selectAllBtn.innerHTML = isAllSelected ? | |
| '<i class="fas fa-check-circle mr-1"></i> Select All' : | |
| '<i class="fas fa-times-circle mr-1"></i> Deselect All'; | |
| } | |
| function updateMigrationDetails() { | |
| const totalTracks = selectedPlaylists.reduce((sum, playlist) => sum + playlist.tracks, 0); | |
| playlistCount.textContent = selectedPlaylists.length; | |
| trackCount.textContent = totalTracks; | |
| } | |
| function startMigration() { | |
| // Disable button and show loading state | |
| startMigrationBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Migrating...'; | |
| startMigrationBtn.disabled = true; | |
| // Simulate migration progress | |
| let progress = 0; | |
| const interval = setInterval(() => { | |
| progress += Math.random() * 10; | |
| if (progress >= 100) { | |
| progress = 100; | |
| clearInterval(interval); | |
| migrationComplete(); | |
| } | |
| progressFill.style.width = `${progress}%`; | |
| progressPercent.textContent = `${Math.round(progress)}%`; | |
| }, 500); | |
| } | |
| function migrationComplete() { | |
| // Hide migration section and show results | |
| migrationSection.classList.add('hidden'); | |
| resultsSection.classList.remove('hidden'); | |
| // Update results | |
| document.getElementById('success-count').textContent = selectedPlaylists.length; | |
| document.getElementById('partial-count').textContent = Math.floor(selectedPlaylists.length * 0.3); | |
| document.getElementById('failed-count').textContent = Math.floor(selectedPlaylists.length * 0.1); | |
| } | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=nock2/playlist-migrator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |