Full Code of jlund/spotify-export for AI

master 210183c5103c cached
20 files
12.5 KB
3.7k tokens
15 symbols
1 requests
Download .txt
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
Download .txt
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
Download .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.

Copied to clipboard!