Git Product home page Git Product logo

shpotify's Introduction

shpotify

shpotify is a simple Bash/Apple script to control Spotify from the command line on a Mac.

If you find this interesting, you should follow me on Twitter to learn about the other things I do.

Installation

Download and install the Spotify desktop application if you haven’t already.

With Homebrew

The easiest way to install shpotify is by using the Homebrew package manager:

brew install shpotify

Manual installation

If you don’t use Homebrew, you can install the script manually by following a few simple steps:

  1. Fetch a copy of this repository, either with git or download the zip archive.

  2. Navigate to the folder where you fetched the repository (unzip if needed) and make sure the file called spotify is executable:

    cd shpotify
    chmod +x spotify
    
  3. Copy the file spotify to a convenient location in your PATH, or set your PATH to include the folder where the file is located.

Connecting to Spotify’s API

shpotify needs to connect to Spotify’s API in order to find music by name. It is very likely you want this feature!

To get this to work, you first need to sign up (or into) Spotify’s developer site and create an Application. Once you’ve done so, you can find its Client ID and Client Secret values and enter them into your shpotify config file at ${HOME}/.shpotify.cfg.

Be sure to quote your values and don’t add any extra spaces. When done, it should look like the following (but with your own values):

CLIENT_ID="abc01de2fghijk345lmnop"
CLIENT_SECRET="qr6stu789vwxyz"

Usage

With shpotify you can control Spotify with the following commands:

spotify play                       Resumes playback where Spotify last left off.
spotify play <song name>           Finds a song by name and plays it.
spotify play album <album name>    Finds an album by name and plays it.
spotify play artist <artist name>  Finds an artist by name and plays it.
spotify play list <playlist name>  Finds a playlist by name and plays it.
spotify play uri <uri>             Play songs from specific uri.

spotify next                       Skips to the next song in a playlist.
spotify prev                       Returns to the previous song in a playlist.
spotify replay                     Replays the current track from the beginning.
spotify pos <time>                 Jump to a specific time (in seconds) in the current song.
spotify pause                      Pauses (or resumes) Spotify playback.
spotify stop                       Stops playback.
spotify quit                       Stops playback and quits Spotify.

spotify vol up                     Increases the volume by 10%.
spotify vol down                   Decreases the volume by 10%.
spotify vol <amount>               Sets the volume to an amount between 0 and 100.
spotify vol [show]                 Shows the current volume.

spotify status                     Shows the play status, including the current song details.
spotify status artist              Shows the currently playing artist.
spotify status album               Shows the currently playing album.
spotify status track               Shows the currently playing track.

spotify share                      Displays the current song's Spotify URL and URI.
spotify share url                  Displays the current song's Spotify URL and copies it to the clipboard.
spotify share uri                  Displays the current song's Spotify URI and copies it to the clipboard.

spotify toggle shuffle             Toggles shuffle playback mode.
spotify toggle repeat              Toggles repeat playback mode.

Authors and contributing

shpotify is primarily written and maintained by Harish Narayanan.

Since it’s an open source project, it contains numerous contributions from many helpful people, including:

  • Jorge Colindres
  • Thomas Pritchard
  • iLan Epstein
  • Gabriele Bonetti
  • Sean Heller
  • Eric Martin
  • Peter Fonseca

If you’re interested in contributing too, please consider addressing some of the issues people have previously reported and submitting a pull request. Thank you!

Copyright and license

Copyright (c) 2012–2024 Harish Narayanan.

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.

shpotify's People

Contributors

adrianruizmora avatar ajjahn avatar amar1729 avatar bfontaine avatar bplommer avatar colindresj avatar dkniffin avatar dmofot avatar ericmarkmartin avatar flxsource avatar fonsecapeter avatar gabrielebonetti avatar genevera avatar henrik242 avatar hnarayanan avatar ieuang avatar kievechua avatar lukeberry99 avatar oschrenk avatar prestonknopp avatar snipem avatar ubuntudroid avatar vszakats avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

shpotify's Issues

Random search seems to do the wrong thing?

@thepuzzlemaster: I am not sure that randomly picking one of the returning search results is the best behaviour since the returning results are already sorted in some way (perhaps popularity?) the first result seemed to work for me.

e.g.

search query          what i expect                       searchtrack behaviour               current behaviour
amazing               amazing by kanye west               amazing by kanye west               all you need is a spark by danny saucedo
blood on the leaves   blood on the leaves by kanye west   blood on the leaves by kanye west   only one by kanye west

Picked one multi-word and one single word song name as an example.

If you agree that this behaviour should be changed, do you want me to do it or should I?

Sub commands should show separate help messages when used incorrectly

Currently, spotify vol will give you a message if you use the command incorrectly:

$ spotify vol foo
Improper use of 'vol' command
The 'vol' command should be used as follows:
  vol up                       # Increases the volume by 10%.
  vol down                     # Decreases the volume by 10%.
  vol [amount]                 # Sets the volume to an amount between 0 and 100.
  vol                          # Shows the current Spotify volume.

I think it would be wise to make all commands conform to this, because otherwise things like this happen:

$ spotify toggle
$
$ spotify toggle foo
$

I suppose that for most commands, this isn't particularly helpful (status, next, etc) but for some current commands and especially in the future, I think it would be helpful as new commands are implemented.

Additionally, I think that sub commands should allow this help menu to be invoked with "help" parameter, eg:

$ spotify vol help
The 'vol' command should be used as follows:
  vol up                       # Increases the volume by 10%.
  vol down                     # Decreases the volume by 10%.
  vol [amount]                 # Sets the volume to an amount between 0 and 100.
  vol                          # Shows the current Spotify volume.

Maybe that "should" should be changed to "can", but at that point I'm just messing with semantics.

Of course, if we decide to go this route, spotify play poses an issue:

$ spotify play help
Searching tracks for: help
Playing (help Search) -> Spotify URI: spotify:track:55jAHaYaZtUImilMnroSna
EricMacbookPro:shpotify ericmarkmartin$ spotify status
Spotify is currently playing.
Artist: The Beatles
Album: Live At The Hollywood Bowl
Track: Help! - Live / Remastered
Position: 0:02 / 2:52

which totally skipped my mind. Unfortunately, this is a completely valid usage, so we'll have to think of a way to deal with that...

Of course, we could also implement a synonymous -h flag for sub commands.

Side note, and maybe I'm getting too ambitious (especially for 1.3.0) but just screwing around with the implementation of this sort of a feature is indicating to me that maybe it's time for the sub commands to separated out into their own files? I don't know.

Thoughts?

Pos without an argument should return current position

Currently executing spotify pos returns an error:

$ spotify pos
Adjusting Spotify play position.
52:52: syntax error: Expected expression but found end of script. (-2741)

Instead, I suggest that it should return the current position:

$ spotify pos
Position: 3:01/4:55

Would a PR with this change be accepted?

spotify play list not working

When searching for playlists, the API does not seem to find any playlists in Spotify wether user owned or not. For instance the command:

spotify play list daily lift

gives the following output:

Searching playlists for: daily lift
No results when searching for daily lift

Play songs by their lyrics

Adding feature "Search by Lyrics" & #play it instantly.
It could use lyric search engines to fetch song name. This is very useful since Spotify itself doesn't have this feature. And when we forget song name simply play it by the catchy lyric words we remember!
like :
spotify play lyric kiki do you love me --> Plays Drakes "In My Feelings"

Shpotify picks unlikely choice

Instead of picking the first song in spotify's search or the first playlist/artist that comes up, shpotify plays an unrelated song. While this song does have certain keywords, this isn't how it generally acts.
dadrock

Clean up code

The script is starting to look a bit all over the place after many people have started touching it. Make it somewhat consistent again. Especially:

  • Check and polish whitespace
  • Set https://api.spotify.com/v1/searchto a reusable variable
  • Rename SPTFY_URI to something nicer
  • Replace the numerous echo$bold$greens by a function cecho that does just that
  • Replace all "tell application \"Spotify\" to ..." with 'tell application "Spotify" to ...' -> Only works for the cases where there is no $variable in the command.

Return non-zero exit codes for invalid commands

To help with scripting, it'd be nice if shpotify returned a non-zero [exit code](collaboratively via the Slack messaging app.) gave when given invalid commands. Right now it returns an exit code of 0, with a Usage message.

Also, just FYI, this is for a project I'm working on called Maestro, a tool for controlling a Spotify player collaboratively via the Slack messaging app. I'm in the process of rewriting it to heavily rely on shpotify.

Using any command causes spotify to not automatically play next track

This is a very weird thing I keep running into. When I use a command spotify will respond, but the next song in a playlist won't automatically play. When I use the next command, it tells me spotify is paused. I have to open up my app and manually go to each song to play. Only doing a "clean" re-install of spotify fixes things. I have had to re-install a couple times. It's very strange.

I am on a mac

Times/Durations Incorrect

It looks like spotify info is outputting the wrong Seconds and Duration. In addition, the spotify status command has Position displayed in decimal format (mm.mm) rather than Minutes:Seconds (mm:ss). Here's an example output, song duration is actually 3:41 (3 minutes 41 seconds):

$ spotify info && spotify status

Artist:         Das EFX
Track:          They Want EFX
Album Artist:   Das EFX
Album:          Dead Serious
Seconds:        221400
Seconds played: 85.104
Duration:       3690min 0s
Now at:         1min 25s
Played Count:   0
Track Number:   3
Popularity:     44
Id:             spotify:track:3HCzj965m8Fz2E8wZkir6q
Spotify URL:    spotify:track:3HCzj965m8Fz2E8wZkir6q
Artwork:        missing value
Player:         playing
Volume:         43
Shuffle:        true
Repeating:      false
Spotify is currently playing.
Artist: Das EFX
Album: Dead Serious
Track: They Want EFX
Position: 1.42 / 3.69

spotify info issues: So Seconds should be 221.400 and not 221400. It's currently missing the decimal, although everything after the decimal can probably be dropped. Duration should be 3:41, which is the mm:ss. It's currently showing the decimal, but the decimal is missing - 3690min 0s - ((3 minutes x 60 seconds) + 41) = 221 seconds / 60 seconds = 3.683min.

spotify status issues: Position is currently displaying minutes in decimal 1.42 / 3.69, which is the Now at of 1min 25s in decimal ((1 minute x 60 seconds) + 25 seconds) = 85 / 60 seconds = 1.416 and the correct Duration shown above, 3.69.

I've never written any AppleScript before, but I'll take a look. My initial guess is the Seconds is actually miliseconds.

Differentiate restart from prev

I have found the fact that previous track actually restarts the song if you are a substantial amount through it to be an annoying feature amongst many audio control solutions, and while I often believe it to be a necessary evil (Mac laptops have only three audio buttons, the same with in-line headset controls), with a command line interface one should have the luxury of separate functions.

Fix play commands when Spotify.app is not previously running

Currently, none of the ./spotify play commands do the right thing if they were invoked before actually starting the Spotify application. Upon play, this behaviour should be changed to:

  • check if Spotify.app is running
  • if so, behave as it does now
  • if not, start Spotify.app and then behave as it does now

spotify play artist not working

Searching for artists doesn't seem to be working.

When I do
$ ./spotify.sh play artist metallica

It outputs:
Connecting to Spotify's API
Searching artists for: metallica
play uri: spotify:artist:2ye2Wgw4gimLv2eAKyk1NB
Playing (metallica Search) -> Spotify URI: spotify:artist:2ye2Wgw4gimLv2eAKyk1NB

but Spotify doesn't play. Searching and playing albums, tracks and playlists does work.

Add possibility to queue songs

I would be good to add the functionality of queueing songs doing something like:

shpotify queue song "ble"
shpotify queue album ""

as to be able to make yourself a short list of songs to hear at once, without it having to be a fixed album or pre-existing list.

There's already an open issue on spotify but it has a lot of support for them to add the functionality so we can expect it to happen soon.

apostrophes cause an error

shpotify is brilliant! Amazing work. Question:

$ spotify play wouldnt it be nice works, but if I use an apostrophe and do $ spotify play wouldn't it be nice it puts me in some kind of process that gives me a > and won't accept commands.

Library not loaded "libreadline"?

I'm not sure what happened, I probably updated something on my system and didn't realize it broke shpotify... I'm getting this error when I try to run it:

$ spotify
dyld: Library not loaded: /usr/local/opt/readline/lib/libreadline.6.dylib
  Referenced from: /usr/local/bin/bash
  Reason: image not found

When I look in /usr/local/opt/readline/lib I see libreadline, but it's libreadline.7.dylib (which I assume means I have version 7). I've got shpotify installed with brew.

Package does not list Spotify as a dependency

This might seem obvious, but nothing on the shpotify main page lists Spotify as a dependency. I think I'm probably not the only person to assume that shpotify was a command-line spotify client, rather than a command-line dispatcher for the official Spotify application. ("Interface" in the project description is ambiguous as to which meaning of the word it refers to)

In addition, once installed, the error messages are not really kind or user friendly:

aaron@mb:~$ spotify play The Boy in the Bubble
30:38: execution error: Can’t get application "Spotify". (-1728)
Searching tracks for: The Boy in the Bubble
Playing (The Boy in the Bubble Search) -> Spotify URI: spotify:track:00IrSynHsun7DpDrLkRIjM
30:40: syntax error: A identifier can’t go after this identifier. (-2740)

One would think that the error message would a) note that the issue is that Spotify is not installed, and b) not do the search once it fails to find the application.

Version 1.2

@hnarayanan I think with all of the recent improvements and features, it's time for a v1.2 release (inc. sending it out via Homebrew).

If we create a new release, could @bfontaine update the Homebrew stuff?

Collect the different repeated search-and-play code into a reusable function

To make things like the following look better:

                if [ "$2" = "album" ]; then
                    _args=${array[@]:2:$len};
                    Q=$_args;

                    cecho "Searching albums for: $Q";

                    SPOTIFY_PLAY_URI=$( \
                        curl -s -G $SPOTIFY_SEARCH_API --data-urlencode "q=$Q" -d "type=album&limit=1&offset=0" -H "Accept: application/json" \
                        | grep -E -o "spotify:album:[a-zA-Z0-9]+" -m 1 \
                    )

                elif [ "$2" = "artist" ]; then
                    _args=${array[@]:2:$len};
                    Q=$_args;

                    cecho "Searching artists for: $Q";

                    SPOTIFY_PLAY_URI=$( \
                        curl -s -G $SPOTIFY_SEARCH_API --data-urlencode "q=$Q" -d "type=artist&limit=1&offset=0" -H "Accept: application/json" \
                        | grep -E -o "spotify:artist:[a-zA-Z0-9]+" -m 1 \
                    )

osascript: command not found

spotify play The Boy in the Bubble
/home/matter/script/spotify: line 136: osascript: command not found
/home/matter/script/spotify: line 136: [: =: unary operator expected
Connecting to Spotify's API
Searching tracks for: The Boy in the Bubble
play uri: spotify:track:1buYMDRKWawlr7T7SnsJdu
Playing (The Boy in the Bubble Search) -> Spotify URI: spotify:track:1buYMDRKWawlr7T7SnsJdu
/home/matter/script/spotify: line 254: osascript: command not found

Version command

For the sake of debugging and compliance with CLI standards, a version command of some sort should be included in the shpotify API.

Possible ideas for the command syntax (multiple different syntaxes can be used):

  • spotify -V
  • spotify -v
  • spotify --version
  • spotify version

"spotify next" bug issue

It worked once for me. Now it will shift to the next song but not play it saying that "Spotify is currently paused". It is weird because it would be playing a song and when I enter Spotify next this bug pops up. Originally it worked but after I tried all the other commands it wouldn't work again. I tried restarting the terminal but it still does it for me.
screen shot 2017-09-05 at 12 26 10 pm

Artist Not Found

First noticed issue when a command that previously worked stopped working.
"spotify play artist moderat" now returns "no results when searching for moderat"
Issue persists for all searches. "spotify play artist drake" also returns that no results were found.

Searching artists for: drake
No results when searching for drake

toggle repeat does not handle "repeat single track" mode

Thanks for this package!!! Happened upon this:

Steps to reproduce

  • Use the spotify client to set repeat to single track mode (the repeat w/ a 1 badge)
  • Issue spotify toggle repeat command.
  • Regardless of command output, the spotify client's repeat mode is still set to single track, and will incorrectly report false

Expected Behaviors

  • toggle repeat when spotify is in a repeat single track mode, will set the repeat mode to false in the spotify client.
  • toggle repeat moves between the three statuses of false, repeat single track, and repeat playlist. This is just one possible implementation.
    • Other possible combinations of suggestions include: renaming toggle repeat to toggle repeat-queue would reflect the current behavior. Adding toggle track-repeat command.

Linux support

Love this little tool, use it at work all the time. At home I work on linux (Ubuntu 16.10).
Would be great to be able to use it at home too 😃

When attempted to run on Linux:

[0305/145225:ERROR:main_delegate.cc(752)] Could not load cef_extensions.pak
[0305/145225:ERROR:main_delegate.cc(752)] Could not load cef_extensions.pak

Spotify restarts when I attempt to run the spotify command

skip-forward / skip-backward functionality

It would be great to have commands to skip forward/backward some number of seconds, perhaps defaulting to 15 seconds like the forward/back controls in Apple Podcasts and other similar applications. Happy to submit a pull request if it would be accepted!

Running spotify command generates permissions related error output

Running any spotify command in my terminal (iTerm or Terminal) executes the command but also outputs the following permissions error:

osascript[62109:4956741] kCFURLVolumeIsAutomountedKey missing for file:///Volumes/casper/: Error Domain=NSCocoaErrorDomain Code=257 "The file “casper” couldn’t be opened because you don’t have permission to view it." UserInfo={NSURL=file:///Volumes/casper/, NSFilePath=/Volumes/casper, NSUnderlyingError=0x7fe0316026e0 {Error Domain=NSPOSIXErrorDomain Code=13 "Permission denied"}}

`spotify vol` either reads or sets volume incorrectly.

I observed the following behavior:

$ spotify vol
Current Spotify volume level is 100.
$ spotify vol 90
$ spotify vol
Current Spotify volume level is 89.

This behavior also occurs with spotify vol up and spotify vol down. Note that this behavior was not changed when #45 was merged which caused spotify vol [amt] to print a message that the volume was being changed.

Please also note that this behavior is erratic and doesn't always result in the volume being (or being read as) 1 less than the requested volume.

Version

Hello,

Would you mind versionning the script and tagging it on the git repo? You can start at, say, 0.1.0 or 1.0.0 then increment when you make some changes. Use git tags to get releases on GitHub.

I’d like to submit a PR to Homebrew to get a formula for shpotify but they disallow formulae without versions in the main repo.

Thanks!

choose playlist

Can we add the option to list and select a playlist to play?

Make it possible to play own playlists

spotify play list mylist is useless if I can't play my own lists.
I read somewhere that the chosen list gets picked at random. That's fine, but I would like shpotify to search my own lists first, and give priority to those.

(I must say that the random list picking have given me some wonderful song results, so I am not at all upset about this, but, you know, it would be nice to be able to play ones lists)

Spotify URI not working

The uri link for Final Countdown on Spotify is `spotify play uri spotify:track:3MrRksHupTVEQ7YbA0FsZK. It returns this for me:

No results when searching for uri spotify:track:79BtIFYdWgo9FZiotlcNks

Anyone else getting this?

Btw, awesome project. Dig it.

Vol up after 100 set it to 0

When I type "spotify vol up" and the vol in already to 100, Spotify set it to 10 and turn up it again from 10. I think it would be better if when I try to turn up the volume more then 100 a message advice me that I'm at the max.

Clean up output messages and make them consistent

Especially:

  • The output that's shown after play-by-search
  • The output that's shown when toggling playback features
  • The status option can show more things and it could be renamed to info. Think about whether share could be folded into it?

A "pause" command that doesn't toggle play/pause

I'd like a pause command that doesn't resume play if Spotify is already paused. So maybe spotify toggle for play/pause toggle and spotify pause that always pauses? Or maybe spotify force-pause so it doesn't interfere with the existing command?

playing song or playlist from uri

Awesome utility! Using brew had it working in less than a minute. I saw issue #26 and that is the functionality I was hoping to find.

spotify play list mylist

However, I was thinking I could work around this issue for the time being by manually copying the uri from my playlist and triggering its playback directly with the uri command. I've noticed Spotify gives you two kinds of uris depending how you copy them from their GUI. I have tried them both:

spotify play uri spotify:user:123785495:playlist:2k7wiwTEE0JlN39pvPlpUT and
spotify play uri https://open.spotify.com/user/123785495/playlist/1iJoqOMxUxP4IHCqMl2E1N

But neither works for me. I have also tried both style of uris to play a song directly (as opposed to a playlist) without success. Am I using the uri feature incorrectly?

Flag to disable colors

I'd like to do some scripting around this tool, including capturing the output. It'd be helpful if there was a flag that could be given like --no-colors to disable colors.

Support Spotify v1.0.1.1060

Shpotify worked like a charm with Spotify 0.9.15.27, but gives me execution errors after I updated Spotify to version v1.0.1 today.

I'm getting the following errors

$ ./spotify play
Playing Spotify.
30:34: execution error: The variable play is not defined. (-2753)
$ ./spotify pause
Pausing Spotify.
30:35: execution error: The variable pause is not defined. (-2753)

They probably renamed some actions in Spotify?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.