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> |