bdbenim / stash-empornium Goto Github PK
View Code? Open in Web Editor NEWA userscript and backend server to help with uploading to EMP.
License: The Unlicense
A userscript and backend server to help with uploading to EMP.
License: The Unlicense
What I've noticed is that most porn scenes follow a formulaic approach to how a scene unfolds. By the number of actors, gender, and basic tags, you can string together an EMP-passable text description.
For instance, if a scene has a male and a female actor:
"This is a scene involving a male and a female."
For tags associated (especially ones with AI generation (#6) as that can pick out the sequence and timing) you can almost certainly bet:
"The female begins with <blowjob> and moves to intercourse involving <positions>. The scene concludes with the man finishing <locations>."
Even that may be more than enough.
`2023-10-07 13:17:42,585 - INFO - Generating submission for scened ID 59971 including screens.
2023-10-07 13:17:43,303 - ERROR - Exception while serving /fill
Traceback (most recent call last):
File "/usr/local/lib/python3.10/site-packages/waitress/channel.py", line 428, in service
task.service()
File "/usr/local/lib/python3.10/site-packages/waitress/task.py", line 168, in service
self.execute()
File "/usr/local/lib/python3.10/site-packages/waitress/task.py", line 456, in execute
for chunk in app_iter:
File "/usr/local/lib/python3.10/site-packages/werkzeug/wsgi.py", line 256, in __next__
return self._next()
File "/usr/local/lib/python3.10/site-packages/werkzeug/wrappers/response.py", line 32, in _iter_encoded
for item in iterable:
File "/usr/local/lib/python3.10/site-packages/flask/helpers.py", line 115, in generator
yield from gen
File "/emp_stash_fill/emp_stash_fill.py", line 344, in generate
with Image.open(studio_img_file[1]) as img:
File "/usr/local/lib/python3.10/site-packages/PIL/Image.py", line 3280, in open
raise UnidentifiedImageError(msg)
PIL.UnidentifiedImageError: cannot identify image file '/tmp/tmpdz7daizc.svg'
2023-10-07 13:18:06,218 - INFO - Generating submission for scened ID 59971 including screens.
2023-10-07 13:18:06,557 - ERROR - Exception while serving /fill
Traceback (most recent call last):
File "/usr/local/lib/python3.10/site-packages/waitress/channel.py", line 428, in service
task.service()
File "/usr/local/lib/python3.10/site-packages/waitress/task.py", line 168, in service
self.execute()
File "/usr/local/lib/python3.10/site-packages/waitress/task.py", line 456, in execute
for chunk in app_iter:
File "/usr/local/lib/python3.10/site-packages/werkzeug/wsgi.py", line 256, in __next__
return self._next()
File "/usr/local/lib/python3.10/site-packages/werkzeug/wrappers/response.py", line 32, in _iter_encoded
for item in iterable:
File "/usr/local/lib/python3.10/site-packages/flask/helpers.py", line 115, in generator
yield from gen
File "/emp_stash_fill/emp_stash_fill.py", line 344, in generate
with Image.open(studio_img_file[1]) as img:
File "/usr/local/lib/python3.10/site-packages/PIL/Image.py", line 3280, in open
raise UnidentifiedImageError(msg)
PIL.UnidentifiedImageError: cannot identify image file '/tmp/tmp8txhcgvf.svg'`
Move upload generation into discrete jobs with unique IDs so that they can be triggered from different origins (e.g. within stash or the webui) and then retrieved by the userscript.
Male performers currently affect the presence of tags like "tattoo", "piercings", and "tattoos.and.piercings". They also affect the presence of tags relating to nationality (e.g. "american", "australian", etc).
I recall seeing some efforts in the code to discount male performers from certain tags when I read the PR adding this functionality, so I'm not sure if this is an oversight/bug or not. I'm not even sure that this needs to be an option/setting (i.e. males should just always be discounted from tagging, or if they're tagged then they should use the male tag variants). Emp either doesn't care about male performers when it comes to tags, or in the cases that they do, they use separate tags with 'male' in them—it's almost universally true that any genderless tag implies female on Emp.
E.g. "tattoo" is the popular tag, but it's pretty much only applied to the female talent, there's a separate tag "tattooed.male" for males. It's worth noting that "tattooed.female" also exists but only has like 7% of the popularity of the "tattoo" tag—this is often the case with the more specific "female" gendered tags, this specificity is just not as widely used because the genderless tags are assumed female.
I'd need to test more widely to see what other tags are inherited from the male performers—these might be the only ones, or there might be others that have been missed.
Add integration with deluge's JSON RPC API
Add integration with qbittorrent's web API
would be nice to have {codec} = {hevc|h264|avi|mpeg2|etc} for Custom Formats use
In the config file, include an option to default to upload anonymously true/false
If a scene has a tag which is not found in the [empornium.tags]
section of config.ini
, the user should be presented with a suggested tag mapping, which they can accept, override, or ignore. Here's what I believe would need to go into that:
config.ini
Add integration with rutorrent's XMLRPC API. To allow adding .torrent
files directly and specifying the location of files on disk.
Gettting this traceback:
2023-10-19 03:32:12,161 - INFO - Making torrent 2023-10-19 03:32:13,787 - INFO - Generating media info 2023-10-19 03:32:15,215 - INFO - Uploading cover 2023-10-19 03:32:15,215 - ERROR - Exception while serving /fill Traceback (most recent call last): File "/usr/local/lib/python3.10/site-packages/waitress/channel.py", line 428, in service task.service() File "/usr/local/lib/python3.10/site-packages/waitress/task.py", line 168, in service self.execute() File "/usr/local/lib/python3.10/site-packages/waitress/task.py", line 456, in execute for chunk in app_iter: File "/usr/local/lib/python3.10/site-packages/werkzeug/wsgi.py", line 256, in __next__ return self._next() File "/usr/local/lib/python3.10/site-packages/werkzeug/wrappers/response.py", line 32, in _iter_encoded for item in iterable: File "/usr/local/lib/python3.10/site-packages/flask/helpers.py", line 115, in generator yield from gen File "/emp_stash_fill/emp_stash_fill.py", line 839, in generate cover_remote_url = img_host_upload(img_host_token, cookies, cover_file[1], cover_mime_type, cover_ext) File "/emp_stash_fill/emp_stash_fill.py", line 324, in img_host_upload digest = hashlib.file_digest(f, hashlib.md5).hexdigest() AttributeError: module 'hashlib' has no attribute 'file_digest'
Downgrading to 0.7.0 works fine.
hashlib.file_digest does not exist in python 3.10
It does exists in 3.11.
Can the required version of python in the dockerfile be bumped?
Documentation of command line arguments should be added to README.me or another documentation file
Either create a second userscript or extend the functionality of the existing userscript to add an upload button to stash's UI. This button will trigger a job to start as in #128 before navigating the user to upload.php
. If possible, pre-populate the upload form with the result of the job. If not, a menu can be displayed to the user allowing them to select the job to retrieve.
Allow configuring the following from the UI:
Having issues when using the jinja template:
Traceback (most recent call last):
File "/usr/local/lib/python3.10/site-packages/waitress/channel.py", line 428, in service
task.service()
File "/usr/local/lib/python3.10/site-packages/waitress/task.py", line 168, in service
self.execute()
File "/usr/local/lib/python3.10/site-packages/waitress/task.py", line 456, in execute
for chunk in app_iter:
File "/usr/local/lib/python3.10/site-packages/werkzeug/wsgi.py", line 256, in __next__
return self._next()
File "/usr/local/lib/python3.10/site-packages/werkzeug/wrappers/response.py", line 32, in _iter_encoded
for item in iterable:
File "/usr/local/lib/python3.10/site-packages/flask/helpers.py", line 115, in generator
yield from gen
File "/emp_stash_fill/emp_stash_fill.py", line 734, in generate
title = render_template_string(TITLE_TEMPLATE, **{
File "/usr/local/lib/python3.10/site-packages/flask/templating.py", line 164, in render_template_string
return _render(app, template, context)
File "/usr/local/lib/python3.10/site-packages/flask/templating.py", line 133, in _render
rv = template.render(context)
File "/usr/local/lib/python3.10/site-packages/jinja2/environment.py", line 1301, in render
self.environment.handle_exception()
File "/usr/local/lib/python3.10/site-packages/jinja2/environment.py", line 936, in handle_exception
raise rewrite_traceback_stack(source=source)
File "<template>", line 1, in top-level template code
File "/usr/local/lib/python3.10/site-packages/jinja2/utils.py", line 83, in from_obj
if hasattr(obj, "jinja_pass_arg"):
jinja2.exceptions.UndefinedError: 'str object' has no attribute 'strftime'
Looks like the current data is being pulled only as a string in the .py code?
2023-10-07 10:08:33,824 - INFO - Reading config from /config/config.ini
2023-10-07 10:08:33,832 - INFO - Template "fakestash-v2" has a new version available in the default-templates directory
Traceback (most recent call last):
File "/emp_stash_fill/emp_stash_fill.py", line 103, in
TITLE_DEFAULT = conf["backend"].get("title_default", "[{studio}] {performers} - {title} ({date}){resolution}").value # type: ignore
AttributeError: 'str' object has no attribute 'value'
As mentioned in #63, this would allow the userscript to attach the generated .torrent
file to the upload without requiring the user to navigate to it.
For security reasons, it is normally not possible for a script running on the page to attach a file without user interaction, as this would make it trivial for any malicious webpage to steal files from the user's computer without their knowledge. However, since we are actually generating the contents of this file ourselves, we don't actually care what file it comes from as long as we can somehow upload it to EMP. As such, here is how I see this feature eventually working:
.torrent
file directly from the backend server and stores the data in memorycheck for dupes
and Upload torrent
buttons to use this data stored in memory instead of the file referenced by the normal file chooser inputAlthough complex, this ought to succeed in circumventing the limitations of how scripts can interact with file inputs. The main issue will be maintainability, as this creates another possible breaking point of the script if the process of uploading to EMP ever changes. If the script is not updated at that time, it will cease to work entirely for uploading torrents. To prevent this, it should be easy for the user to disable this functionality if necessary. It also might be possible for the script to hijack existing code for submitting the upload instead of reverse engineering it. This would both save effort and reduce likelihood of the feature breaking in some future update to EMP.
Title basically.
Add a button on a stash scene's panel . The edit tab makes most sense, as the workflow would be to scrape the scene, edit all the tags/metainfo etc, then upload. The button would basically open an empornium upload.php tab, and pre-populate that scenes id number into the "stash ID" field provided by the script. It could automatically activate the "fill form" button provided by the script. You'd have to assume that the user is already authenticated/logged in, so maybe/maybe not handle that "error", and you'd need to make .sx or .is domain configurable in the config I guess.
Workflow becomes:
This enhancement would be improved by caching of image urls (which you've implemented but not released I believe), caching of the .torrent generation job being complete, etc, etc. So even if an upload tab is forgotten/closed by the user at some point they can simply click the button on the scene again and have it be "instantly" ready.
Potential issue:
If the user processes like 10 scenes at a time, it might be painful to associate the correct .torrent with each tab. It would be great if you could inject this as a part of the script. It's entirely possible to just make the .torrent in client-side js (see anthelion's upload.php, the js is all there). This should be it's own issue (lol). And this approach probably isn't great, because then caching the resulting .torrent becomes problematic should you need to re-run the script.
Not getting any info on the logs about the image not being suitable, but it isn't making it into the fakeplayer.
Only one session with the image host is established on startup, which eventually expires, causing all subsequent uploads to fail. This session should be periodically renewed or reestablished to prevent failures.
Added on behalf of @preshow6237
Add a button on a stash scene's panel . The edit tab makes most sense, as the workflow would be to scrape the scene, edit all the tags/metainfo etc, then upload. The button would basically open an empornium upload.php tab, and pre-populate that scenes id number into the "stash ID" field provided by the script. It could automatically activate the "fill form" button provided by the script. You'd have to assume that the user is already authenticated/logged in, so maybe/maybe not handle that "error", and you'd need to make .sx or .is domain configurable in the config I guess.
** Workflow becomes:**
This enhancement would be improved by caching of image urls (which you've implemented but not released I believe), caching of the .torrent generation job being complete, etc, etc. So even if an upload tab is forgotten/closed by the user at some point they can simply click the button on the scene again and have it be "instantly" ready.
** Potential issue:**
If the user processes like 10 scenes at a time, it might be painful to associate the correct .torrent with each tab. It would be great if you could inject this as a part of the script. It's entirely possible to just make the .torrent in client-side js (see anthelion's upload.php, the js is all there). This should be it's own issue (lol). And this approach probably isn't great, because then caching the resulting .torrent becomes problematic should you need to re-run the script.
TOML has better support than ini
for more complex data such as arrays and data types such as integers, booleans, etc. As well, tomlkit
appears to be a more mature parsing library than configupdater
, which has some formatting bugs when writing config files.
Once implemented, the server should migrate the user's configuration file to the new format so that no user intervention is required and all settings are preserved.
I considered YAML
as well due to its more visually obvious representation of nested hierarchies, but I don't think it's the best option for two reasons:
pyyaml
, which is recommended in case you need to write and not only read yaml
files, does not appear to preserve comments or other formattingYAML
is less user-friendly to write for users who don't use code editors because it relies on correct indentation
ini
and therefore might present a greater challenge either for the automated migration process or for user adjustmentThe default title_default = [{studio}] {performers} - {title} ({date})[{resolution}]
is a fairly non-standard release title format. Would like to propose a few tweaks.
New config option:
replace spaces with periods
= false|true
New config variables:
titleslug
(Site Title's
becomes SiteTitles
2ddate
(2023-10-14
becomes 23.10.14
)
Change config option
title_default
= {titleslug} {2ddate} {title} {performers} {codec} {resolution}
remove commas between multiple performers
[Blender Institute] Big Buck Bunny, Frank, Rinky, Gimera - Big Buck Bunny (2008-05-10)[1080p]
becomes BlenderInstitute.08.05.10.Big.Buck.Bunny.Big.Buck.Bunny.Frank.Rinky.Gimera.x264.1080p
I'd like to be able to check a box on the upload page that tells the script to generate a torrent with both the scene and the associated gallery if one exists. This will require a few things to be done:
Essentially the title, inherit the biographical data from any performer and convert them to tags. But to expand on the idea:
Simple (limited scope, straight forward mapping):
This obviously only applies to female performers, there's different tags for male performers. They often go untagged on EMP though, so not sure if they need to be implemented (sorry gaybros).
Complex (so therefore less important XD):
Tangentially related, scene composition tags (male performers are often poorly tagged, so this might not be a good idea. You also have "uninvolved performers", i.e. in cuck scenes or whatever...):
Related to #64, a couple of proposed changes to how .torrent
files are saved:
.torrent
to torrent client
This will likely be broken into multiple issues, but since the goal for all will be the same I'm just creating one here for now.
Add the ability to integrate with torrent clients via their APIs so that .torrent
files can be added automatically and pointed to the correct location of files on disk based on the paths returned by stash. A custom label could also be specified allowing rules to be created in the client, such as seed time or upload targets.
Major clients to be targeted are:
Currently most of the backend is written on the assumption that it will process only one upload at a time. This causes problems when more than one upload is being processed in parallel, due to issues with race conditions or simply global variables that should be local.
Hoping someone else can help with this since I'm not great with JS. Lately I'm getting a bug where the final JSON string containing the rendered template, title, tags, etc is not being parsed by the userscript.
For troubleshooting, I added a delay followed by an additional JSON response to emp_stash_fill.py
:
yield json.dumps(
{
"status": "success",
"data": {
"message": "Done",
"fill": {
"title": title,
"cover": cover_remote_url,
"tags": " ".join(tags),
"description": description,
"torrent_path": torrent_path,
"file_path": stash_file["path"],
},
},
}
)
time.sleep(1)
return json.dumps({
"status": "success",
"data": {
"message": "Finished"
}
})
With this code, the first object is parsed successfully, and the new last object is ignored. So the issue doesn't seem to be with the returned data, but something about being the last object in the stream.
Copied for tracking since #57 was deleted:
Essentially the title, inherit the biographical data from any performer and convert them to tags. But to expand on the idea:
Simple (limited scope, straight forward mapping):
- Eye colour mapping to tags: blue.eyes, brown.eyes, green.eyes, ...
- Tit "status" mapping to tags: natural.tits, or fake.tits
- Hair colour mapping to tags: blonde, brunette, black.hair, redhead, ...
- Ethnicity mapping to tags: caucasian, asian, ebony, latina, ...
- Tattoos mapping to tags: tattoo
- Piercings mapping to tags: piercings
This obviously only applies to female performers, there's different tags for male performers. They often go untagged on EMP though, so not sure if they need to be implemented (sorry gaybros).
Complex (so therefore less important XD):
- Nationality mapping to tags—this is pretty "easy", but the scope is large, so I've categorised it as "complex": australian, english, scottish, welsh, irish, russian, ukranian, croatian, latvian, czech, french, mexican, german, ...
- Nationality "conglomerations" mapping to tags: british {welsh, english, scottish, ...}, european {french, german, czech, russian, ...}, ...
- Breast size mapping to tags: tiny.tits, small.tits, medium.tits, big.tits huge.tits, ...
- Breast "combo" mapping to tags: big.natural.tits, big.fake.tits, ...
- Age (at time of performance) to tags—EMP sucks at age tags: teen, milf (when they're 20 I guess??,), grandmother (when they're 30 I guess??).
Tangentially related, scene composition tags (male performers are often poorly tagged, so this might not be a good idea. You also have "uninvolved performers", i.e. in cuck scenes or whatever...):
- 1on1, threesome, foursome (just count the number of performers and spit these out, regardless of gender)
- ffm, 3females.1male, 4females.1male, 5females.1male, ..., reverse.gangbang (anything that's 3female.1male or more)
- mmf, 1female.3males, 1female.4males, 4female.5males, ..., gangbang (anything that's 1female.3male or more)
- mmff, 3females.2males, 4females.2males, ... 2females.3males, 2females.4males, ..., orgy (anything that's 3females.2males, or 2females.3males or more)
- probably a whole lot more tags for lesbian stuff, gay stuff, or trans stuff
If this is pulling from Stash as-is. would be helpful to parse the date into the YY.MM.DD
format standard as referenced here: https://scenerules.org/html/2008_XXXPAY.html
This would increase compatibility with https://github.com/Whisparr/Whisparr
Nothing is making it through, even stashdb ones aren't getting to the final template.
`2023-09-24 13:18:48,432 - ERROR - Exception while serving /fill
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/requests/models.py", line 971, in json
return complexjson.loads(self.text, **kwargs)
File "/usr/local/lib/python3.9/json/init.py", line 346, in loads
return _default_decoder.decode(s)
File "/usr/local/lib/python3.9/json/decoder.py", line 337, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/usr/local/lib/python3.9/json/decoder.py", line 355, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/waitress/channel.py", line 428, in service
task.service()
File "/usr/local/lib/python3.9/site-packages/waitress/task.py", line 168, in service
self.execute()
File "/usr/local/lib/python3.9/site-packages/waitress/task.py", line 456, in execute
for chunk in app_iter:
File "/usr/local/lib/python3.9/site-packages/werkzeug/wsgi.py", line 289, in next
return self._next()
File "/usr/local/lib/python3.9/site-packages/werkzeug/wrappers/response.py", line 32, in _iter_encoded
for item in iterable:
File "/usr/local/lib/python3.9/site-packages/flask/helpers.py", line 118, in generator
yield from gen
File "/emp_stash_fill/emp_stash_fill.py", line 159, in generate
stash_response_body = stash_response.json()
File "/usr/local/lib/python3.9/site-packages/requests/models.py", line 975, in json
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)`
is there a way to import your port/url to a variable instead of hardcoding in localhost:9999 ?
I don't know much about how folk get their really long run-time ultra-compressed gifs that don't look like garbage. But assuming this conversion is relatively straight forward and uses foss that can be included in the docker image (rather than another web api or something—though I suppose that would also be feasible), then:
Stash already generates a generic preview .mp4 video that's 10 seconds long, this seems like a convenient source for automatic conversion to a cover.gif. Unfortunately they sample from the very start of the video, so often the leading frames in these previews are garbage.
Alternatively/in addition, and more challenging from a development point of view, markers conveniently generate a series of little .mp4s that are 20 seconds long. You could envision a few options here:
The use of markers auto-generated .mp4s might be a red herring given their length and the likely need to sub-sample them anyway. It might actually be better/easier to run ffmpeg on the scene's file directly using only the markers time-points.
2023-10-05 08:14:23,736 - INFO - Template "fakestash-v2" has a new version available in the default-templates directory Traceback (most recent call last): File "/emp_stash_fill/emp_stash_fill.py", line 102, in <module> for k,v in conf["templates"].to_dict(): ValueError: too many values to unpack (expected 2) 2023-10-05 18:49:06,514 - INFO - Template "fakestash-v2" has a new version available in the default-templates directory Traceback (most recent call last): File "/emp_stash_fill/emp_stash_fill.py", line 102, in <module> for k,v in conf["templates"].to_dict(): ValueError: too many values to unpack (expected 2) 2023-10-06 15:14:37,431 - INFO - Template "fakestash-v2" has a new version available in the default-templates directory Traceback (most recent call last): File "/emp_stash_fill/emp_stash_fill.py", line 102, in <module> for k,v in conf["templates"].to_dict(): ValueError: too many values to unpack (expected 2)
Would like the ability from the scenes view, select multiple scenes and toggle an option via Plug-in to Create Pack
.
Pack would then generate a folder in a specified directory with the files hardlinked into it.
Related to #69 and #70. Add a listener to the upload button in the userscript and transfer the .torrent
file to the client after the user submits the upload, whether by copying to a directory or transferring via an API.
Update:
Since #70 is now closed covering three major torrent clients, the goal of this enhancement is changed from adding the torrent file to the client after submitting the upload, to starting the torrent after submitting. Multiple output directories will remain supported, but (for now at least) complex behaviour involving copying to different directories at different times is not planned. Starting uploads on submission will be added for torrent client APIs, but not for watch directories.
Implementation will require a couple of other tasks:
ambitious, but if this could be added to generate tags automatically? https://github.com/cc1234475/stashtag
Normally no issues, but this one scene is causing problems.
2023-09-24 14:29:09,966 - INFO - Uploading cover
2023-09-24 14:29:11,293 - ERROR - Exception while serving /fill
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/waitress/channel.py", line 428, in service
task.service()
File "/usr/local/lib/python3.9/site-packages/waitress/task.py", line 168, in service
self.execute()
File "/usr/local/lib/python3.9/site-packages/waitress/task.py", line 456, in execute
for chunk in app_iter:
File "/usr/local/lib/python3.9/site-packages/werkzeug/wsgi.py", line 289, in next
return self._next()
File "/usr/local/lib/python3.9/site-packages/werkzeug/wrappers/response.py", line 32, in _iter_encoded
for item in iterable:
File "/usr/local/lib/python3.9/site-packages/flask/helpers.py", line 118, in generator
yield from gen
File "/emp_stash_fill/emp_stash_fill.py", line 409, in generate
cover_remote_url = img_host_upload(img_host_token, cookies, cover_file[1], cover_mime_type, cover_ext)
File "/emp_stash_fill/emp_stash_fill.py", line 130, in img_host_upload
return response.json()["image"]["image"]["url"]
KeyError: 'image'
is there a way to use the site logos instead of the stash box on the default template?
2023-10-12 07:34:33,011 - INFO - Generating submission for scene ID 63102 including screens.
2023-10-12 07:34:33,054 - ERROR - Unknown studio logo file type: image/webp
When I click fill form
I get no text generated on the browser page and the above is the only log notes shown.
Since configupdater
is used to handle the config files instead of the builtin configparser
, changes can be made to the config programmatically without deleting comments or otherwise changing formatting in a way the user would not expect. This means that if new config options are added in the future to default.ini
, such as in #14, then these new default values can automatically be added to the user's config. This should be accompanied by a log message notifying them of the new option so that they can configure it.
This relates to #17 but doesn't entirely resolve it.
Include MediaInfo into Requirements and include a generated output of its scan results.
In order to hit the 8 tag minimum, you can use things like year
year.month
year.month.day
codec
small things like that save time.
Animated WebP is not supported
latest docker build
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.