Skip welcome & menu and move to editor
Welcome to JS Bin
Load cached copy from
 
<!DOCTYPE html>
<html>
<head>
    <script src="//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'>
</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>
    
</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();
}
Output 300px

You can jump to the latest bin by adding /latest to your URL

Dismiss x
public
Bin info
oskarpro
0viewers