crustymonkey / py-sonic Goto Github PK
View Code? Open in Web Editor NEWA python library to wrap the Subsonic REST API
Home Page: http://stuffivelearned.org/doku.php?id=programming:python:py-sonic
License: GNU General Public License v3.0
A python library to wrap the Subsonic REST API
Home Page: http://stuffivelearned.org/doku.php?id=programming:python:py-sonic
License: GNU General Public License v3.0
Using:
The album IDs obtained from earching for albums and the looking at the albums in the results of getArtist() seem to correspond with the ones in the Subsonic web interface. However, they cannot be used for getAlbum().
When I do a getSong(), the artist and album IDs returned are different and the ones that can be used with getArtist() and getAlbum().
This small python script demonstrates the issue:
#!/usr/bin/env python2
import libsonic
import json
conn = libsonic.Connection('http://example.com' , 'username' , 'password', port=8080)
print "IS THE ARTIST ERIC CLAPTON"
print 50*"#"
print json.dumps(conn.getArtist(114), sort_keys=True, indent=2)
print "\n\n"
print "SOULD BE THE ARTIST ERIC CLAPTON ACCORDING TO SUBSONIC WEB INTERFACE"
print 50*"#"
print json.dumps(conn.getArtist(72), sort_keys=True, indent=2)
print "\n\n"
print "IS THE ALBUM UNPLUGGED"
print 50*"#"
print json.dumps(conn.getAlbum(1000), sort_keys=True, indent=2)
print "\n\n"
print "SHOULD BE THE ALBUM UNPLUGGED ACCORDING TO SUBSONIC WEB INTERFACE"
print 50*"#"
print json.dumps(conn.getAlbum(16012), sort_keys=True, indent=2)
$ ./test.py
IS THE ARTIST ERIC CLAPTON
##################################################
{
"artist": {
"album": {
"artist": "Eric Clapton",
"artistId": 114,
"coverArt": "al-1000",
"created": "2013-02-24T20:38:08",
"duration": 3698,
"id": 1000,
"name": "Unplugged",
"songCount": 14
},
"albumCount": 1,
"coverArt": "ar-114",
"id": 114,
"name": "Eric Clapton"
},
"status": "ok",
"version": "1.8.0",
"xmlns": "http://subsonic.org/restapi"
}
SOULD BE THE ARTIST ERIC CLAPTON ACCORDING TO SUBSONIC WEB INTERFACE
##################################################
{
"artist": {
"albumCount": 1,
"coverArt": "ar-72",
"id": 72,
"name": "DutchReleaseTeam"
},
"status": "ok",
"version": "1.8.0",
"xmlns": "http://subsonic.org/restapi"
}
IS THE ALBUM UNPLUGGED
##################################################
{
"album": {
"artist": "Eric Clapton",
"artistId": 114,
"coverArt": "al-1000",
"created": "2013-02-24T20:38:08",
"duration": 3698,
"id": 1000,
"name": "Unplugged",
"song": [
{
"album": "Unplugged",
"albumId": 1000,
"artist": "Eric Clapton",
"artistId": 114,
"bitRate": 128,
"contentType": "audio/mpeg",
"coverArt": 16012,
"created": "2013-02-24T20:38:08",
"discNumber": 1,
"duration": 192,
"id": 16013,
"isDir": false,
"isVideo": false,
"parent": 16012,
"path": "Eric Clapton/Unplugged/01 Signe.mp3",
"size": 3113301,
"suffix": "mp3",
"title": "Signe",
"track": 1,
"type": "music",
"year": 1992
},
{
"album": "Unplugged",
"albumId": 1000,
"artist": "Eric Clapton",
"artistId": 114,
"bitRate": 128,
"contentType": "audio/mpeg",
"coverArt": 16012,
"created": "2013-02-24T20:38:08",
"discNumber": 1,
"duration": 224,
"id": 16014,
"isDir": false,
"isVideo": false,
"parent": 16012,
"path": "Eric Clapton/Unplugged/02 Before You Accuse Me.mp3",
"size": 3623226,
"suffix": "mp3",
"title": "Before You Accuse Me",
"track": 2,
"type": "music",
"year": 1992
},
{
"album": "Unplugged",
"albumId": 1000,
"artist": "Eric Clapton",
"artistId": 114,
"bitRate": 128,
"contentType": "audio/mpeg",
"coverArt": 16012,
"created": "2013-02-24T20:38:07",
"discNumber": 1,
"duration": 196,
"id": 16015,
"isDir": false,
"isVideo": false,
"parent": 16012,
"path": "Eric Clapton/Unplugged/03 Hey Hey.mp3",
"size": 3177669,
"suffix": "mp3",
"title": "Hey Hey",
"track": 3,
"type": "music",
"year": 1992
},
{
"album": "Unplugged",
"albumId": 1000,
"artist": "Eric Clapton",
"artistId": 114,
"bitRate": 128,
"contentType": "audio/mpeg",
"coverArt": 16012,
"created": "2013-02-24T20:38:07",
"discNumber": 1,
"duration": 276,
"id": 16016,
"isDir": false,
"isVideo": false,
"parent": 16012,
"path": "Eric Clapton/Unplugged/04 Tears in Heaven.mp3",
"size": 4453706,
"suffix": "mp3",
"title": "Tears in Heaven",
"track": 4,
"type": "music",
"year": 1992
},
{
"album": "Unplugged",
"albumId": 1000,
"artist": "Eric Clapton",
"artistId": 114,
"bitRate": 128,
"contentType": "audio/mpeg",
"coverArt": 16012,
"created": "2013-02-24T20:38:08",
"discNumber": 1,
"duration": 328,
"id": 16017,
"isDir": false,
"isVideo": false,
"parent": 16012,
"path": "Eric Clapton/Unplugged/05 Lonely Stranger.mp3",
"size": 5272070,
"suffix": "mp3",
"title": "Lonely Stranger",
"track": 5,
"type": "music",
"year": 1992
},
{
"album": "Unplugged",
"albumId": 1000,
"artist": "Eric Clapton",
"artistId": 114,
"bitRate": 128,
"contentType": "audio/mpeg",
"coverArt": 16012,
"created": "2013-02-24T20:38:08",
"discNumber": 1,
"duration": 229,
"id": 16018,
"isDir": false,
"isVideo": false,
"parent": 16012,
"path": "Eric Clapton/Unplugged/06 Nobody Knows You When You're Down & Out.mp3",
"size": 3702230,
"suffix": "mp3",
"title": "Nobody Knows You When You're Down & Out",
"track": 6,
"type": "music",
"year": 1992
},
{
"album": "Unplugged",
"albumId": 1000,
"artist": "Eric Clapton",
"artistId": 114,
"bitRate": 128,
"contentType": "audio/mpeg",
"coverArt": 16012,
"created": "2013-02-24T20:38:08",
"discNumber": 1,
"duration": 286,
"id": 16019,
"isDir": false,
"isVideo": false,
"parent": 16012,
"path": "Eric Clapton/Unplugged/07 Layla.mp3",
"size": 4614192,
"suffix": "mp3",
"title": "Layla",
"track": 7,
"type": "music",
"year": 1992
},
{
"album": "Unplugged",
"albumId": 1000,
"artist": "Eric Clapton",
"artistId": 114,
"bitRate": 128,
"contentType": "audio/mpeg",
"coverArt": 16012,
"created": "2013-02-24T20:38:08",
"discNumber": 1,
"duration": 390,
"id": 16020,
"isDir": false,
"isVideo": false,
"parent": 16012,
"path": "Eric Clapton/Unplugged/08 Running on Faith.mp3",
"size": 6270158,
"suffix": "mp3",
"title": "Running on Faith",
"track": 8,
"type": "music",
"year": 1992
},
{
"album": "Unplugged",
"albumId": 1000,
"artist": "Eric Clapton",
"artistId": 114,
"bitRate": 128,
"contentType": "audio/mpeg",
"coverArt": 16012,
"created": "2013-02-24T20:38:08",
"discNumber": 1,
"duration": 217,
"id": 16021,
"isDir": false,
"isVideo": false,
"parent": 16012,
"path": "Eric Clapton/Unplugged/09 Walkin' Blues.mp3",
"size": 3513714,
"suffix": "mp3",
"title": "Walkin' Blues",
"track": 9,
"type": "music",
"year": 1992
},
{
"album": "Unplugged",
"albumId": 1000,
"artist": "Eric Clapton",
"artistId": 114,
"bitRate": 128,
"contentType": "audio/mpeg",
"coverArt": 16012,
"created": "2013-02-24T20:38:08",
"discNumber": 1,
"duration": 222,
"id": 16022,
"isDir": false,
"isVideo": false,
"parent": 16012,
"path": "Eric Clapton/Unplugged/10 Alberta.mp3",
"size": 3593120,
"suffix": "mp3",
"title": "Alberta",
"track": 10,
"type": "music",
"year": 1992
},
{
"album": "Unplugged",
"albumId": 1000,
"artist": "Eric Clapton",
"artistId": 114,
"bitRate": 128,
"contentType": "audio/mpeg",
"coverArt": 16012,
"created": "2013-02-24T20:38:07",
"discNumber": 1,
"duration": 203,
"id": 16023,
"isDir": false,
"isVideo": false,
"parent": 16012,
"path": "Eric Clapton/Unplugged/11 San Francisco Bay Blues.mp3",
"size": 3289280,
"suffix": "mp3",
"title": "San Francisco Bay Blues",
"track": 11,
"type": "music",
"year": 1992
},
{
"album": "Unplugged",
"albumId": 1000,
"artist": "Eric Clapton",
"artistId": 114,
"bitRate": 128,
"contentType": "audio/mpeg",
"coverArt": 16012,
"created": "2013-02-24T20:38:08",
"discNumber": 1,
"duration": 217,
"id": 16024,
"isDir": false,
"isVideo": false,
"parent": 16012,
"path": "Eric Clapton/Unplugged/12 Malted Milk.mp3",
"size": 3499501,
"suffix": "mp3",
"title": "Malted Milk",
"track": 12,
"type": "music",
"year": 1992
},
{
"album": "Unplugged",
"albumId": 1000,
"artist": "Eric Clapton",
"artistId": 114,
"bitRate": 128,
"contentType": "audio/mpeg",
"coverArt": 16012,
"created": "2013-02-24T20:38:08",
"discNumber": 1,
"duration": 471,
"id": 16025,
"isDir": false,
"isVideo": false,
"parent": 16012,
"path": "Eric Clapton/Unplugged/13 Old Love.mp3",
"size": 7564569,
"suffix": "mp3",
"title": "Old Love",
"track": 13,
"type": "music",
"year": 1992
},
{
"album": "Unplugged",
"albumId": 1000,
"artist": "Eric Clapton",
"artistId": 114,
"bitRate": 128,
"contentType": "audio/mpeg",
"coverArt": 16012,
"created": "2013-02-24T20:38:08",
"discNumber": 1,
"duration": 247,
"id": 16026,
"isDir": false,
"isVideo": false,
"parent": 16012,
"path": "Eric Clapton/Unplugged/14 Rollin' & Tumblin'.mp3",
"size": 3984343,
"suffix": "mp3",
"title": "Rollin' & Tumblin'",
"track": 14,
"type": "music",
"year": 1992
}
],
"songCount": 14
},
"status": "ok",
"version": "1.8.0",
"xmlns": "http://subsonic.org/restapi"
}
SHOULD BE THE ALBUM UNPLUGGED ACCORDING TO SUBSONIC WEB INTERFACE
##################################################
Traceback (most recent call last):
File "./test.py", line 28, in <module>
print json.dumps(conn.getAlbum(16012), sort_keys=True, indent=2)
File "/home/nicke/src/crusty/libsonic/connection.py", line 1500, in getAlbum
self._checkStatus(res)
File "/home/nicke/src/crusty/libsonic/connection.py", line 1888, in _checkStatus
raise exc(result['error']['message'])
libsonic.errors.DataNotFoundError: Album not found.
Hi,
when using baseurl with something like 'http://hostname/madsonic' and having port set as '8885'
the constructed url gets 'http://hostname/madsonic:8885/rest' which is wrong as the port has to come directly after the hostname.
I think it would be better to add an parameter hostname so you can construct the url by HOSTNAME + PORT + BASEURL
I am not sure why it seems that I am the first one running into this, perhaps its also an missunderstanding on my side.
I, my server has Subsonic 5.3 (build 4568); and I use your library (v.0.5.1) through an addon for Kodi that I forked.
It seems that the argument 'size' in the function getAlbumList2 doesn't limit the list size :
q = self._getQueryDict({'type': ltype, 'size': size,
'offset': offset, 'fromYear': fromYear, 'toYear': toYear,
'genre': genre})
Even if I hardcode it, the list returns ALL my albums; which makes my addon quite slow.
q = self._getQueryDict({'type': ltype, 'size': 5})
Do you know what may cause this and is there a way to fix it ?
Thanks !
I've ran into this on 2 machines so far. Could it possibly be because requirements.txt
is empty?
% pip install py-sonic
Collecting py-sonic
Downloading https://files.pythonhosted.org/packages/e7/ae/4a48ca9010d8f69714f01a223de66949cbe52c233d231479dcaabb4df1a7/py-sonic-0.7.2.tar.gz
Complete output from command python setup.py egg_info:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/tmp/pip-install-mgl7m7fu/py-sonic/setup.py", line 25, in <module>
requirements = [line for line in open(req_file) if line]
FileNotFoundError: [Errno 2] No such file or directory: '/tmp/pip-install-mgl7m7fu/py-sonic/requirements.txt'
----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-mgl7m7fu/py-sonic/
Note: running python setup.py install
works without errors
I am unable to successfully query a subsonic installation that is behind a nginx proxy/redirect over HTTPS. My subsonic install is very similar to what is outlined here in this guide, so that I'm able to use sane SSL/HTTPS settings and not the config that comes with subsonic. This setup is totally accessible via web browser and other subsonic applications (e.g. D-Sub on android).
If I remove subsonic from behind the proxy/redirect, I can connect to it from py-sonic.
Here's the relevant code in my application where it fails (I have tried other methods besides getArtists as well, they also fail with the same error)
subConn = subsonicConn()
subConn.connect(configOpts.get('Server','url'),
configOpts.get('Server','username'),
configOpts.get('Server','password'),
configOpts.get('Server','port'))
self.artists = subConn.getArtists()
I do not want to supply my url, user, or pass for obvious reasons.. but if you'd like to reproduce this and test a possible solution please let me know and I'll create a guest account for you to test on my subsonic server.
Traceback when call to getArtists is made:
<libsonic.connection.Connection object at 0x7f8a4d4338d0>
Traceback (most recent call last):
File "./vimsonic.py", line 198, in <module>
if __name__ == '__main__': main()
File "./vimsonic.py", line 49, in main
playerInterface(player)
File "./vimsonic.py", line 110, in __init__
self.artists = subConn.getArtists()
File "./vimsonic.py", line 173, in getArtists
self.getIndexes()
File "./vimsonic.py", line 184, in getIndexes
self.indexes = self.conn.getIndexes()
File "/usr/lib/python2.7/site-packages/libsonic/connection.py", line 361, in getIndexes
res = self._doInfoReq(req)
File "/usr/lib/python2.7/site-packages/libsonic/connection.py", line 2549, in _doInfoReq
res = self._opener.open(req)
File "/usr/lib/python2.7/urllib2.py", line 437, in open
response = meth(req, response)
File "/usr/lib/python2.7/urllib2.py", line 550, in http_response
'http', request, response, code, msg, hdrs)
File "/usr/lib/python2.7/urllib2.py", line 475, in error
return self._call_chain(*args)
File "/usr/lib/python2.7/urllib2.py", line 409, in _call_chain
result = func(*args)
File "/usr/lib/python2.7/urllib2.py", line 558, in http_error_default
raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
urllib2.HTTPError: HTTP Error 404: Not Found
nginx config:
location /subsonic {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Host $http_host;
proxy_max_temp_file_size 0;
proxy_pass http://127.0.0.1:4040;
proxy_redirect http:// https://;
}
py-sonic/libsonic/connection.py
Line 75 in ba48888
As in title getSong(id) returns list instead of just dictionary.
>>> s1 = conn.getSong(300000076)
>>> s1.get("song")
[{'id': 300000076, 'parent': 200000006, 'title': 'Marina', 'isDir': False, 'isVideo': False, 'type': 'music', 'albumId': 200000006, 'album': 'The Ultimate Collectio
n - CD1', 'artistId': 100000005, 'artist': 'Azra', 'coverArt': 200000006, 'duration': 183, 'bitRate': 320, 'track': 7, 'year': 2007, 'genre': 'Rock', 'size': 748435
6, 'discNumber': 1, 'suffix': 'mp3', 'contentType': 'audio/mpeg', 'path': 'Azra - The Ultimate Collection (2007)-MuSi/CD 1/07 - Azra - Marina.mp3'}]
>>>
In documentation it says only dict is returned.
I am attempting to install via pip (as provided by MacPorts python 2.7) on MacOSX 10.10.4 and I get the following failure:
Collecting py-sonic
Downloading py-sonic-0.3.4.tar.gz
Complete output from command python setup.py egg_info:
Traceback (most recent call last):
File "<string>", line 20, in <module>
File "/private/tmp/pip-build-l9dx8wop/py-sonic/setup.py", line 21, in <module>
from libsonic import __version__ as version
File "/private/tmp/pip-build-l9dx8wop/py-sonic/libsonic/__init__.py", line 30, in <module>
from connection import *
ImportError: No module named 'connection'
Any suggestions? I am new to using pip.
Nice job on the implementation, but I cannot get the sample code to execute. I copied it from your site and I believe that I am correctly connecting since when I debug, I see the API version number that's running on my Airsonic server (a fork of subsonic). The username, password and salting all seem to be working correctly. When I try to execute .getRandomSongs(size=2), I receive the following error messages:
Traceback (most recent call last):
File "C:\Users\james\Documents\SVSU Python Scripts\pysonic.py", line 8, in
songs = conn.getRandomSongs(size=2)
File "C:\Users\james\AppData\Local\Continuum\anaconda3\lib\site-packages\libsonic\connection.py", line 1374, in getRandomSongs
res = self._doInfoReq(req)
File "C:\Users\james\AppData\Local\Continuum\anaconda3\lib\site-packages\libsonic\connection.py", line 2802, in _doInfoReq
res = self._opener.open(req)
File "C:\Users\james\AppData\Local\Continuum\anaconda3\lib\urllib\request.py", line 525, in open
response = self._open(req, data)
File "C:\Users\james\AppData\Local\Continuum\anaconda3\lib\urllib\request.py", line 543, in _open
'_open', req)
File "C:\Users\james\AppData\Local\Continuum\anaconda3\lib\urllib\request.py", line 503, in _call_chain
result = func(*args)
File "C:\Users\james\AppData\Local\Continuum\anaconda3\lib\site-packages\libsonic\connection.py", line 77, in https_open
return self.do_open(HTTPSConnectionChain, req)
File "C:\Users\james\AppData\Local\Continuum\anaconda3\lib\urllib\request.py", line 1350, in do_open
encode_chunked=req.has_header('Transfer-encoding'))
File "C:\Users\james\AppData\Local\Continuum\anaconda3\lib\http\client.py", line 1277, in request
self._send_request(method, url, body, headers, encode_chunked)
File "C:\Users\james\AppData\Local\Continuum\anaconda3\lib\http\client.py", line 1323, in _send_request
self.endheaders(body, encode_chunked=encode_chunked)
File "C:\Users\james\AppData\Local\Continuum\anaconda3\lib\http\client.py", line 1272, in endheaders
self._send_output(message_body, encode_chunked=encode_chunked)
File "C:\Users\james\AppData\Local\Continuum\anaconda3\lib\http\client.py", line 1032, in _send_output
self.send(msg)
File "C:\Users\james\AppData\Local\Continuum\anaconda3\lib\http\client.py", line 993, in send
self.sock.sendall(data)
AttributeError: 'NoneType' object has no attribute 'sendall'
It looks like the HTTP library is bombing out in the send method. I traced through and I watched that the proper request string is being built up and that's where it stops. I have upgraded all of my Anaconda installation to the latest version of the various tools. I also tried using other requests, like .getUser, but the same thing happens. Any ideas? I'd really much rather use your library than starting to code a REST API from scratch. Thanks!
See discussion in #17 (comment)
Some subsonic-compatible servers (for example supysonic) deliberately only support pre-1.13.0 authentication.
Since pre-1.13 auth is no longer works with py-sonic
(last compatible commit was ad9d24d), it would be awesome to have py-sonic fall back to using pre-1.13.0 authentication either on authentication errors, and/or when a parameter is provided to Connection
.
Thanks for the wonderful library!
Using:
Attempting to create a connection with a subsonic address fails: name.subsonic.org produces an error
Traceback (most recent call last): File "C:\Users\Supreme\Documents\Workspace\Test_Subsonic_API\root\Hello_Sub.py", line 16, in <module> pprint(conn.getStarred2()) File "C:\Users\Supreme\Documents\Workspace\Test_Subsonic_API\libsonic\connection.py", line 1820, in getStarred2 res = self._doInfoReq(req) File "C:\Users\Supreme\Documents\Workspace\Test_Subsonic_API\libsonic\connection.py", line 2394, in _doInfoReq res = self._opener.open(req) File "C:\Python27\lib\urllib2.py", line 431, in open response = self._open(req, data) File "C:\Python27\lib\urllib2.py", line 449, in _open '_open', req) File "C:\Python27\lib\urllib2.py", line 409, in _call_chain result = func(*args) File "C:\Python27\lib\urllib2.py", line 1227, in http_open return self.do_open(httplib.HTTPConnection, req) File "C:\Python27\lib\urllib2.py", line 1197, in do_open raise URLError(err) urllib2.URLError: <urlopen error [Errno 10061] No connection could be made because the target machine actively refused it>
The scrobble function in libsonic does not seem to support specifying the play_date time. Documentation here:
http://www.subsonic.org/pages/api.jsp#scrobble
The function on line 2689
in connection.py
:
def _doBinReq(self, req):
res = self._opener.open(req)
contType = res.info().getheader('Content-Type')
if contType:
if contType.startswith('text/html') or \
contType.startswith('application/json'):
dres = json.loads(res.read())
return dres['subsonic-response']
return res
Throws the error:
'HTTPMessage' object has no attribute 'getheader'
Replacing getheader()
with get()
resolves the issue. I found this solution here when researching the issue.
I made this issue to identify what issues there are that are preventing this from being used with https://github.com/airsonic/airsonic.
I am using https://github.com/Prior99/mopidy-subidy, which uses py-sonic, and am running into errors along with the following log message:
WARNING Connecting to subsonic failed when loading list of playlists.
I'll try to spend some time later this week to figure out what is going wrong.
When I request the index using getIndexes
, I get a response with lastModified
is X
(where X
is time in milliseconds). When I query getIndexes
again using ifModifiedSince
set to X - 1
, I don't get a result, but I would expect the same response because X - 1
is a timestamp in the past.
Now I understand that this problem is because ifModifiedSince
is translated using this method, but that is inconsistent with the API, which requires the ifModifiedSince
parameter to be in milliseconds.
I would suggest to remove the _ts2milli
method.
Hi!
I see you have python3 working in a branch. I use libsonic for a Subsonic skill with the Mycroft voice assistant system (https://mycroft.ai). They recently switched to python3. I am moving my module over to python3, but I have py-sonic as a dependency, and I can't 'pip install' the python3-compatible version. This means the skill isn't able to be nicely distributed/used without the users doing some extra work to install the python3 branch of py-sonic.
Are you able to merge in the python3 support and release a new version to pip (PyPI?)?
Thanks!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.