Giter Site home page Giter Site logo

christopher-dg / gpymusic Goto Github PK

View Code? Open in Web Editor NEW
200.0 11.0 13.0 233 KB

Google Py Music: A simple TUI client for Google Play Music

License: MIT License

Python 99.67% Makefile 0.33%
music-player command-line stream-music linux-terminal google-play-music

gpymusic's Introduction

Google Py Music

PyPI

A simple TUI client for Google Play Music

Browse and stream Google Play Music from the comfort and familiarity of your favourite terminal.

screencast

Disclaimer

Google Py Music is essentiallly unmaintained; I'll address security alerts and review PRs but I won't be fixing bugs or adding anything new myself.

These days I use Spotify with spotifyd and baton. For GPM, I recommend tuijam, which has loads more features and is actively maintained!

Dependencies

All of these can usually be installed with your operating system's package manager.

Installation

The easiest way to install is with pip by running pip3 install gpymusic.

You can also install from source:

$ git clone https://github.com/christopher-dG/gpymusic
$ cd gpymusic
$ python setup.py install

A special thanks goes to ftxrc for his work on the pip installation.

Configuration

Once gpymusic is installed, run gpymusic-setup. An example config file will be placed in ~/.config/gpymusic. Additionally, you should run gpymusic-oauth-login and follow the prompts if you have a free account.

Device ID

If you don't know your device ID, run gpymusic-get-dev-id and follow the prompts to generate a list of valid device IDs.

Password

You can choose to leave the password field empty and you will be prompted for it upon starting the program. Otherwise, you can supply it for automatic logins.

If you store your password in plain text, be aware the potential consequences.

Colours

Colour themes are defined in the colour section of your config file. To enable colour, make sure enable is set to yes and set the fields to hex colours as desired. highlight affects the section headers and 'now playing' output, content{1|2} affect the main window, and background/foreground are self-explanatory.

Setting background to default allows you to use your terminal background colour, which means this allows transparency if your terminal supports it.

Note: Upon exiting the program, your terminal colours will likely be modified. Just open a new terminal session and your colours will be back to normal.

Now Playing

To log the currently playing track to a file, include a nowplaying section in your config file with enable set to yes. A filename may then be specified, or the default value of ~/.nowplaying will be used.

This file may then be used in status bars or simple notifications systems. For example, here is a simple i3blocks definition:

[music]
command=cat ~/.nowplaying
interval=5

Running Google Py Music

Once installed and configured, the program can be run from the terminal with gpymusic. While the program is running, don't resize your terminal.

Controls

  • s/search search-term: Search for search-term
  • e/expand 123: Expand item number 123
  • p/play: Play the current queue
  • p/play s: Shuffle and play the current queue
  • p/play 123: Play item number 123
  • q/queue: Show the current queue
  • q/queue 123: Add item number 123 to queue
  • q/queue 1 2 3: Add items 1, 2, and 3 to the queue
  • q/queue c: Clear the current queue
  • radio 123: Create radio station around item number 123
  • w/write playlist-name: Write the current queue to playlist playlist-name
  • r/restore playlist-name: Replace the current queue with a playlist from file-name
  • h/help: Show help message
  • Ctrl-C: Exit Google Py Music

When playing music:

  • spc: Play/pause
  • 9/0: Volume down/up (Note: volume changes are not persistent across songs, so I recommend that you adjust your system volume instead)
  • n: Next track
  • q: Stop
  • ↑/↓/←/→: Seek

Accounts

Google Py Music works similarly to the web interface in that users with free accounts can only arbitrarily access music that they've purchased or uploaded, whereas users with paid accounts can search for and stream anything. When playing music with a free account, the entire song is downloaded and played locally rather than streamed.

Notes for free users:

  • OAuth2 login is required for free users, make sure you've run gpymusic-oauth-login as described in configuration!
  • If you don't want to wait for songs to download on the fly, you can download them all in one go by running gpymusic-download-all. Songs are stored in ~/.local/share/gpymusic/songs.
  • The e/expand command does not work for free users because artists and albums cannot be generated, so there is nothing for it to do.
  • I don't have enough music uploaded to my free account to properly test it, so please open issues about any crashes or other problems.

2-Factor Authentication

If your account has 2FA set up, you will need to use an app password to log in. If you're storing your password in your config file, replace it with the app password.

Crashes

If gpymusic crashes, your terminal settings will likely be messed up, in which case stty sane will restore order. Don't forget to open an issue!


Thanks for using Google Py Music!

gpymusic's People

Contributors

benfradet avatar cfangmeier avatar christopher-dg avatar dependabot[bot] avatar eightseventhreethree avatar ftxrc avatar ismaelpuerto avatar nickspoons 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

gpymusic's Issues

Api issues (it seems)

I am on a paid account (albeit in trial mode). Activated the login with oauth. No matter which song I try to play, it crashes.

system: macOs sierra, python 3.6.0

File "/Users/plumps/.pyenv/versions/pmcli/lib/python3.6/site-packages/gmusicapi/protocol/shared.py", line 218, in perform
response.raise_for_status()
File "/Users/plumps/.pyenv/versions/pmcli/lib/python3.6/site-packages/requests/models.py", line 909, in raise_for_status
                                                 raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://mclients.googleapis.com/music/mplay?opt=hi&net=mob&pt=e&slt=1488809739061&sig=KRSB9QoLLes7vYyWJO54qW4pHzE&mjck=T3mam5ah6ne36nued4puyhqyapy&hl=en_US&dv=0&tier=aa
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/bin/pmcli", line 28, in <module>
client.transition()
File "/Users/plumps/.local/share/pmcli/src/client.py", line 51, in transition
commands[command](arg)
File "/Users/plumps/.local/share/pmcli/src/client.py", line 281, in play
item.play()
File "/Users/plumps/.local/share/pmcli/src/music_objects.py", line 224, in play
MusicObject.play(self['songs'])
File "/Users/plumps/.local/share/pmcli/src/music_objects.py", line 50, in play
url = common.mc.get_stream_url(song['id'])
File "<decorator-gen-102>", line 2, in get_stream_url
File "/Users/plumps/.pyenv/versions/pmcli/lib/python3.6/site-packages/gmusicapi/utils/utils.py", line 293, in wrapper
return function(*args, **kw)
File "/Users/plumps/.pyenv/versions/pmcli/lib/python3.6/site-packages/gmusicapi/clients/mobileclient.py", line 371, in get_stream_url
                                                               return self._make_call(mobileclient.GetStreamUrl, song_id, device_id, quality)
File "/Users/plumps/.pyenv/versions/pmcli/lib/python3.6/site-packages/gmusicapi/clients/shared.py", line 84, in _make_call
                                                                 return protocol.perform(self.session, self.validate, *args, **kwargs)
File "/Users/plumps/.pyenv/versions/pmcli/lib/python3.6/site-packages/gmusicapi/protocol/shared.py", line 226, in perform
                                                         raise CallFailure(err_msg, call_name)
gmusicapi.exceptions.CallFailure: GetStreamUrl: 403 Client Error: Forbidden for url: https://mclients.googleapis.com/music/mplay?opt=hi&net=mob&pt=e&slt=1488809739061&sig=KRSB9QoLLes7vYyWJO54qW4pHzE&mjck=T3mam5ah6ne36nued4puyhqyapy&hl=en_US&dv=0&tier=aa
Now playing: None                                                                                                                              (requests kwargs: {'method': 'GET', 'url': 'https://mclients.googleapis.com/music/mplay', 'allow_redirects': False, 'headers': {'X-Device-ID': '16_seemingly_random_characters', 'Authorization': '<omitted>'}, 'params': {'opt': 'hi', 'net': 'mob', 'pt': 'e', 'slt': '1488809739061', 'sig': b'KRSB9QoLLes7vYyWJO54qW4pHzE', 'mjck': 'T3mam5ah6ne36nued4puyhqyapy', 'hl': 'en_US', 'dv': 0, 'tier': 'aa'}})
                                                                                                                                     (response was: '<HTML>\n<HEAD>\n<TITLE>Forbidden</TITLE>\n</HEAD>\n<BODY BGCOLOR="#FFFFFF" TEXT="#000000">\n<H1>Forbidden</H1>\n<H2>Error 403</H2>\n</BODY>\n</HTML>\n')

Any way of hooking up play/pause media key?

This might be a long shot, but is there any way it might be possible to allow users to use keyboard media keys with this client? I'm using Ubuntu and have a Play/Pause key on my laptop. It would be great to not have to tab to gpymusic to play/pause music.

Which Device ID do I use?

I know it has something to do with Google, but I am unsure which Device ID to input into my config.

I tried running gpymusic anyway, and the resulting errors were pretty malformed insofar as the spacing is messed up after newlines:

Logging in...Traceback (most recent call last):
                                                 File "/home/tyler/.local/bin/gpymusic", line 17, in <module>
                                                                                                                 start.login(config['user'])
                                                                                                                                              File "/home/tyler/.local/lib64/python3.5/site-packages/gpymusic/start.py", line 254, in login
                            if not common.mc.login(user['email'], user['password'], user['deviceid']):
                                                                                                        File "/home/tyler/.local/lib64/python3.5/site-packages/gmusicapi/clients/mobileclient.py", line 143, in login
      self.android_id = self._validate_device_id(device_id, is_mac=is_mac)
                                                                            File "/home/tyler/.local/lib64/python3.5/site-packages/gmusicapi/clients/mobileclient.py", line 58, in _validate_device_id
                                                                                                                                                                                                          raise InvalidDeviceId('Invalid device_id %s.' % device_id, devices)
                                                          gmusicapi.exceptions.InvalidDeviceId: Invalid device_id .Your valid device IDs are:
                            <list of device IDs>

Anyway, so I am not sure which ID to choose. There are 8 available to choose from. Some have capitalized letters, others have lowercase letters, some are 12 characters long, and some are 60 characters long. No further information is given in the list.

Unable to start in virtualenv

I get this crash in a newly created virtualenv:

 ~/work/gpymusic   master *  bin/gpymusic-setup 
Traceback (most recent call last):
  File "bin/gpymusic-setup", line 6, in <module>
    exec(compile(open(__file__).read(), __file__, 'exec'))
  File "/home/tibi/work/gpymusic/bin/gpymusic-setup", line 6, in <module>
    exec(compile(open(__file__).read(), __file__, 'exec'))
  File "/home/tibi/work/gpymusic/bin/gpymusic-setup", line 6, in <module>
    exec(compile(open(__file__).read(), __file__, 'exec'))
...
  File "/home/tibi/work/gpymusic/bin/gpymusic-setup", line 6, in <module>
    exec(compile(open(__file__).read(), __file__, 'exec'))
  File "/home/tibi/work/gpymusic/bin/gpymusic-setup", line 6, in <module>
    exec(compile(open(__file__).read(), __file__, 'exec'))
  File "/home/tibi/work/gpymusic/bin/gpymusic-setup", line 4, in <module>
    __import__('pkg_resources').require('gpymusic==1.0.3')
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/__init__.py", line 971, in require
    needed = self.resolve(parse_requirements(requirements))
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/__init__.py", line 816, in resolve
    requirements = list(requirements)[::-1]
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/__init__.py", line 2886, in parse_requirements
    yield Requirement(line)
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/__init__.py", line 2893, in __init__
    super(Requirement, self).__init__(requirement_string)
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/packaging/requirements.py", line 90, in __init__
    req = REQUIREMENT.parseString(requirement_string)
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 1607, in parseString
    loc, tokens = self._parse( instring, 0 )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 1379, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 3376, in parseImpl
    loc, exprtokens = e._parse( instring, loc, doActions )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 1379, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 3511, in parseImpl
    ret = e._parse( instring, loc, doActions )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 1379, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 3359, in parseImpl
    loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 1379, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 3376, in parseImpl
    loc, exprtokens = e._parse( instring, loc, doActions )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 1379, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 3964, in parseImpl
    loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 1379, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 3511, in parseImpl
    ret = e._parse( instring, loc, doActions )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 1379, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 3698, in parseImpl
    return self.expr._parse( instring, loc, doActions, callPreParse=False )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 1379, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 3359, in parseImpl
    loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 1379, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 3430, in parseImpl
    loc2 = e.tryParse( instring, loc )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 1421, in tryParse
    return self._parse( instring, loc, doActions=False )[0]
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 1383, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 2782, in parseImpl
    ret[k] = d[k]
  File "/home/tibi/work/gpymusic/lib/python3.5/site-packages/pkg_resources/_vendor/pyparsing.py", line 405, in __setitem__
    self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)]
RecursionError: maximum recursion depth exceeded

Crashing when starting song

After searching for a song then starting it with p 4 for example returns this terminal output, this is copied and is all that I could get to display, I couldnt retrieve the beginning of the error message.

ile "/usr/local/bin/pmcli", line 50, in transition commands[command](arg) File "/usr/local/bin/pmcli", line 357, in play opt.play(infobar) File "/home/getz/.local/share/pmcli/src/music_objects.py", line 337, in play MusicObject.play(win, [(self['id'], to_string(self), self['time'])]) File "/home/getz/.local/share/pmcli/src/music_objects.py", line 42, in play url = api.get_stream_url(song[0]) File "<decorator-gen-102>", line 2, in get_stream_url File "/usr/local/lib/python3.5/dist-packages/gmusicapi/utils/utils.py", line 293, in wrapper return function(*args, **kw) File "/usr/local/lib/python3.5/dist-packages/gmusicapi/clients/mobileclient.py", line 371, in get_stream_url return self._make_call(mobileclient.GetStreamUrl, song_id, device_id, quality) File "/usr/local/lib/python3.5/dist-packages/gmusicapi/clients/shared.py", line 84, in _make_call return protocol.perform(self.session, self.validate, *args, **kwargs) File "/usr/local/lib/python3.5/dist-packages/gmusicapi/protocol/shared.py", line 226, in perform raise CallFailure(err_msg, call_name) gmusicapi.exceptions.CallFailure: GetStreamUrl: 403 Client Error: Forbidden for url: https://mclients.googleapis.com/music/mplay?dv=0&sig=7nxuPmyCuhPI9tkTonOkVMgKzYo&net=mob&hl=en_US&slt=1487500852934&tier=aa&mjck=T4tohzge6kd766ycj76jgbuxmdu&opt=hi&pt=e (requests kwargs: {'method': 'GET', 'params': {'dv': 0, 'sig': b'7nxuPmyCuhPI9tkTonOkVMgKzYo', 'net': 'mob', 'hl': 'en_US', 'slt': '1487500852934', 'tier': 'aa', 'mjck': 'T4tohzge6kd766ycj76jgbuxmdu', 'opt': 'hi', 'pt': 'e'}, 'url': 'https://mclients.googleapis.com/music/mplay', 'allow_redirects': False, 'headers': {'Authorization': '<omitted>', 'X-Device-ID': 'random_characters'}}) (response was: '<HTML>\n<HEAD>\n<TITLE>Forbidden</TITLE>\n</HEAD>\n<BODY BGCOLOR="#FFFFFF" TEXT="#000000">\n<H1>Forbidden</H1>\n<H2>Error 403</H2>\n</BODY>\n</HTML>\n')

Thumbs Up/Down Support

Would it be at all possible to add thumbs up/down support? Or would this be pointless given how the program currently works?

pmcli for free users.

I would glady use the Google Play services only for uploading my music and listening to it, and that would be pretty rad if I could do it through the terminal, so if you mind, I would glady use pmcli in a state of only songs purchased and songs uploaded myself. :)

Crashes on search

What I did:

s tribe

reproducable: yes

Searching for 'tribe'...Traceback (most recent call last):
Now playing: None
File "/home/CELLULOIDVFX.INC/johannesa/.local/lib/python3.5/site-packages/gmusicapi/protocol/shared.py", line 218, in perform
response.raise_for_status()
File "/home/CELLULOIDVFX.INC/johannesa/.local/lib/python3.5/site-packages/requests/models.py", line 909, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://mclients.googleapis.com/sj/v2.5/query?max-results=12&hl=en_US&ct=1%2C2%2C3%2C4%2C6%2C7%2C8%2C9&tier=fr&q=tribe&dv=0
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/bin/pmcli", line 1253, in <module>
transition(out.get_input())
File "/usr/local/bin/pmcli", line 717, in transition
commands[command](arg)
File "/usr/local/bin/pmcli", line 967, in search
result = api.search(query, max_results=limit)
File "/home/CELLULOIDVFX.INC/johannesa/.local/lib/python3.5/site-packages/gmusicapi/clients/mobileclient.py", line 1820, in search
res = self._make_call(mobileclient.Search, query, max_results)
File "/home/CELLULOIDVFX.INC/johannesa/.local/lib/python3.5/site-packages/gmusicapi/clients/shared.py", line 84, in _make_call
return protocol.perform(self.session, self.validate, *args, **kwargs)
File "/home/CELLULOIDVFX.INC/johannesa/.local/lib/python3.5/site-packages/gmusicapi/protocol/shared.py", line 226, in perform
raise CallFailure(err_msg, call_name)
gmusicapi.exceptions.CallFailure: Search: 403 Client Error: Forbidden for url: https://mclients.googleapis.com/sj/v2.5/query?max-results=12&hl=en_US&ct=1%2C2%2C3%2C4%2C6%2C7%2C8%2C9&tier=fr&q=tribe&dv=0
(requests kwargs: {'url': 'https://mclients.googleapis.com/sj/v2.5/query', 'method': 'GET', 'headers': {'Authorization': '<omitted>'}, 'params': {'max-results': 12, 'hl': 'en_US', 'ct': '1,2,3,4,6,7,8,9', 'tier': 'fr', 'q': 'tribe', 'dv': 0}})
(response was: '{"error":{"errors":[{"domain":"global","reason":"forbidden","message":"Forbidden"}],"code":403,"message":"Forbidden"}}')

git commit 01ddf08b6314aec52aca7ff1ad34ae1ceb3e9d32

terminator 0.98

Python 3.5.2

Distributor ID:	Ubuntu
Description:	Ubuntu 16.04.1 LTS
Release:	16.04
Codename:	xenial
[cplayer] mpv 0.22.0 (C) 2000-2016 mpv/MPlayer/mplayer2 projects
[cplayer]  built on Mon Nov 21 14:57:51 UTC 2016
[cplayer] ffmpeg library versions:
[cplayer]    libavutil       55.34.100
[cplayer]    libavcodec      57.64.100
[cplayer]    libavformat     57.56.100
[cplayer]    libswscale      4.2.100
[cplayer]    libavfilter     6.65.100
[cplayer]    libswresample   2.3.100
[cplayer] ffmpeg version: 3.2

ImportError: No module named gmusicapi

I'm getting this error even though I have gmusicapi installed

[cjb@pizza ~]$ gpymusic 
Traceback (most recent call last):
  File "/usr/local/bin/gpymusic", line 3, in <module>
    import client
  File "/home/cjb/.local/share/gpymusic/src/client.py", line 1, in <module>
    import common
  File "/home/cjb/.local/share/gpymusic/src/common.py", line 1, in <module>
    from gmusicapi import Mobileclient
ImportError: No module named 'gmusicapi'
[cjb@pizza ~]$
[cjb@pizza ~]$ pip install gmusicapi
Collecting gmusicapi
Collecting validictory!=0.9.2,>=0.8.0 (from gmusicapi)
Collecting proboscis>=1.2.5.1 (from gmusicapi)
Collecting mutagen>=1.34 (from gmusicapi)
Collecting python-dateutil!=2.0,>=1.3 (from gmusicapi)
  Using cached python_dateutil-2.6.0-py2.py3-none-any.whl
Collecting future (from gmusicapi)
Collecting mock>=0.7.0 (from gmusicapi)
  Using cached mock-2.0.0-py2.py3-none-any.whl
Collecting protobuf>=3.0.0 (from gmusicapi)
  Using cached protobuf-3.2.0-cp27-cp27mu-manylinux1_x86_64.whl
Collecting decorator>=3.3.1 (from gmusicapi)
  Using cached decorator-4.0.11-py2.py3-none-any.whl
Collecting oauth2client>=1.1 (from gmusicapi)
  Using cached oauth2client-4.0.0-py2.py3-none-any.whl
Collecting gpsoauth>=0.2.0 (from gmusicapi)
Collecting requests!=1.2.0,!=2.12.0,!=2.12.1,!=2.12.2,!=2.2.1,!=2.8.0,!=2.8.1,>=1.1.0 (from gmusicapi)
  Using cached requests-2.13.0-py2.py3-none-any.whl
Collecting MechanicalSoup>=0.4.0 (from gmusicapi)
  Using cached MechanicalSoup-0.6.0-py2.py3-none-any.whl
Collecting appdirs>=1.1.0 (from gmusicapi)
  Using cached appdirs-1.4.3-py2.py3-none-any.whl
Collecting six>=1.9.0 (from gmusicapi)
  Using cached six-1.10.0-py2.py3-none-any.whl
Collecting funcsigs>=1; python_version < "3.3" (from mock>=0.7.0->gmusicapi)
  Using cached funcsigs-1.0.2-py2.py3-none-any.whl
Collecting pbr>=0.11 (from mock>=0.7.0->gmusicapi)
  Using cached pbr-2.1.0-py2.py3-none-any.whl
Collecting setuptools (from protobuf>=3.0.0->gmusicapi)
  Using cached setuptools-34.4.1-py2.py3-none-any.whl
Collecting rsa>=3.1.4 (from oauth2client>=1.1->gmusicapi)
  Using cached rsa-3.4.2-py2.py3-none-any.whl
Collecting httplib2>=0.9.1 (from oauth2client>=1.1->gmusicapi)
Collecting pyasn1-modules>=0.0.5 (from oauth2client>=1.1->gmusicapi)
  Using cached pyasn1_modules-0.0.8-py2.py3-none-any.whl
Collecting pyasn1>=0.1.7 (from oauth2client>=1.1->gmusicapi)
  Using cached pyasn1-0.2.3-py2.py3-none-any.whl
Collecting pycryptodomex>=3.0 (from gpsoauth>=0.2.0->gmusicapi)
Collecting beautifulsoup4 (from MechanicalSoup>=0.4.0->gmusicapi)
  Using cached beautifulsoup4-4.5.3-py2-none-any.whl
Collecting packaging>=16.8 (from setuptools->protobuf>=3.0.0->gmusicapi)
  Using cached packaging-16.8-py2.py3-none-any.whl
Collecting pyparsing (from packaging>=16.8->setuptools->protobuf>=3.0.0->gmusicapi)
  Using cached pyparsing-2.2.0-py2.py3-none-any.whl
Installing collected packages: validictory, proboscis, mutagen, six, python-dateutil, future, funcsigs, pbr, mock, appdirs, pyparsing, packaging, setuptools, protobuf, decorator, pyasn1, rsa, httplib2, pyasn1-modules, oauth2client, pycryptodomex, requests, gpsoauth, beautifulsoup4, MechanicalSoup, gmusicapi
Successfully installed MechanicalSoup-0.6.0 appdirs-1.4.3 beautifulsoup4-4.5.3 decorator-4.0.11 funcsigs-1.0.2 future-0.16.0 gmusicapi-10.1.2 gpsoauth-0.4.1 httplib2-0.10.3 mock-2.0.0 mutagen-1.37 oauth2client-4.0.0 packaging-16.8 pbr-2.1.0 proboscis-1.2.6.0 protobuf-3.2.0 pyasn1-0.2.3 pyasn1-modules-0.0.8 pycryptodomex-3.4.5 pyparsing-2.2.0 python-dateutil-2.6.0 requests-2.13.0 rsa-3.4.2 setuptools-34.4.1 six-1.10.0 validictory-1.1.1
You are using pip version 8.1.1, however version 9.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
[cjb@pizza ~]$
[cjb@pizza ~]$ ls .local/lib/python2.7/site-packages/gmusicapi
appdirs.py  appdirs.pyc  clients  exceptions.py  exceptions.pyc  gmtools  __init__.py  __init__.pyc  protocol  session.py  session.pyc  test  utils  _version.py  _version.pyc
[cjb@pizza ~]$

Running gpymusic ends with a TypeError

It looks like gpymusic can't read the OAuth credentials:

2017-10-29 22:52:21,012 - gmusicapi.Musicmanager2 (musicmanager:186) [WARNING]: could not retrieve oauth credentials from ''/home/max/.local/share/gmusicapi/oauth.cred''
Traceback (most recent call last):
  File "/usr/local/bin/gpymusic", line 4, in <module>
    __import__('pkg_resources').run_script('gpymusic==1.2.1', 'gpymusic')
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 719, in run_script
    self.require(requires)[0].run_script(script_name, ns)
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 1511, in run_script
    exec(script_code, namespace, namespace)
  File "/usr/local/lib/python3.5/dist-packages/gpymusic-1.2.1-py3.5.egg/EGG-INFO/scripts/gpymusic", line 25, in <module>
  File "/usr/local/lib/python3.5/dist-packages/gpymusic-1.2.1-py3.5.egg/gpymusic/client.py", line 314, in __init__
  File "/usr/local/lib/python3.5/dist-packages/gpymusic-1.2.1-py3.5.egg/gpymusic/client.py", line 350, in gen_library
  File "/usr/local/lib/python3.5/dist-packages/gmusicapi-10.1.2-py3.5.egg/gmusicapi/clients/musicmanager.py", line 287, in get_uploaded_songs
    to_return = [song for chunk in to_return for song in chunk]
  File "/usr/local/lib/python3.5/dist-packages/gmusicapi-10.1.2-py3.5.egg/gmusicapi/clients/musicmanager.py", line 287, in <listcomp>
    to_return = [song for chunk in to_return for song in chunk]
  File "/usr/local/lib/python3.5/dist-packages/gmusicapi-10.1.2-py3.5.egg/gmusicapi/clients/musicmanager.py", line 337, in _get_all_songs
    export_type)
  File "/usr/local/lib/python3.5/dist-packages/gmusicapi-10.1.2-py3.5.egg/gmusicapi/clients/shared.py", line 84, in _make_call
    return protocol.perform(self.session, self.validate, *args, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/gmusicapi-10.1.2-py3.5.egg/gmusicapi/protocol/shared.py", line 207, in perform
    req_kwargs = cls.build_request(*args, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/gmusicapi-10.1.2-py3.5.egg/gmusicapi/protocol/shared.py", line 89, in build_request
    val = val(*args, **kwargs)
  File "<decorator-gen-68>", line 2, in dynamic_data
  File "/usr/local/lib/python3.5/dist-packages/gmusicapi-10.1.2-py3.5.egg/gmusicapi/protocol/musicmanager.py", line 69, in pb
    msg = f(*args, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/gmusicapi-10.1.2-py3.5.egg/gmusicapi/protocol/musicmanager.py", line 652, in dynamic_data
    msg.client_id = client_id
  File "/usr/local/lib/python3.5/dist-packages/protobuf-3.4.0-py3.5.egg/google/protobuf/internal/python_message.py", line 662, in field_setter
    new_value = type_checker.CheckValue(new_value)
  File "/usr/local/lib/python3.5/dist-packages/protobuf-3.4.0-py3.5.egg/google/protobuf/internal/type_checkers.py", line 177, in CheckValue
    raise TypeError(message)
TypeError: None has type <class 'NoneType'>, but expected one of: (<class 'bytes'>, <class 'str'>)

Crash during search, Ubuntu 16

I installed gpymusic on Ubuntu 16.04. As far as I can tell, all my dependencies are up to date and I don't have anything wacky going on, but gpymusic crashes when I try to search for anything. Here's the traceback:

Traceback (most recent call last):
File "/home/dcrane/.local/bin/gpymusic", line 28, in <module>
common.client.transition()
File "/home/dcrane/.local/lib/python3.5/site-packages/gpymusic/client.py", line 57, in transition
 commands[command](arg)
 File "/home/dcrane/.local/lib/python3.5/site-packages/gpymusic/client.py", line 402, in search
 limit = common.w.main.ylimit - 4
AttributeError: '_curses.curses window' object has no attribute 'ylimit'

I have a free Google Play Music account, not a paid one, which I know may make a difference.

Issue with get_device_id.py

First off, thanks for writing this cli for google play music. I've been looking for one for a while and haven't' been able to find a good one before I stumbled upon this repo.

Now to the issue. I tried to use the script/get_device_id.py to get a list of devices registered with my google account but when I ran the script (with proper email and app specific passwd) nothing was printed out.

I added a print statement to print out the devices object and got this as output

[{'kind': 'sj#devicemanagementinfo', 'id': 'removed', 'friendlyName': 'Google Play Music for Chrome on Windows', 'type': 'DESKTOP_APP', 'lastAccessedTimeMs': '1488256354344'}, {'kind': 'sj#devicemanagementinfo', 'id': 'removed', 'friendlyName': 'Verizon Samsung SM-N910V', 'type': 'ANDROID', 'lastAccessedTimeMs': '1488227936743'}, {'kind': 'sj#devicemanagementinfo', 'id': 'removed', 'friendlyName': 'Verizon Samsung SM-N910V', 'type': 'ANDROID', 'lastAccessedTimeMs': '1481225985805', 'smartPhone': False}]

Clearly there are registered devices so I believe there is an issue with this for loop:

for i, device in enumerate([d for d in devices if d['kind'] in (u'ANDROID', u'IOS')]):
            id = device['id']
            print('%d: %s' % (i + 1, id[2:]if id.startswith('0x') else id))

`queue` command doesn't sanitize arguments

Using develop branch.
Running q 03 04 got me a crash with the exception being a ValueError from an int() call.
Side note: being able to queue multiple songs at once (as I tried here), or queue a range (say, q 01-09) of values, would be useful.

Login failed on 'gpymusic-get-dev-id'

I've tried running gpymusic-get-dev-id so that I can start using gpymusic. However, after entering my details, I always get the message "Login failed, verify your email and password." I am sure that I have entered my email and password correctly, and I have tested it numerous times in a browser, where it consistently works, and in the terminal, where it does not.

My email address contains several full-stops - I don't know whether that could be an issue. The password is not hugely long (<15 chars; I know the API has issues with very long passwords).

I am running Python 3.5.3 (python3) on Debian 9.

Any idea how I can debug this?

How to reset config?

I put the wrong device id in the config file and everytime i try to strat the program i get:

gmusicapi.exceptions.InvalidDeviceId: Invalid device_id 510371589486E02.Your valid device IDs are:
If i try to re-run gpymusic-setup then i get

image

search with no arguments crash

When I run the s command with no arguments, pmcli crashes. Also, in xterm, it apparently makes the terminal stop writing newlines or something?

Searching for 'None'...Traceback (most recent call last):
Now playing: None                                          File "/usr/lib/python3.6/site-packages/gmusicapi/protocol/shared.py", line 218, in perform
> s                                                                                                                                                      response.raise_for_status()
                                                                                                                                                                                      File "/usr/lib/python3.6/site-packages/requests/models.py", line 909, in raise_for_status
                                                                        raise HTTPError(http_error_msg, response=self)
                                                                                                                      requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: https://mclients.googleapis.com/sj/v2.5/query?ct=1%2C2%2C3%2C4%2C6%2C7%2C8%2C9&max-results=19&hl=en_US&dv=0&tier=fr

                                                                                                    During handling of the above exception, another exception occurred:

                                                                                                                                                                       Traceback (most recent call last):
                                                                                                                                                                                                           File "/usr/local/bin/pmcli", line 496, in <module>
                                                      transition(get_input())
                                                                               File "/usr/local/bin/pmcli", line 47, in transition
                                                                                                                                      commands[command](arg)
                                                                                                                                                              File "/usr/local/bin/pmcli", line 416, in search
       result = api.search(query, max_results=limit)
                                                      File "/usr/lib/python3.6/site-packages/gmusicapi/clients/mobileclient.py", line 1820, in search
                                                                                                                                                         res = self._make_call(mobileclient.Search, query, max_results)
              File "/usr/lib/python3.6/site-packages/gmusicapi/clients/shared.py", line 84, in _make_call
                                                                                                             return protocol.perform(self.session, self.validate, *args, **kwargs)
                                                                                                                                                                                    File "/usr/lib/python3.6/site-packages/gmusicapi/protocol/shared.py", line 226, in perform
                                                                       raise CallFailure(err_msg, call_name)
                                                                                                            gmusicapi.exceptions.CallFailure: Search: 400 Client Error: Bad Request for url: https://mclients.googleapis.com/sj/v2.5/query?ct=1%2C2%2C3%2C4%2C6%2C7%2C8%2C9&max-results=19&hl=en_US&dv=0&tier=fr
                                                                                                     (requests kwargs: {'method': 'GET', 'url': 'https://mclients.googleapis.com/sj/v2.5/query', 'params': {'ct': '1,2,3,4,6,7,8,9', 'q': None, 'max-results': 19, 'hl': 'en_US', 'dv': 0, 'tier': 'fr'}, 'headers': {'Authorization': '<omitted>'}})
                                                                                                                                          (response was: '{"error":{"errors":[{"domain":"global","reason":"required","message":"Required parameter: q","locationType":"parameter","location":"q"}],"code":400,"message":"Required parameter: q"}}')

search command blocking other commands, not returning

It seems as though I've logged into pmcli correctly—it reports upon launch that it's logged in—and at first the help command is functional, but after I run a search command (which always fails to return anything, no matter what I search for—including things I've checked in Music proper to ensure exist) pmcli will display exclusively this:

Now playing: None
> 

...and subsequent executions of the help command return nothing, though quitting with CTRL-C will show the exit message, and trying to run expand and play commands will show the error

Error: Invalid number. Valid between 1-0 Enter 'h' or 'help' for help.

xterm, python 3.6.0.

Make the backend streaming service modular, aka v2.0.0

I'm personally making the switch from GPM to Spotify, and I'd like to support multiple services. I realize that other projects like ncmpcpp, mopidy, etc. already exist for this, but the idea behind this project has always been to "just work" with little configuration required and I don't think that's really the case anywhere else.

This will probably turn into a fairly massive rewrite, which is probably a good idea because I'm a much less bad programmer than I was earlier this year and this code is spaghetti.

A rename will also be in order...

Search not working with free account

From the gmusicapi docs:

Free account search is restricted so may not contain hits for all result types.

After some investigation, the restricted content is values for keys 'album_hits', 'artist_hits', 'playlist_hits', 'situation_hits', and 'song_hits' pmcli.search()Uses 'song_hits', 'artist_hits', and 'album_hits' to generate results, so naturally it's unsuccessful.

I don't really have a solution for this, other than making free users only capable of radio stations. It's something that needs to be worked on in gmusicapi. If anyone has thoughts on how to work around this, I'd love to hear them.

ImportError

Hi,
downloaded the latest version and im getting this error

File "/usr/local/bin/pmcli", line 6, in
from music_objects import Song, Artist, Album, Queue

Previous to updating it did work

Crashing on start

Logging in... Logged in as [email protected] (Free).
Enter 'h' or 'help' if you need help.Traceback (most recent call last):
File "/home/maxim/bin/gpymusic", line 26, in
) else common.client.FreeClient()
AttributeError: 'NoneType' object has no attribute 'FreeClient'

long passwords crash

For those who use password generators, very long passwords (32-64 characters) fail.

Audio fails to play; songs end immediately

I tried individual songs and a queue of many, and each time, when a song plays, it pretty instantly finishes. It goes through each one until the queue is done playing within a few seconds. I do have the dependencies:

$ pip list | grep gpymusic
gpymusic (1.2.1)
$ python --version
Python 3.4.5
$ mpv --version
mpv 0.25.0 (C) 2000-2017 mpv/MPlayer/mplayer2 projects
 built on UNKNOWN
ffmpeg library versions:
   libavutil       55.58.100
   libavcodec      57.89.100
   libavformat     57.71.100
   libswscale      4.6.100
   libavfilter     6.82.100
   libswresample   2.7.100
ffmpeg version: 3.3.6

Question for YOU: How are you discovering gpymusic?

I'm curious how people are actually arriving at this repo. GitHub tells me that many are coming from the Reddit posts in /r/unixporn and /r/googleplaymusic, please confirm or tell me otherwise 🙂.

There's been an influx in stars the past week or so which is mostly what raises this question for me.

pmcli will not launch, is_subscribed error

After setting up the config file with my login credentials and device ID, I've gotten this error.

`Traceback (most recent call last):
File "/usr/lib/python3.6/site-packages/gmusicapi/utils/utils.py", line 655, in get
value, last_update = inst._cache[self.name]
KeyError: 'is_subscribed'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/bin/pmcli", line 24, in <module>
    common.mc.is_subscribed
  File "/usr/lib/python3.6/site-packages/gmusicapi/utils/utils.py", line 660, in __get__
    value = self.fget(inst)
  File "/usr/lib/python3.6/site-packages/gmusicapi/clients/mobileclient.py", line 75, in is_subscribed
    res = self._make_call(mobileclient.Config)
  File "/usr/lib/python3.6/site-packages/gmusicapi/clients/shared.py", line 84, in _make_call
    return protocol.perform(self.session, self.validate, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/gmusicapi/protocol/shared.py", line 209, in perform
    response = session.send(req_kwargs, cls.required_auth)
  File "/usr/lib/python3.6/site-packages/gmusicapi/session.py", line 84, in send
    raise NotLoggedIn
gmusicapi.exceptions.NotLoggedIn

All dependencies should be installed through pip or pacman on Arch. I see the error is calling from the python gmusicapi package, so if this isn't the right place to be posting this, please let me know!

gpymusic-get-dev-id no valid MAC traceback

On x86_64 Debian Stretch, Python 3.5.3. Ran setup.py install on master branch, as instructed in README.

When I run gpymusic-get-dev-id it fails with the following traceback error:

$ gpymusic-get-dev-id 
Enter your email: ********@gmail.com
Enter password for ********@gmail.com: ******** 
Traceback (most recent call last):
  File "/usr/local/bin/gpymusic-get-dev-id", line 4, in <module>
    __import__('pkg_resources').run_script('gpymusic==1.1.0', 'gpymusic-get-dev-id')
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 739, in run_script
    self.require(requires)[0].run_script(script_name, ns)
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 1501, in run_script
    exec(script_code, namespace, namespace)
  File "/usr/local/lib/python3.5/dist-packages/gpymusic-1.1.0-py3.5.egg/EGG-INFO/scripts/gpymusic-get-dev-id", line 17, in <module>
  File "/usr/local/lib/python3.5/dist-packages/gmusicapi-10.1.2-py3.5.egg/gmusicapi/clients/mobileclient.py", line 130, in login
    raise OSError("a valid MAC could not be determined."
OSError: a valid MAC could not be determined. Provide an android_id (and be sure to provide the same one on future runs).

pmcli crashes while generating a new library

This is the complete output:

Logging in.../home/user/exp/lib/python3.6/site-packages/Cryptodome/Math/_Numbers_gmp.py:230: UserWarning: implicit cast to 'char *' from a different pointer type: will be forbidden in the future (check that the types are as you expect; use an explicit ffi.cast() if they are correct)
                                                                                      _gmp.gmp_snprintf(buf, c_size_t(buf_len), b(%Zd), self._mpz_p)
Generating your library...Traceback (most recent call last):
Could not find library file.                                  File src/main.py, line 25, in <module>
                                                                                                          ) else client.FreeClient()
                                                                                                                                      File /home/user/exp/pmcli/src/client.py, line 304, in __init__
                                                                                                                                                                                                          self.gen_library()
                       File /home/user/exp/pmcli/src/client.py, line 351, in gen_library
                                                                                              with zipfile.ZipFile(join(common.DATA_DIR, 'library.zip'), 'w') as z:
                                                                                                                                                                     File /usr/local/lib/python3.6/zipfile.py, line 1082, in __init__
                                    self.fp = io.open(file, filemode)
                                                                     FileNotFoundError: [Errno 2] No such file or directory: '/home/user/.local/share/pmcli/library.zip'
                                                                                                                                                                        %

That's how it comes out. I don't know why but after running pmcli my terminal remains in a modified state and commands' output is hijacked. Maybe it's due to the fact that it crashes. If that persists I'll open a new issue.

The error is due to the fact that I don't have the pmcli directory inside ~/.local/share. I think that the program should fail gracefully in this case. I can confirm that after creating it everything works.

pressing h or help leads to an exception

symptom: Calling the help menu shows the usage, but crashes pmcli.

I already looked in your code for a while, und found the common.v object to be empty, which seems to not be accounted for. Will have a look further.

Weird issue on launch

Logging in.../usr/lib/python3.6/site-packages/Cryptodome/Math/_Numbers_gmp.py:230: UserWarning: implicit cast to 'char *' from a different pointer type: will be forbidden in the future (check that the types are as you expect; use an explicit ffi.cast() if they are correct)
                                                              _gmp.gmp_snprintf(buf, c_size_t(buf_len), b("%Zd"), self._mpz_p)

I used my app password and device ID I already had stored for mopidy.

Error when play album

File "/usr/bin/gpymusic", line 28, in <module> 
common.client.transition()
File "/usr/lib/python3.6/site-packages/gpymusic/client.py", line 57, in transition 
commands[command](arg)
File "/usr/lib/python3.6/site-packages/gpymusic/client.py", line 290, in play 
item.play()
File "/usr/lib/python3.6/site-packages/gpymusic/music_objects.py", line 237, in play 
MusicObject.play(self['songs'])
File "/usr/lib/python3.6/site-packages/gpymusic/music_objects.py", line 48, in play 
common.w.display()
File "/usr/lib/python3.6/site-packages/gpymusic/writer.py", line 287, in display 
crs.color_pair(3 if y % 2 == 0 else 4) if cl else 0)
_curses.error: addwstr() returned ERR

I have Play Music Full but when i try to play single album i have that error

Add audio title to mpv

This should just help with when using mpv with MPRIS support which displays song titles in the UI better. May be we can use mpv to play the whole playlist which should make other keybindings work through MPRIS.

Only the call to mpv need to change to include ['--title', str(song)].

Radio Support

Are you planning on adding support for radio and playlists?

a valid MAC could not be determined

I have the same error as here, and here, and here. What is the reason of this error?

I have it on Kali Linux LiveCD inserted into physical laptop but can successfully login on AWS EC2 instance. Sounds like nonsence because laptop has physical NIC and instance is not.

I tried to replace Mobileclient.FROM_MAC_ADDRESS with MAC literal and that didn't help, but then script proposed me to replace it with some HEX value should have been working and it worked when I replaced Mobileclient.FROM_MAC_ADDRESS with it.

But for another account this didn't work, so no consistency here.

What to do?

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.