GithubHelp home page GithubHelp logo

sonos's Introduction

Sonos

Control Sonos speakers with Ruby.

Huge thanks to Rahim Sonawalla for making SoCo. This gem would not be possible without his work.

Code Climate

Note: Currently discovery is broken in Ruby 2.1

Installation

Add this line to your application's Gemfile:

gem 'sonos'

And then execute:

$ bundle

Or install it yourself as:

$ gem install sonos

Usage

I'm working on a CLI client. For now, we'll use IRB.

$ gem install sonos
$ irb
require 'rubygems'
require 'sonos'
system = Sonos::System.new # Auto-discovers your system
speaker = system.speakers.first

Now that we have a reference to the speaker, we can do all kinds of stuff.

speaker.pause # Pause whatever is playing
speaker.play  # Resumes the playlist
speaker.play 'http://assets.samsoff.es/music/Airports.mp3' # Stream!
speaker.now_playing
speaker.volume
speaker.volume = 70
speaker.volume -= 10
speaker.queue
speaker.add_to_queue 'http://assets.samsoff.es/music/Airports.mp3'
speaker.remove_from_queue(speaker.queue[:items].last[:queue_id])
speaker.save_queue 'Jams'
speaker.clear_queue
speaker.set_sleep_timer '00:13:00'

Or go into what the official control from Sonos, Inc. calls "Party Mode": put all speakers into a single group

system.party_mode
system.party_over

Topology

Sonos.discover finds the first speaker it can. We can get all of the Sonos devices (including Bridges, etc) by calling Sonos.system.devices. To get the groups, call Sonos.system.groups.

All of this is based off of the raw Sonos.system.topology.

Services

Currently there is support to queue items from the following services, provided the service accounts are set up:

  • Spotify
    • tracks
    • albums
    • playlists
    • top lists
    • starred
  • Rdio
    • tracks
    • albums

The way to add items differs per service at moment:

For Spotify only the 'Spotify URI' is required:

speaker.add_spotify_to_queue('2CwulIyrmEYwbUWzcEVIhR')

Whereas for Rdio more information needs to be provided:

speaker.add_rdio_to_queue({
  :track => '42083055',
  :album => '3944937',
  :username => 'RDIO_USERNAME_HERE' })

CLI

There is a very limited CLI right now. You can run sonos devices to get the IP of all of your devices.

You can also run sonos pause_all to pause all your Sonos groups.

To Do

General

  • Handle errors better
  • Handle line-in in now_playing
  • Detect fixed volume
  • Detect stereo pair
  • CLI client for everything
  • Nonblocking calls with Celluloid::IO
  • Unified method of adding items from music services

Features

  • Manipulating groups doesn't update System#groups
  • Pause all (there is no play all in the controller, we could loop through and do it though)
  • Party Mode
  • Line-in
  • Search music library
  • Browse music library
  • Skip to song in queue
  • Alarm clock
  • Pandora doesn't use the Queue. I bet things are all jacked up.
  • CONNECT (and possibly PLAY:5) line in settings
    • Source name
    • Level
    • Autoplay room
    • Autoplay include grouped rooms

Maybe

If we are implementing everything the official Sonos Controller does, here's some more stuff:

  • Set zone name and icon
  • Support for SUB
  • Support for DOCK
  • Support for CONNECT:AMP (not sure if this is any different from CONNECT)
  • Manage services
  • Date and time
  • Wireless channel
  • Audio compression
  • Automatically check for updates (not sure if this is a controller only preference)
  • Local music servers
  • Add component

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

sonos's People

Contributors

carl-stripe avatar constantinexvi avatar evenv avatar gotwalt avatar jasperla avatar jontg avatar madhok avatar masone avatar meltingice avatar pabloav avatar pfeffed avatar rtlong avatar sarahemm avatar scoop avatar soffes avatar tmaher avatar vidyapissaye avatar woodbusy 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sonos's Issues

in `queue': undefined method `browse

Getting this when I ask for a queue with more than one item in it.

/var/lib/gems/1.9.1/gems/sonos-0.3.5/lib/sonos/endpoint/content_directory.rb:24:in queue': undefined methodbrowse' for #Sonos::Device::Speaker:0x0000000104df98 (NoMethodError)

Here is the code (with line numbers added):
18 # Paginate
19 # TODO: This is ugly and inflexible
20 if starting_index == 0
21 start = starting_index
22 while hash[:items].count < hash[:total]
23 start += requested_count
24 hash[:items] += browse(start, requested_count)[:items]
25 end
26 end

Turn off Savon logging

I'm using the gem in an Alfred workflow and needed to turn off logging. I added a savon_config hash to the Sonos module and use it to create Savon clients and am setting { log: false }. If you want a PR, I'll send it. But you may way to implement it some other way.

Discovery is timing out

Does this gem work with modern versions of Ruby? I am unable to install it on Ruby 2.0.

$ rvm use 2.0
$ gem install sonos
ERROR:  Error installing sonos:
        rack requires Ruby version >= 2.2.2.

On Ruby 2.2, 2.3, and 2.4 it is timing out on Discovery:

2.3.1 :003 > Sonos::System.new
Timed out...
 => #<Sonos::System:0x007faf27d40b88 @topology=[], @groups=[], @devices=[]>

Any suggestions?

sonos party_mode fails to group 3 speakers

On OS 10.9 (ruby 2.0.0p481 (2014-05-08 revision 45883) [universal.x86_64-darwin13]) and 3 Sonos devices (Play 1, Playbar, and Play 3):

08:59 ~$ sonos devices
BRIDGE              10.0.1.100
Kitchen             10.0.1.119
Living Room         10.0.1.101
Dining Room         10.0.1.104

Party mode seems to always group 2 of them, but fails to group the third and gives an error:

08:59 ~$ sonos party_mode
/Library/Ruby/Gems/2.0.0/gems/savon-2.7.2/lib/savon/response.rb:85:in `raise_soap_and_http_errors!': (s:Client) UPnPError (Savon::SOAPFault)
    from /Library/Ruby/Gems/2.0.0/gems/savon-2.7.2/lib/savon/response.rb:14:in `initialize'
    from /Library/Ruby/Gems/2.0.0/gems/savon-2.7.2/lib/savon/operation.rb:64:in `new'
    from /Library/Ruby/Gems/2.0.0/gems/savon-2.7.2/lib/savon/operation.rb:64:in `create_response'
    from /Library/Ruby/Gems/2.0.0/gems/savon-2.7.2/lib/savon/operation.rb:55:in `call'
    from /Library/Ruby/Gems/2.0.0/gems/savon-2.7.2/lib/savon/client.rb:36:in `call'
    from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/lib/sonos/endpoint/a_v_transport.rb:262:in `send_transport_message'
    from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/lib/sonos/endpoint/a_v_transport.rb:252:in `set_av_transport_uri'
    from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/lib/sonos/endpoint/a_v_transport.rb:220:in `join'
    from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/lib/sonos/system.rb:43:in `block in party_mode'
    from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/lib/sonos/system.rb:41:in `each'
    from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/lib/sonos/system.rb:41:in `party_mode'
    from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/lib/sonos/cli.rb:32:in `party_mode'
    from /Library/Ruby/Gems/2.0.0/gems/thor-0.19.1/lib/thor/command.rb:27:in `run'
    from /Library/Ruby/Gems/2.0.0/gems/thor-0.19.1/lib/thor/invocation.rb:126:in `invoke_command'
    from /Library/Ruby/Gems/2.0.0/gems/thor-0.19.1/lib/thor.rb:359:in `dispatch'
    from /Library/Ruby/Gems/2.0.0/gems/thor-0.19.1/lib/thor/base.rb:440:in `start'
    from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/bin/sonos:7:in `<top (required)>'
    from /usr/bin/sonos:23:in `load'
    from /usr/bin/sonos:23:in `<main>'

CLI for everything

So discovering the IP of speakers is painful and specifying it every time would suck. I was thinking about storing the current topology in ~/.sonos. You could run sonos sync or something to update the cache. This should be run automatically if it fails to connect.

We could also cache your music library here to since it takes awhile to transfer it all.

Since we have speaker cached, we could just run sonos play and it could figure it out. If you have multiple zones, this gets complicated. Maybe we should store you last selected zone in the cache file. You could do sonos zone to see all of the zones and sonos zone bathroom to select the bathroom zone. Then all of commands will apply to that zone unless you use the --zone option or something.

I think instead of caching everything, we should just store the last selected zone and IP. If you change networks and those aren't found, just clear it out and require them to enter a new one. We could even get smart and store them by network SSID so it would remember your settings from your house to the office, etc.

NameError when retriving queue

Hi,
I am recieving the following error when trying to retrive the queue from my PLAY:5;

NameError: uninitialized constant Sonos::Endpoint::ContentDirectory::PORT
    from /opt/local/lib/ruby1.9/gems/1.9.1/gems/sonos-0.3.0/lib/sonos/endpoint/content_directory.rb:46:in 'block in parse_items'
    from /opt/local/lib/ruby1.9/gems/1.9.1/gems/nokogiri-1.5.9/lib/nokogiri/xml/node_set.rb:239:in 'block in each'
    from /opt/local/lib/ruby1.9/gems/1.9.1/gems/nokogiri-1.5.9/lib/nokogiri/xml/node_set.rb:238:in 'upto'
    from /opt/local/lib/ruby1.9/gems/1.9.1/gems/nokogiri-1.5.9/lib/nokogiri/xml/node_set.rb:238:in 'each'
    from /opt/local/lib/ruby1.9/gems/1.9.1/gems/sonos-0.3.0/lib/sonos/endpoint/content_directory.rb:40:in 'parse_items'
    from /opt/local/lib/ruby1.9/gems/1.9.1/gems/sonos-0.3.0/lib/sonos/endpoint/content_directory.rb:15:in 'queue'
    from (irb):10
    from /opt/local/bin/irb1.9:12:in '<main>'

Any ideas what went wrong?

Can't access now_playing from groups[0].master_speaker

Looks like the speakers returned in a group don't return the full object that calling system.speakers does. I haven't dug in to this much yet, but I can call system.speakers.first.now_playing, but system.groups.master_speaker.now_playing fails.

system.groups.first.master_speaker.now_playing
NoMethodError: undefined method `ip' for nil:NilClass
    from /Users/cdw/.rvm/gems/ruby-2.1.0@sonos/gems/sonos-0.3.4/lib/sonos/endpoint/a_v_transport.rb:131:in `transport_client'
    from /Users/cdw/.rvm/gems/ruby-2.1.0@sonos/gems/sonos-0.3.4/lib/sonos/endpoint/a_v_transport.rb:137:in `send_transport_message'
    from /Users/cdw/.rvm/gems/ruby-2.1.0@sonos/gems/sonos-0.3.4/lib/sonos/endpoint/a_v_transport.rb:10:in `now_playing'
    from (irb):68
    from /Users/cdw/.rvm/rubies/ruby-2.1.0/bin/irb:11:in `<main>'

The ip attribute exists on system.groups.master_speaker. I haven't done much digging in to this beyond that yet. If I find anything else I'll add it here, but it does seem like returning the same object any time a speaker is access would make sense.

Pretty much everything borked with latest version of Sonos?

Can't play links mp3s directly, nor queue songs to spotify. Even get an error on speaker.pause

Savon::SOAPFault: (s:Client) UPnPError from lib/ruby/gems/2.3.0/gems/savon-2.11.1/lib/savon/response.rb:85:in raise_soap_and_http_errors!'`

add_spotify_to_queue fails

I know its a pretty recent addition, but when I try and use the add_spotify_to_queue method I get

2.0.0p353 :006 > speaker.add_spotify_to_queue(id: '2CwulIyrmEYwbUWzcEVIhR')
Savon::SOAPFault: (s:Client) UPnPError
    from /Users/willstrinz/.rvm/gems/ruby-2.0.0-p353/gems/savon-2.3.3/lib/savon/response.rb:85:in `raise_soap_and_http_errors!'
    from /Users/willstrinz/.rvm/gems/ruby-2.0.0-p353/gems/savon-2.3.3/lib/savon/response.rb:14:in `initialize'
    from /Users/willstrinz/.rvm/gems/ruby-2.0.0-p353/gems/savon-2.3.3/lib/savon/operation.rb:64:in `new'
    from /Users/willstrinz/.rvm/gems/ruby-2.0.0-p353/gems/savon-2.3.3/lib/savon/operation.rb:64:in `create_response'
    from /Users/willstrinz/.rvm/gems/ruby-2.0.0-p353/gems/savon-2.3.3/lib/savon/operation.rb:55:in `call'
    from /Users/willstrinz/.rvm/gems/ruby-2.0.0-p353/gems/savon-2.3.3/lib/savon/client.rb:36:in `call'
    from /Users/willstrinz/.rvm/gems/ruby-2.0.0-p353/gems/sonos-0.3.5/lib/sonos/endpoint/a_v_transport.rb:197:in `send_transport_message'
    from /Users/willstrinz/.rvm/gems/ruby-2.0.0-p353/gems/sonos-0.3.5/lib/sonos/endpoint/a_v_transport.rb:108:in `add_to_queue'
    from /Users/willstrinz/.rvm/gems/ruby-2.0.0-p353/gems/sonos-0.3.5/lib/sonos/endpoint/a_v_transport.rb:156:in `add_spotify_to_queue'
    from (irb):6
    from /Users/willstrinz/.rvm/rubies/ruby-2.0.0-p353/bin/irb:12:in `<main>'

I can successfully add tracks using the old method (speaker.add_to_queue 'x-sonos-spotify:spotify%3atrack%3a60KbvwQPIv8soov3wzbySK?sid=9&amp;flags=0'), so I think my spotify connection is ok. I'll take a look and see if I can track down the error further.

SONOS PLAYBAR SetAVTransportURI fail

I used Sonos Ruby control program.
It works find and feel great.

This writing doesn’t matter your program’s issue.
Since you have expertise in control Sonos products, I need your help.

I’m trying to play DMS content on Sonos PLAYBAR on my Ruby Script.
When I execute SetAVTransportURI on PLAYBAR, PLAYBAR replys error code 714.
But other DMR can play the contents I specified, but PLAYBAR doesn’t.

I suspect the root cause in metadata.
How do I modifiy metadata to play DMS contents on PLAYBAR?
Do you have any advice?
Your help are very appreciated.

The following is the arguments data on SetAVTransportURI to PLAYBAR.

uri:'http://192.168.0.102:60400/getContent?id=6826'
metadata: '<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"
xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"
xmlns:dlna="urn:schemas-dlna-org:metadata-1-0/"
xmlns:arib="urn:schemas-arib-or-jp:elements-1-0/"
xmlns:av="urn:schemas-sony-com:av">
<item id="60001aaa;70001114;70001107;700010a7;70001004;6;0" parentID="70001114;70001107;700010a7;70001004;6;0" restricted="0" refID="60001aaa;5;0"><dc:title>MPEG-1-L3-44.1kHz-JS2ch-CBR-128kbps</dc:tit
le><upnp:class>object.item.audioItem.musicTrack</upnp:class><res protocolInfo="http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000" size="5339136" dura
tion="00:05:32" sampleFrequency="44100" bitrate="16000" bitsPerSample="16" nrAudioChannels="2">http://192.168.0.102:60400/getContent?id=6826</res><upnp:originalTrackNumber>0</upnp:originalTrackNumber>
<upnp:album>Joint Stereo</upnp:album><upnp:albumArtURI>http://192.168.0.102:60400/getTrackArt?id=6826</upnp:albumArtURI><upnp:albumArtURI dlna:profileID="JPEG_TN">http://192.168.0.102:60400/getAlbumIc
on?id=867</upnp:albumArtURI><pv:playcount xmlns:pv="http://www.pv.com/pvns/">0</pv:playcount></item></DIDL-Lite>'

Streaming spotify fails

So, based on speaker.now_playing, the format or spotify songs is like this:

x-sonos-spotify:spotify%3atrack%3a60KbvwQPIv8soov3wzbySK?sid=9&flags=0

Based on this, I started playing another piece and then did

speaker.play 'x-sonos-spotify:spotify%3atrack%3a60KbvwQPIv8soov3wzbySK?sid=9&flags=0'

expecting it to start playing the previous song, but that fails it seems:

I, [2013-03-16T01:22:43.467042 #47136]  INFO -- : SOAP response (status 500)
D, [2013-03-16T01:22:43.467137 #47136] DEBUG -- : <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><s:Fault><faultcode>s:Client</faultcode><faultstring>UPnPError</faultstring><detail><UPnPError xmlns="urn:schemas-upnp-org:control-1-0"><errorCode>402</errorCode></UPnPError></detail></s:Fault></s:Body></s:Envelope>
Savon::SOAPFault: (s:Client) UPnPError

Timed out after latest Sonos update with TruePlay

I just updated my Sonos system to the latest version with support for TruePlay on November 10, 2015.

Since that update I can no longer connect to Sonos, instead I get a "Timed out..." message.

Does anyone have the same experience?

I'm trying using irb:
require 'rubygems'
require 'sonos'
system = Sonos::System.new # Auto-discovers your system
speaker = system.speakers.first

BR200 not supported

FYI - BR200 is the Boost.

sonos devices
/Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/lib/sonos/device/base.rb:21:in detect': BR200 not supported (ArgumentError) from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/lib/sonos/topology_node.rb:18:indevice'
from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/lib/sonos/system.rb:76:in collect' from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/lib/sonos/system.rb:76:inrescan'
from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/lib/sonos/system.rb:12:in initialize' from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/lib/sonos/cli.rb:94:innew'
from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/lib/sonos/cli.rb:94:in system' from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/lib/sonos/cli.rb:8:indevices'
from /Library/Ruby/Gems/2.0.0/gems/thor-0.19.1/lib/thor/command.rb:27:in run' from /Library/Ruby/Gems/2.0.0/gems/thor-0.19.1/lib/thor/invocation.rb:126:ininvoke_command'
from /Library/Ruby/Gems/2.0.0/gems/thor-0.19.1/lib/thor.rb:359:in dispatch' from /Library/Ruby/Gems/2.0.0/gems/thor-0.19.1/lib/thor/base.rb:440:instart'
from /Library/Ruby/Gems/2.0.0/gems/sonos-0.3.6/bin/sonos:7:in <top (required)>' from /usr/bin/sonos:23:inload'
from /usr/bin/sonos:23:in `

'

I had a quick look and can't really see why - it should be detected as an Accessory.

DeviceDescription:

1 0 urn:schemas-upnp-org:device:ZonePlayer:1 192.168.1.14 - Sonos BOOST Sonos, Inc. http://www.sonos.com BR200 Sonos BOOST Sonos BOOST http://www.sonos.com/store/products/BR200 29.5-90191 1.12.1.2-2 REDACTED uuid:RINCON_REDACTED ... 28.0-00000 24.0-0000 5.4 BOOST BOOST 11 0x00000000 0x00008173 0x00030000 -1 64 16 ...

Undefined method `browse' for Sonos::Device::Speaker

Hi,

I wrote a really simple app (https://github.com/skinnyfit/tastebud) with this great gem, and it was working perfectly for a few days... then today, while running, it suddenly crashed with this error message:

/path/to/gems/sonos-0.3.5/lib/sonos/endpoint/content_directory.rb:24:in `queue': undefined method `browse' for #<Sonos::Device::Speaker:0x007f8e44223448> (NoMethodError)

Any call to speaker.queue is triggering this error.

I have no idea why this suddenly started happening (maybe Sonos updated itself?), but it's stopped working altogether now. I attempted to start debugging by looking at lib/sonos/endpoint/content_directory.rb:24, but there is no method browse in that class... in fact, I searched the whole repo for def browse, but no dice.

Any ideas what might be causing this error (or failing that, where browse is defined so I can investigate)?

Cheers!
– Richard

Play:1 blows things up

 kurt  ~  sonos speakers   1   master
WARNING: Nokogiri was built against LibXML version 2.9.1, but has dynamically loaded 2.8.0
/Users/kurt/.rvm/gems/ruby-2.0.0-p247/gems/sonos-0.3.3/lib/sonos/device/base.rb:17:in detect': S1 not supported (ArgumentError) from /Users/kurt/.rvm/gems/ruby-2.0.0-p247/gems/sonos-0.3.3/lib/sonos/topology_node.rb:18:indevice'
from /Users/kurt/.rvm/gems/ruby-2.0.0-p247/gems/sonos-0.3.3/lib/sonos/system.rb:73:in `collect'

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.