Skip to content

Instantly share code, notes, and snippets.

@oskarrough
Created August 23, 2014 15:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oskarrough/7430034e47daec4c7f73 to your computer and use it in GitHub Desktop.
Save oskarrough/7430034e47daec4c7f73 to your computer and use it in GitHub Desktop.
Youtube to Spotify YouTube to Spotify playlist converter // source http://jsbin.com/caben/28
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<meta name="description" content="YouTube to Spotify playlist converter" />
<meta charset="utf-8">
<title>Youtube to Spotify</title>
<link href='http://fonts.googleapis.com/css?family=Fira+Mono:400,700' rel='stylesheet' type='text/css'>
<style id="jsbin-css">
/* hsl(75, 53%, 53%) */
/* #00438a */
/* fea600 */
/* 141414 */
/* 908f8d */
*, *:after, *::before { box-sizing: border-box;}
html {
font-size: 16px;
line-height: 1.3;
font-family: 'Fira Mono', 'Helvetica neue', sans-serif;
background-color: hsl(0,0%, 90%);
color: #141414;
}
input, button {
font-family: inherit;
}
body {
margin: 0 1em;
}
h2 {
font-size: 16px;
font-weight: bold;
line-height: 1;
margin-bottom: 0.5em;
}
small {
font-size: 0.875em;
color: #666;
}
.Header {
background-color: #00438a;
margin: 0 -1em;
padding: 1em;
color: #fff;
border-bottom: 4px solid hsl(210, 100%, 22%);
}
.Bucket {
margin: 1em 0;
float: left;
width: 50%;
padding-right: 2em;
}
.Bucket ul {}
.Bucket li {
list-style: none;
font-size: 13px;
border-bottom: 1px solid #ccc;
padding: 0.3em 0 0.2em;
}
.Analyzed {
display: none;
width: auto;
float: none;
}
.Analyzed li {
display: none;
}
.Success {
display: none;
}
.Success li {
display: none;
}
.Failed {
display: none;
}
.Failed h2 {
color: red;
}
.Failed li {
display: none;
}
.Matches {
clear: both;
width: auto;
float: none;
}
/* ::-webkit-placeholder,
::-moz-placeholder, */
input,
button,
li {
font-size: 14px;
}
input,
button {
display: inline-block;
margin: 0;
padding: 0.5em;
border: 0;
background-color: #fff;
border-radius: 2px;
line-height: normal;
}
label {
/* display: none; */
/* margin-bottom: 0.3em; */
/* color: #dadada; */
/* font-size: 14px; */
}
input {
width: 100%;
max-width: 100%;
}
input:focus {
outline: 0;
}
button {
background-color: hsl(75, 53%, 53%);
color: #fff;
font-weight: bold;
padding-left: 0.6em;
padding-right: 0.6em;
text-align: center;
-webkit-font-smoothing: antialiased;
}
button:focus {
outline: 0;
}
.Playlist {
overflow: auto;
}
.Playlist-label {
position: relative;
float: left;
width: calc(100% - 5.3em);
}
.Playlist-input {
position: relative;
z-index: 1;
}
.Playlist-submit {
float: right;
min-width: 5.5em;
transition: background 200ms, border 200ms, transform 300ms ease-out;
transform: translateX(-120%);
}
.Playlist-submit.is-visible {
transform: translateX(0);
}
.is-loading .Playlist-submit {
background-color: hsl(60, 100%, 50%);
color: #000;
}
.Progress {
position: absolute;
z-index: 2;
top: 0;
background-color: black;
width: 0;
transition: width 400ms ease-out;
font-size: 14px;
border-radius: 2px;
line-height: 34px;
height: 34px;
text-align: right;
overflow: hidden;
}
.is-done .Progress {
width: 0 !important;
background-color: hsl(75, 53%, 53%);
}
.is-failed .Progress {
background-color: red;
}
.Progress-inner {
margin: 0.6em;
}
footer {
display: none;
clear: both;
font-size: 14px;
color: #666
}
footer a {
color: #666;
}
</style>
</head>
<body>
<header class="Header">
<p><strong>Convert a YouTube playlist to Spotify tracks</strong></p>
<form class="Playlist">
<label class="Playlist-label">
<input type="text" class="Playlist-input" placeholder="YouTube playlist ID" value="PL6AEA53CDF028371E" />
<div class="Progress">
<span class="Progress-inner">
<i class="Progress-analyzing">Analyzing</i>
<i class="Progress-value"></i>%
</span>
</div>
</label>
<button type="submit" class="Playlist-submit">Match</button>
</form>
</header>
<main>
<p class="js-error"></p>
<div class="Results">
<p><i class="js-status">Matching</i>
<i class="js-total"></i> <i>tracks from</i>
<strong><i class="js-title"></i></strong>
</p>
<div class="Bucket Analyzed">
<h2></h2>
</div>
<div class="Bucket Success">
<h2>Found <i class="js-matched"></i></h2>
</div>
<div class="Bucket Failed">
<h2><span class="js-failed"></span> not found</h2>
</div>
<div class="Bucket Matches">
<h2><i class="js-matched"></i> Matches</h2>
</div>
</div>
</main>
<footer>
<p>Made by <a href="http://oskarrough.com">Oskar Rough</a></p>
</footer>
<script id="jsbin-javascript">
// based on: http://kmturley.blogspot.de/2014/01/converting-youtube-playlists-to-spotify.html
// tunes: PL-tpclp_nA62tdgkqX1hcyKgH_DPp_RSi
// instrumentals: PL6AEA53CDF028371E
// https://gdata.youtube.com/feeds/api/playlists/PL07D6B14E58F50900?alt=jsonc&v=2&start-index=1&max-results=50
var start = 1,
index = 0,
matches = [],
apilimit = 20,
$analyzed = $('.Analyzed'),
$success = $('.Success'),
$failed = $('.Failed'),
$matches = $('.Matches'),
$form = $('.Playlist'),
$input = $('.Playlist-input'),
$submit = $('.Playlist-submit'),
$results = $('.Results').hide(),
$error = $('.js-error'),
$total = $('.js-total'),
$title = $('.js-title');
var model = {
title: '',
total: 0,
analyzed: 0,
matched: 0,
failed: 0,
progress: 0,
matchedPercentage: 0
};
// Toggle the button depending on the input
$input.on('focus keyup change', function (event) {
if (this.value !== '') {
showSubmit();
} else {
hideSubmit();
}
});
$form.on('submit', function (event) {
event.preventDefault();
index = 0; // why?
matches = []; // why?
loadPage();
});
// Load a YouTube playlist
function loadPage(id) {
before();
// Without argument we take the value of the form
if (!id) {
id = $input.val();
}
var url = 'https://gdata.youtube.com/feeds/api/playlists/' + id;
var urlAttributes = '?alt=jsonc&v=2&start-index=' + start + '&max-results=' + apilimit;
var request = $.get(url + urlAttributes, function (event) {
// console.log('success');
}).done(function(event) {
// console.log('second success');
// console.log(event);
model.total = event.data.totalItems;
setTotal(model.total);
model.title = event.data.title;
setTitle(model.title);
if (!event.data.items) {
console.log('no items');
} if (event.data.items) {
render(event.data);
} else {
console.log('STOP!');
onDone();
}
}).fail(function(event) {
console.log('error');
onFail();
}).always(function(event) {
// console.log('ended');
onEnd();
});
}
// note this is called several times while loading
// should actually only be called once
function before() {
console.log('before');
clearTexts();
$('.Progress-status').text('Analyzing');
$submit.text('Loading').attr('disabled', 'disabled');
$('body').removeClass('is-failed').removeClass('is-done').addClass('is-loading');
}
function after() {
console.log('after');
$submit.text('Match').removeAttr('disabled');
$('body').addClass('is-done').removeClass('is-loading');
updateCount();
}
function onFail() {
$('body').addClass('is-failed');
$('.Results').hide();
setError('Sorry, didnt find the playlist. Did you enter a correct YouTube playlist ID?');
after();
}
function onEnd() {
// console.log('onEnd');
}
function onDone() {
$('.Progress-status').text('Analyzed');
$results.show();
after();
}
function clearTexts() {
$('.js-error').text('');
}
function clearResults() {
$('.Bucket').find('li').remove();
}
function render(data) {
// get and set values
// data.title
// data.items.length
// data.author
// data.description
// data.thumbnail.sq
$results.show();
renderAnalyzed(data);
searchSpotify(data.items);
}
function renderAnalyzed(data) {
for (var i = 0; i < data.items.length; i++) {
$analyzed.append('<li>'+ data.items[i].video.title +'</li>');
updateCount();
}
}
// Search spotify
function searchSpotify(items) {
// Filter words that Spotify would otherwise choke on
var name = items[index].video.title || [];
name = filterName(name);
// Search for a matching track
$.get('http://ws.spotify.com/search/1/track.json?q=' + encodeURIComponent(name), function (e) {
// success
if (e.tracks && e.tracks[0]) {
//console.log('success: ', name, e);
$success.show().append('<li>'+ name +'</li>');
matches.push(e.tracks[0]);
// console.log(e.tracks[0]);
// e.tracks[0].name
// e.tracks[0].artists[0].name
$matches.append('<li>'+ e.tracks[0].href +'</li>');
// fail
} else {
//console.log('fail: ', name, e);
$failed.show().append('<li>'+ name +'</li>');
}
updateCount();
// recursive until there are no more items
if (index < items.length - 1) {
index += 1;
searchSpotify(items);
// Otherwise continue searching YouTube (50 results limit)
} else {
// console.log(matches);
start += apilimit;
index = 0;
loadPage();
}
});
}
function updateCount() {
model.analyzed = $analyzed.find('li').length;
model.matched = $success.find('li').length;
model.failed = $failed.find('li').length;
model.progress = Math.round(model.analyzed / model.total * 100);
model.matchedPercentage = Math.round(model.matched / model.analyzed * 100);
// animateValue('.js-analyzed', model.analyzed, 1000);
// animateValue('.js-matched', model.matched, 1000);
// animateValue('.js-failed', model.failed, 1000);
$('.js-analyzed').text(model.analyzed);
$('.js-matched').text(model.matched);
$('.js-failed').text(model.failed);
$('.Progress').width(model.progress + '%');
$('.Progress-value').text(model.progress + '%');
// animateValue('.Progress-value', progress, 2000);
}
function setError(msg) {
$error.text(msg);
}
function setTotal(msg) {
$total.text(msg);
}
function setTitle(msg) {
if ($title.text() === '') {
$title.text(msg + '.');
}
}
function filterName(name) {
name = name.match(/[\w']+/g)
.join(' ')
.replace(/official/ig, '')
.replace(/featuring/ig, '')
.replace(/feat/ig, '')
.replace(/video/ig, '')
.replace(/720p/ig, '')
.replace(/1080p/ig, '')
.replace(/version/ig, '')
.replace(/7''/ig, '');
return name;
}
function showSubmit() {
$submit.addClass('is-visible');
}
function hideSubmit() {
$submit.removeClass('is-visible');
}
function animateValue(id, end, duration) {
var obj = $(id)[0];
var start = $(obj).text();
var range = end - start;
// no timer shorter than 50ms (not really visible any way)
var minTimer = 50;
// calc step time to show all interediate values
var stepTime = Math.abs(Math.floor(duration / range));
// never go below minTimer
stepTime = Math.max(stepTime, minTimer);
// get current time and calculate desired end time
var startTime = new Date().getTime();
var endTime = startTime + duration;
var timer;
function run() {
var now = new Date().getTime();
var remaining = Math.max((endTime - now) / duration, 0);
var value = Math.round(end - (remaining * range));
obj.innerHTML = value;
if (value == end) {
clearInterval(timer);
}
}
timer = setInterval(run, stepTime);
run();
}
</script>
</body>
</html>
/* hsl(75, 53%, 53%) */
/* #00438a */
/* fea600 */
/* 141414 */
/* 908f8d */
*, *:after, *::before { box-sizing: border-box;}
html {
font-size: 16px;
line-height: 1.3;
font-family: 'Fira Mono', 'Helvetica neue', sans-serif;
background-color: hsl(0,0%, 90%);
color: #141414;
}
input, button {
font-family: inherit;
}
body {
margin: 0 1em;
}
h2 {
font-size: 16px;
font-weight: bold;
line-height: 1;
margin-bottom: 0.5em;
}
small {
font-size: 0.875em;
color: #666;
}
.Header {
background-color: #00438a;
margin: 0 -1em;
padding: 1em;
color: #fff;
border-bottom: 4px solid hsl(210, 100%, 22%);
}
.Bucket {
margin: 1em 0;
float: left;
width: 50%;
padding-right: 2em;
}
.Bucket ul {}
.Bucket li {
list-style: none;
font-size: 13px;
border-bottom: 1px solid #ccc;
padding: 0.3em 0 0.2em;
}
.Analyzed {
display: none;
width: auto;
float: none;
}
.Analyzed li {
display: none;
}
.Success {
display: none;
}
.Success li {
display: none;
}
.Failed {
display: none;
}
.Failed h2 {
color: red;
}
.Failed li {
display: none;
}
.Matches {
clear: both;
width: auto;
float: none;
}
/* ::-webkit-placeholder,
::-moz-placeholder, */
input,
button,
li {
font-size: 14px;
}
input,
button {
display: inline-block;
margin: 0;
padding: 0.5em;
border: 0;
background-color: #fff;
border-radius: 2px;
line-height: normal;
}
label {
/* display: none; */
/* margin-bottom: 0.3em; */
/* color: #dadada; */
/* font-size: 14px; */
}
input {
width: 100%;
max-width: 100%;
}
input:focus {
outline: 0;
}
button {
background-color: hsl(75, 53%, 53%);
color: #fff;
font-weight: bold;
padding-left: 0.6em;
padding-right: 0.6em;
text-align: center;
-webkit-font-smoothing: antialiased;
}
button:focus {
outline: 0;
}
.Playlist {
overflow: auto;
}
.Playlist-label {
position: relative;
float: left;
width: calc(100% - 5.3em);
}
.Playlist-input {
position: relative;
z-index: 1;
}
.Playlist-submit {
float: right;
min-width: 5.5em;
transition: background 200ms, border 200ms, transform 300ms ease-out;
transform: translateX(-120%);
}
.Playlist-submit.is-visible {
transform: translateX(0);
}
.is-loading .Playlist-submit {
background-color: hsl(60, 100%, 50%);
color: #000;
}
.Progress {
position: absolute;
z-index: 2;
top: 0;
background-color: black;
width: 0;
transition: width 400ms ease-out;
font-size: 14px;
border-radius: 2px;
line-height: 34px;
height: 34px;
text-align: right;
overflow: hidden;
}
.is-done .Progress {
width: 0 !important;
background-color: hsl(75, 53%, 53%);
}
.is-failed .Progress {
background-color: red;
}
.Progress-inner {
margin: 0.6em;
}
footer {
display: none;
clear: both;
font-size: 14px;
color: #666
}
footer a {
color: #666;
}
// based on: http://kmturley.blogspot.de/2014/01/converting-youtube-playlists-to-spotify.html
// tunes: PL-tpclp_nA62tdgkqX1hcyKgH_DPp_RSi
// instrumentals: PL6AEA53CDF028371E
// https://gdata.youtube.com/feeds/api/playlists/PL07D6B14E58F50900?alt=jsonc&v=2&start-index=1&max-results=50
var start = 1,
index = 0,
matches = [],
apilimit = 20,
$analyzed = $('.Analyzed'),
$success = $('.Success'),
$failed = $('.Failed'),
$matches = $('.Matches'),
$form = $('.Playlist'),
$input = $('.Playlist-input'),
$submit = $('.Playlist-submit'),
$results = $('.Results').hide(),
$error = $('.js-error'),
$total = $('.js-total'),
$title = $('.js-title');
var model = {
title: '',
total: 0,
analyzed: 0,
matched: 0,
failed: 0,
progress: 0,
matchedPercentage: 0
};
// Toggle the button depending on the input
$input.on('focus keyup change', function (event) {
if (this.value !== '') {
showSubmit();
} else {
hideSubmit();
}
});
$form.on('submit', function (event) {
event.preventDefault();
index = 0; // why?
matches = []; // why?
loadPage();
});
// Load a YouTube playlist
function loadPage(id) {
before();
// Without argument we take the value of the form
if (!id) {
id = $input.val();
}
var url = 'https://gdata.youtube.com/feeds/api/playlists/' + id;
var urlAttributes = '?alt=jsonc&v=2&start-index=' + start + '&max-results=' + apilimit;
var request = $.get(url + urlAttributes, function (event) {
// console.log('success');
}).done(function(event) {
// console.log('second success');
// console.log(event);
model.total = event.data.totalItems;
setTotal(model.total);
model.title = event.data.title;
setTitle(model.title);
if (!event.data.items) {
console.log('no items');
} if (event.data.items) {
render(event.data);
} else {
console.log('STOP!');
onDone();
}
}).fail(function(event) {
console.log('error');
onFail();
}).always(function(event) {
// console.log('ended');
onEnd();
});
}
// note this is called several times while loading
// should actually only be called once
function before() {
console.log('before');
clearTexts();
$('.Progress-status').text('Analyzing');
$submit.text('Loading').attr('disabled', 'disabled');
$('body').removeClass('is-failed').removeClass('is-done').addClass('is-loading');
}
function after() {
console.log('after');
$submit.text('Match').removeAttr('disabled');
$('body').addClass('is-done').removeClass('is-loading');
updateCount();
}
function onFail() {
$('body').addClass('is-failed');
$('.Results').hide();
setError('Sorry, didnt find the playlist. Did you enter a correct YouTube playlist ID?');
after();
}
function onEnd() {
// console.log('onEnd');
}
function onDone() {
$('.Progress-status').text('Analyzed');
$results.show();
after();
}
function clearTexts() {
$('.js-error').text('');
}
function clearResults() {
$('.Bucket').find('li').remove();
}
function render(data) {
// get and set values
// data.title
// data.items.length
// data.author
// data.description
// data.thumbnail.sq
$results.show();
renderAnalyzed(data);
searchSpotify(data.items);
}
function renderAnalyzed(data) {
for (var i = 0; i < data.items.length; i++) {
$analyzed.append('<li>'+ data.items[i].video.title +'</li>');
updateCount();
}
}
// Search spotify
function searchSpotify(items) {
// Filter words that Spotify would otherwise choke on
var name = items[index].video.title || [];
name = filterName(name);
// Search for a matching track
$.get('http://ws.spotify.com/search/1/track.json?q=' + encodeURIComponent(name), function (e) {
// success
if (e.tracks && e.tracks[0]) {
//console.log('success: ', name, e);
$success.show().append('<li>'+ name +'</li>');
matches.push(e.tracks[0]);
// console.log(e.tracks[0]);
// e.tracks[0].name
// e.tracks[0].artists[0].name
$matches.append('<li>'+ e.tracks[0].href +'</li>');
// fail
} else {
//console.log('fail: ', name, e);
$failed.show().append('<li>'+ name +'</li>');
}
updateCount();
// recursive until there are no more items
if (index < items.length - 1) {
index += 1;
searchSpotify(items);
// Otherwise continue searching YouTube (50 results limit)
} else {
// console.log(matches);
start += apilimit;
index = 0;
loadPage();
}
});
}
function updateCount() {
model.analyzed = $analyzed.find('li').length;
model.matched = $success.find('li').length;
model.failed = $failed.find('li').length;
model.progress = Math.round(model.analyzed / model.total * 100);
model.matchedPercentage = Math.round(model.matched / model.analyzed * 100);
// animateValue('.js-analyzed', model.analyzed, 1000);
// animateValue('.js-matched', model.matched, 1000);
// animateValue('.js-failed', model.failed, 1000);
$('.js-analyzed').text(model.analyzed);
$('.js-matched').text(model.matched);
$('.js-failed').text(model.failed);
$('.Progress').width(model.progress + '%');
$('.Progress-value').text(model.progress + '%');
// animateValue('.Progress-value', progress, 2000);
}
function setError(msg) {
$error.text(msg);
}
function setTotal(msg) {
$total.text(msg);
}
function setTitle(msg) {
if ($title.text() === '') {
$title.text(msg + '.');
}
}
function filterName(name) {
name = name.match(/[\w']+/g)
.join(' ')
.replace(/official/ig, '')
.replace(/featuring/ig, '')
.replace(/feat/ig, '')
.replace(/video/ig, '')
.replace(/720p/ig, '')
.replace(/1080p/ig, '')
.replace(/version/ig, '')
.replace(/7''/ig, '');
return name;
}
function showSubmit() {
$submit.addClass('is-visible');
}
function hideSubmit() {
$submit.removeClass('is-visible');
}
function animateValue(id, end, duration) {
var obj = $(id)[0];
var start = $(obj).text();
var range = end - start;
// no timer shorter than 50ms (not really visible any way)
var minTimer = 50;
// calc step time to show all interediate values
var stepTime = Math.abs(Math.floor(duration / range));
// never go below minTimer
stepTime = Math.max(stepTime, minTimer);
// get current time and calculate desired end time
var startTime = new Date().getTime();
var endTime = startTime + duration;
var timer;
function run() {
var now = new Date().getTime();
var remaining = Math.max((endTime - now) / duration, 0);
var value = Math.round(end - (remaining * range));
obj.innerHTML = value;
if (value == end) {
clearInterval(timer);
}
}
timer = setInterval(run, stepTime);
run();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment