Skip to content

Instantly share code, notes, and snippets.

@sagacity
Created November 13, 2012 16:49
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save sagacity/4066915 to your computer and use it in GitHub Desktop.
Save sagacity/4066915 to your computer and use it in GitHub Desktop.
M3U to Spotify tracks
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="4.5.10" targetFramework="net45" />
<package id="taglib" version="2.1.0.0" targetFramework="net45" />
</packages>
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web;
namespace SpotifyImporter
{
public class SongInfo
{
public string Artist { get; set; }
public string Title { get; set; }
public double Duration { get; set; }
public override string ToString()
{
return Artist + " - " + Title;
}
}
class Program
{
public static List<string> invalid = new List<string>();
public static HashSet<SongInfo> songs = new HashSet<SongInfo>();
static void Main(string[] args)
{
var lines = File.ReadAllLines("playlist.m3u");
foreach (var line in lines)
{
if (line.StartsWith("#")) continue;
if (String.IsNullOrWhiteSpace(line)) continue;
if (File.Exists(line))
{
using (var file = TagLib.File.Create(line))
{
var filename = Path.GetFileNameWithoutExtension(line);
var split = filename.Split('-');
var title = file.Tag.Title;
if (String.IsNullOrWhiteSpace(title))
{
title = null;
if (split.Length >= 2) title = split[1].Trim();
if (String.IsNullOrWhiteSpace(title))
{
invalid.Add(filename);
continue;
}
}
var artist = file.Tag.Performers.FirstOrDefault();
if (String.IsNullOrWhiteSpace(artist))
{
artist = file.Tag.AlbumArtists.FirstOrDefault();
if (String.IsNullOrWhiteSpace(artist))
{
artist = split[0].Trim();
if (String.IsNullOrWhiteSpace(artist))
{
invalid.Add(filename);
continue;
}
}
}
var song = new SongInfo
{
Artist = artist,
Title = title,
Duration = file.Properties.Duration.TotalSeconds
};
Console.WriteLine(song);
songs.Add(song);
}
}
}
PrintInvalid("Could not determine metadata for:");
Console.WriteLine();
invalid.Clear();
var output = new List<string>();
foreach (var song in songs)
{
var response = Get<Response>("search", "track", song.Artist + " " + song.Title);
var orderedTracks = response.Tracks.OrderBy(x => LevenshteinDistance.Compute(x.Artists.First().Name, song.Artist)).ThenBy(x => Math.Abs(x.Length - song.Duration));
var bestMatch = orderedTracks.FirstOrDefault();
if (bestMatch == null)
{
invalid.Add(song.ToString());
continue;
}
output.Add(bestMatch.Href);
System.Threading.Thread.Sleep(100); // limit API rate
}
PrintInvalid("Could not find tracks for:");
File.WriteAllLines("notfound.txt", invalid);
Console.WriteLine("Done! Generated output.txt");
File.WriteAllLines("output.txt", output);
Console.ReadLine();
}
public static void PrintInvalid(string msg)
{
if (invalid.Any())
{
Console.WriteLine();
Console.WriteLine(msg);
foreach (var file in invalid)
{
Console.WriteLine("- " + file);
}
}
}
public class Response
{
public class ResponseInfo
{
[JsonProperty("num_results")]
public int NumResults { get; set; }
public int Limit { get; set; }
public int Offset { get; set; }
public int Page { get; set; }
}
public class ArtistInfo
{
public string Name { get; set; }
}
public class TrackInfo
{
public string Name { get; set; }
public string Href { get; set; }
public double Length { get; set; }
public IEnumerable<ArtistInfo> Artists { get; set; }
}
public ResponseInfo Info { get; set; }
public IEnumerable<TrackInfo> Tracks { get; set; }
}
static T Get<T>(string service, string method, string parameters = null) where T : class
{
var url = String.Format("http://ws.spotify.com/{0}/1/{1}", service, method);
if (!String.IsNullOrWhiteSpace(parameters))
{
url += "?q=" + HttpUtility.UrlEncode(parameters);
}
string text = null;
bool done = false;
while (!done)
{
try
{
Console.WriteLine("Requesting: " + url);
var request = (HttpWebRequest)HttpWebRequest.Create(url);
request.Accept = "application/json";
var response = (HttpWebResponse)request.GetResponse();
using (var sr = new StreamReader(response.GetResponseStream()))
{
text = sr.ReadToEnd();
}
done = true;
}
catch (WebException ex)
{
Console.WriteLine("Exception: " + ex.Message);
Console.WriteLine("Retrying in 1 second...");
System.Threading.Thread.Sleep(1000);
}
}
if (String.IsNullOrEmpty(text)) throw new InvalidOperationException();
return JsonConvert.DeserializeObject<T>(text);
}
// http://www.dotnetperls.com/levenshtein
static class LevenshteinDistance
{
/// <summary>
/// Compute the distance between two strings.
/// </summary>
public static int Compute(string s, string t)
{
int n = s.Length;
int m = t.Length;
int[,] d = new int[n + 1, m + 1];
// Step 1
if (n == 0)
{
return m;
}
if (m == 0)
{
return n;
}
// Step 2
for (int i = 0; i <= n; d[i, 0] = i++)
{
}
for (int j = 0; j <= m; d[0, j] = j++)
{
}
// Step 3
for (int i = 1; i <= n; i++)
{
//Step 4
for (int j = 1; j <= m; j++)
{
// Step 5
int cost = (t[j - 1] == s[i - 1]) ? 0 : 1;
// Step 6
d[i, j] = Math.Min(
Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
d[i - 1, j - 1] + cost);
}
}
// Step 7
return d[n, m];
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment