Repository: jlund/spotify-export
Branch: master
Commit: 210183c5103c
Files: 20
Total size: 12.5 KB
Directory structure:
gitextract_gy971w70/
├── .gitignore
├── CHANGELOG.md
├── Gemfile
├── LICENSE
├── README.md
├── bin/
│ └── spotify-export.rb
├── lib/
│ ├── spotify-cache.rb
│ ├── spotify-playlist.rb
│ └── spotify-track.rb
└── spec/
├── spec_helper.rb
├── spotify-playlist_spec.rb
├── spotify-track_spec.rb
└── support/
├── by-track.txt
├── html-entities-track.txt
├── local-track.txt
├── multiple-artist-track.txt
├── multiple-tracks.txt
├── single-track.txt
├── song-link-track.txt
└── utf8-track.txt
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.gem
*.rbc
.bundle
.config
InstalledFiles
coverage
lib/bundler/man
pkg
playlists/
rdoc
spec/reports
spotify-cache.db
test/tmp
test/version_tmp
tmp
# YARD artifacts
.yardoc
_yardoc
doc/
================================================
FILE: CHANGELOG.md
================================================
#### 1.5
* Added support for playlists comprised of Song Links in addition to Spotify URIs in order to solve a common mistake.
* Updated gem versions.
* Retroactively added a 1.4 CHANGELOG entry.
#### 1.4
* Updated to the new Spotify API and response format (contributed by Stephen Fuller).
* Refactored tests for the latest version of RSpec and fixed a few that were broken.
* Deprecated support for Ruby 1.9.3.
* README cleanup and reformatting.
* Updated gem versions.
#### 1.3
* A lovely progress bar is now included.
* Spotify API errors are handled more gracefully:
* "502 Bad Gateway" responses will no longer cause the program to crash.
* Failed requests are automatically retried after five seconds.
#### 1.2
* Added support for playlists that contain Local Files (contributed by Christopher Nguyen).
* Changed the way that the SQLite database is handled:
* Your personal track cache is now ignored by git.
* For new users, the repository contains a database template file that spotify-export will copy to the proper location if it doesn't already exist.
* **Current users who would like to merge these changes will need to temporarily move their SQLite cache file elsewhere, checkout the original version, pull the latest commits, and then move their database back.** Git will otherwise complain about merge conflicts. I sincerely apologize for the hassle; I really should have done it this way from the very beginning (thanks to Christopher Nguyen for this insight as well).
#### 1.1
* Requests are now made using the [Spotify Web API](http://developer.spotify.com/technologies/web-api/):
* Spotify URIs have replaced HTTP Links. **Please see the updated instructions.**
* This also, unfortunately, will invalidate your existing cache. But it's worth it!
* Bandwidth usage has been drastically reduced:
* The JSON response for one of the test tracks is only 486 bytes, compared to 19,753 bytes for the HTML version of the same track.
* The 404 issue (where some tracks were coming back blank) is resolved!
* The JSON API does not return HTML entities, so that gem dependency has been removed.
* Tracks with multiple artists are now exported properly. Rap fans, I got you.
#### 1.0.1
* Fixed a bug where track names containing the word 'by' were not properly exported.
#### 1.0
* Complete rewrite.
* Added Gemfile for Bundler support.
* Added RSpec tests.
* Implemented caching of track information using SQLite for dramatically better performance. **Now you can regularly back up your constantly-growing Starred playlist, even if it contains hundreds and hundreds of tracks!**
#### 0.2
* Added support for album names.
#### 0.1
* Initial release.
================================================
FILE: Gemfile
================================================
source "https://rubygems.org"
gem 'activerecord'
gem 'rspec'
gem 'ruby-progressbar'
gem 'sqlite3'
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2012-2013 Joshua Lund
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
spotify-export
==============
Description
-----------
Let's convert a Spotify playlist into plain text!
1. Open Spotify and go to the playlist that you want to export.
2. Select the tracks that you want to export (Ctrl-A or Cmd-A to Select All).
3. Right-click on the selected tracks and choose "Copy Spotify URI" from the menu.
4. Go to the text editor of your choice and Paste.
5. Save the file.
6. Run `./bin/spotify-export.rb your-filename.txt`.
Running the command on the included `spec/support/multiple-tracks.txt` test file will produce the following output:
1. Illusions -- Shout Out Louds -- Optica (Bonus Track Version)
2. My Number -- Foals -- Holy Fire
3. Love to Get Used -- Matt Pond -- The Lives Inside The Lines In Your Hand
4. Clouds -- Rangleklods -- Beekeeper (incl. Home EP)
5. Kelly -- When Saints Go Machine -- Konkylie
Listening to the songs might be fun too.
Enjoy!
Features
--------
* Lookups are performed using the super-efficient [Spotify Web API](https://developer.spotify.com/web-api/).
* SQLite is used as a caching layer so that information about each track will only be requested once, which allows you to regularly back up large playlists.
Requirements
------------
* [Ruby](http://www.ruby-lang.org/en/) 2.1 or higher
* [Bundler](http://gembundler.com/)
* [ActiveRecord](https://github.com/rails/rails/tree/master/activerecord)
* [RSpec](http://rspec.info/)
* [Ruby/ProgressBar](https://github.com/jfelchner/ruby-progressbar)
* [SQLite3](https://github.com/luislavena/sqlite3-ruby) and a working [SQLite](http://www.sqlite.org/) binary
Setup
-----
* `bundle install`
Acknowledgments
---------------
This product uses a SPOTIFY API but is not endorsed, certified or otherwise approved in any way by Spotify. Spotify is the registered trade mark of the Spotify Group.
================================================
FILE: bin/spotify-export.rb
================================================
#!/usr/bin/env ruby
require 'bundler/setup'
require 'fileutils'
require 'ruby-progressbar'
require_relative '../lib/spotify-playlist'
# Copy the template SQLite file for new users, unless it
# already exists
unless File.exist?("#{ ROOT }/db/spotify-cache.db")
FileUtils.cp("#{ ROOT }/db/spotify-cache-template.db",
"#{ ROOT }/db/spotify-cache.db")
end
output = String.new
playlist = SpotifyPlaylist.new(ARGV.first)
progressbar = ProgressBar.create(format: "%t: %c/%C |%B|",
total: playlist.tracks.size)
playlist.tracks.each_with_index do |track, count|
# Sanity check
unless track.nil?
output << "#{count + 1}. #{ track.name } -- #{ track.artist } -- #{ track.album }\n"
progressbar.increment
end
end
puts output
================================================
FILE: lib/spotify-cache.rb
================================================
require 'active_record'
# So excited for __dir__ in Ruby 2.0!
ROOT = "#{ File.dirname(File.expand_path(__FILE__)) }/.."
class SpotifyCache < ActiveRecord::Base
establish_connection(
adapter: "sqlite3",
database: "#{ ROOT }/db/spotify-cache.db"
)
end
================================================
FILE: lib/spotify-playlist.rb
================================================
require_relative 'spotify-track'
class SpotifyPlaylist
class MissingFileError < StandardError; end
attr_reader :filename
def initialize(filename=nil)
raise MissingFileError, "Usage: spotify-export.rb <filename>" unless filename
raise MissingFileError, "That file does not exist" unless File.exist?(filename)
@filename = filename
end
def export_targets
@export_targets ||= File.read(filename).split
end
def tracks
export_targets.map{ |track_uri| SpotifyTrack.new(track_uri) }
end
end
================================================
FILE: lib/spotify-track.rb
================================================
require 'net/https'
require 'json'
require_relative 'spotify-cache'
class SpotifyTrack
attr_reader :local, :uri
def initialize(uri)
@local = uri.include? ':local:'
@uri = uri
end
def album
attributes[:album]
end
def artist
attributes[:artist]
end
def name
attributes[:name]
end
private
def attributes
@attributes ||= begin
cache = SpotifyCache.where(uri: uri).first
if cache.blank?
get_track_attributes
else
{ name: cache[:name], artist: cache[:artist], album: cache[:album] }
end
end
end
def cache_track(cache_name, cache_artist, cache_album)
SpotifyCache.create(uri: uri,
name: cache_name,
artist: cache_artist,
album: cache_album)
end
def format_artists(artists)
artist_list = []
artists.each do |artist|
artist_list << artist["name"]
end
artist_list.join(", ")
end
def get_track_attributes
if local
local_array = uri.split(':')
# The array should be length 6
# ["spotify", "local", "artist", "album", "song title", "duration"]
name = URI.decode(local_array[4].gsub('+', ' '))
album = URI.decode(local_array[3].gsub('+', ' '))
artist = URI.decode(local_array[2].gsub('+', ' '))
else
track_id = uri[/track(:|\/)(.+)/, 2]
target = URI.parse("https://api.spotify.com/v1/tracks/#{ track_id }")
http = Net::HTTP.new(target.host, target.port)
http.use_ssl = true
request = Net::HTTP::Get.new(target.request_uri)
begin
response = http.request(request)
json = JSON.parse(response.body)
rescue Errno::ECONNREFUSED, JSON::ParserError
puts "Spotify API error. Retrying in five seconds..."
sleep 5
retry
end
name = json["name"]
artist = format_artists( json["artists"] )
album = json["album"]["name"]
cache_track(name, artist, album) if response.code == "200"
end
{ name: name, artist: artist, album: album }
end
end
================================================
FILE: spec/spec_helper.rb
================================================
require 'rspec'
require_relative '../lib/spotify-playlist'
================================================
FILE: spec/spotify-playlist_spec.rb
================================================
require 'spec_helper'
describe SpotifyPlaylist do
let(:filename) { 'spec/support/single-track.txt' }
let(:playlist) { SpotifyPlaylist.new(filename) }
describe ".new" do
context "if no filename is provided" do
it "raises a MissingFileError" do
expect { SpotifyPlaylist.new }.to raise_error(SpotifyPlaylist::MissingFileError)
end
end
context "if the specified file does not exist" do
it "raises a MissingFileError" do
expect { SpotifyPlaylist.new('spec/support/asdf.txt') }.to raise_error(SpotifyPlaylist::MissingFileError)
end
end
end
describe "#tracks" do
it "returns an array" do
expect(playlist.tracks.class).to equal Array
end
it "contains SpotifyTrack objects" do
expect(playlist.tracks.first.class).to equal SpotifyTrack
end
end
end
================================================
FILE: spec/spotify-track_spec.rb
================================================
# encoding: utf-8
require 'spec_helper'
describe SpotifyTrack do
let(:filename) { 'spec/support/single-track.txt' }
let(:playlist) { SpotifyPlaylist.new(filename) }
let(:track) { playlist.tracks.first }
describe "#name" do
it "should return the track name" do
expect(track.name).to eq("Fineshrine")
end
it "properly handles UTF-8 characters" do
track = SpotifyPlaylist.new('spec/support/utf8-track.txt').tracks.first
expect(track.name).to eq("Gangnam Style (강남스타일)")
end
it "properly handles local files" do
track = SpotifyPlaylist.new('spec/support/local-track.txt').tracks.first
expect(track.name).to eq("Life On Mars?")
end
it "properly handles Song Link URLs (e.g. non-Spotify-URI tracks)" do
track = SpotifyPlaylist.new('spec/support/song-link-track.txt').tracks.first
expect(track.name).to eq("8 (circle)")
end
it "properly retrieves track names that contain the word 'by'" do
track = SpotifyPlaylist.new('spec/support/by-track.txt').tracks.first
expect(track.name).to eq("Play by Play")
end
end
describe "#artist" do
it "should return the track artist" do
expect(track.artist).to eq("Purity Ring")
end
it "properly handles HTML entities" do
track = SpotifyPlaylist.new('spec/support/html-entities-track.txt').tracks.first
expect(track.artist).to eq("Niki & The Dove")
end
it "should return all track artists if there are several" do
track = SpotifyPlaylist.new('spec/support/multiple-artist-track.txt').tracks.first
expect(track.artist).to eq("Big Boi, B.o.B, Wavves")
end
end
describe "#album" do
it "should return the track album" do
expect(track.album).to eq("Shrines")
end
end
end
================================================
FILE: spec/support/by-track.txt
================================================
spotify:track:59ZIrPBaCOmLrweJOGHZOD
================================================
FILE: spec/support/html-entities-track.txt
================================================
spotify:track:3XJrdK0Okwfi7waIcUQkca
================================================
FILE: spec/support/local-track.txt
================================================
spotify:local:Seu+Jorge:Life+Aquatic+Studio+Sessions:Life+On+Mars%3f:209
================================================
FILE: spec/support/multiple-artist-track.txt
================================================
spotify:track:225gqtTcsE8cfFtfKNTVjt
================================================
FILE: spec/support/multiple-tracks.txt
================================================
spotify:track:2IWrMpJi3Om3MexLwcNUXf spotify:track:4c9WmjVlQMr0s1IjbYO52Z spotify:track:7rpBFDqUmFrMhiToJKORnY spotify:track:3bzYcthGKdW0gTL3N2YPBF spotify:track:1LZGkZfx4uAFeFZQ4uADc3
================================================
FILE: spec/support/single-track.txt
================================================
spotify:track:5KeyVNymqfqac1wLDseK8v
================================================
FILE: spec/support/song-link-track.txt
================================================
https://open.spotify.com/track/47IklCMgkgWvI4jpkdrop0
================================================
FILE: spec/support/utf8-track.txt
================================================
spotify:track:03UrZgTINDqvnUMbbIMhql
gitextract_gy971w70/
├── .gitignore
├── CHANGELOG.md
├── Gemfile
├── LICENSE
├── README.md
├── bin/
│ └── spotify-export.rb
├── lib/
│ ├── spotify-cache.rb
│ ├── spotify-playlist.rb
│ └── spotify-track.rb
└── spec/
├── spec_helper.rb
├── spotify-playlist_spec.rb
├── spotify-track_spec.rb
└── support/
├── by-track.txt
├── html-entities-track.txt
├── local-track.txt
├── multiple-artist-track.txt
├── multiple-tracks.txt
├── single-track.txt
├── song-link-track.txt
└── utf8-track.txt
SYMBOL INDEX (15 symbols across 3 files)
FILE: lib/spotify-cache.rb
class SpotifyCache (line 6) | class SpotifyCache < ActiveRecord::Base
FILE: lib/spotify-playlist.rb
class SpotifyPlaylist (line 3) | class SpotifyPlaylist
class MissingFileError (line 4) | class MissingFileError < StandardError; end
method initialize (line 8) | def initialize(filename=nil)
method export_targets (line 15) | def export_targets
method tracks (line 19) | def tracks
FILE: lib/spotify-track.rb
class SpotifyTrack (line 5) | class SpotifyTrack
method initialize (line 8) | def initialize(uri)
method album (line 13) | def album
method artist (line 17) | def artist
method name (line 21) | def name
method attributes (line 27) | def attributes
method cache_track (line 39) | def cache_track(cache_name, cache_artist, cache_album)
method format_artists (line 46) | def format_artists(artists)
method get_track_attributes (line 56) | def get_track_attributes
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (14K chars).
[
{
"path": ".gitignore",
"chars": 187,
"preview": "*.gem\n*.rbc\n.bundle\n.config\nInstalledFiles\ncoverage\nlib/bundler/man\npkg\nplaylists/\nrdoc\nspec/reports\nspotify-cache.db\nte"
},
{
"path": "CHANGELOG.md",
"chars": 2682,
"preview": "#### 1.5\n* Added support for playlists comprised of Song Links in addition to Spotify URIs in order to solve a common mi"
},
{
"path": "Gemfile",
"chars": 99,
"preview": "source \"https://rubygems.org\"\n\ngem 'activerecord'\ngem 'rspec'\ngem 'ruby-progressbar'\ngem 'sqlite3'\n"
},
{
"path": "LICENSE",
"chars": 1083,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2012-2013 Joshua Lund\n\nPermission is hereby granted, free of charge, to any person "
},
{
"path": "README.md",
"chars": 1835,
"preview": "spotify-export\n==============\n\nDescription\n-----------\nLet's convert a Spotify playlist into plain text!\n\n1. Open Spotif"
},
{
"path": "bin/spotify-export.rb",
"chars": 790,
"preview": "#!/usr/bin/env ruby\n\nrequire 'bundler/setup'\nrequire 'fileutils'\nrequire 'ruby-progressbar'\nrequire_relative '../lib/spo"
},
{
"path": "lib/spotify-cache.rb",
"chars": 264,
"preview": "require 'active_record'\n\n# So excited for __dir__ in Ruby 2.0!\nROOT = \"#{ File.dirname(File.expand_path(__FILE__)) }/..\""
},
{
"path": "lib/spotify-playlist.rb",
"chars": 525,
"preview": "require_relative 'spotify-track'\n\nclass SpotifyPlaylist\n class MissingFileError < StandardError; end\n\n attr_reader :fi"
},
{
"path": "lib/spotify-track.rb",
"chars": 2113,
"preview": "require 'net/https'\nrequire 'json'\nrequire_relative 'spotify-cache'\n\nclass SpotifyTrack\n attr_reader :local, :uri\n\n de"
},
{
"path": "spec/spec_helper.rb",
"chars": 59,
"preview": "require 'rspec'\nrequire_relative '../lib/spotify-playlist'\n"
},
{
"path": "spec/spotify-playlist_spec.rb",
"chars": 841,
"preview": "require 'spec_helper'\n\ndescribe SpotifyPlaylist do\n\n let(:filename) { 'spec/support/single-track.txt' }\n let(:playlist"
},
{
"path": "spec/spotify-track_spec.rb",
"chars": 1787,
"preview": "# encoding: utf-8\nrequire 'spec_helper'\n\ndescribe SpotifyTrack do\n\n let(:filename) { 'spec/support/single-track.txt' }\n"
},
{
"path": "spec/support/by-track.txt",
"chars": 37,
"preview": "spotify:track:59ZIrPBaCOmLrweJOGHZOD\n"
},
{
"path": "spec/support/html-entities-track.txt",
"chars": 37,
"preview": "spotify:track:3XJrdK0Okwfi7waIcUQkca\n"
},
{
"path": "spec/support/local-track.txt",
"chars": 73,
"preview": "spotify:local:Seu+Jorge:Life+Aquatic+Studio+Sessions:Life+On+Mars%3f:209\n"
},
{
"path": "spec/support/multiple-artist-track.txt",
"chars": 37,
"preview": "spotify:track:225gqtTcsE8cfFtfKNTVjt\n"
},
{
"path": "spec/support/multiple-tracks.txt",
"chars": 185,
"preview": "spotify:track:2IWrMpJi3Om3MexLwcNUXf spotify:track:4c9WmjVlQMr0s1IjbYO52Z spotify:track:7rpBFDqUmFrMhiToJKORnY spotify:t"
},
{
"path": "spec/support/single-track.txt",
"chars": 37,
"preview": "spotify:track:5KeyVNymqfqac1wLDseK8v\n"
},
{
"path": "spec/support/song-link-track.txt",
"chars": 54,
"preview": "https://open.spotify.com/track/47IklCMgkgWvI4jpkdrop0\n"
},
{
"path": "spec/support/utf8-track.txt",
"chars": 37,
"preview": "spotify:track:03UrZgTINDqvnUMbbIMhql\n"
}
]
About this extraction
This page contains the full source code of the jlund/spotify-export GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (12.5 KB), approximately 3.7k tokens, and a symbol index with 15 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.