Giter VIP home page Giter VIP logo

mpv-scripts's Introduction

My Lua scripts

A collection of scripts I have written for mpv over the years. Small ones are kept in this repository, larger ones have their own. Please keep in mind that not all of these scripts are maintained, and some of these were experiments which didn't pan out. Nevertheless, feel free to submit issues if something breaks, and if you have any interesting script ideas feel free to make a suggestion in the discussions tab.

after-playback

Sends commands to nircmd (windows only) on playback finish. Commands include sleep, hibernate, shutdown, lock. Full list is in the file

Uses nircmd (windows only) to change the resolution and refresh rate of the monitor to match the playing video. Saves the original monitor resolution and reverts changes on exit and when hotkey is pressed. Full description in file.

Creates an scrollable list of chapters to allow more granular chapter selection. Requires scroll-list

command-timeout

Sends an input command after a specified delay. I suggest using the newer delay-command.lua instead. This script is deprecated.

Automatically scans the directory of the currently loaded file and loads any valid cover art into mpv as additional video tracks. Has options for selecting what file names and types are considered valid.

cycle-commands

Cycles through a series of commands on a keypress. Each iteration of the cycle can contain as many commands as one wants. Syntax details are at the top of the file.

cycle-profile

Cycles through a list of profiles sent via a script message and prints the profile-desc to the OSD. More details at the top of the file

delay-command

An extremely simple script which executes an input.conf command only after a set delay.

display-profiles

Automatically applies profiles when the mpv window is moved to a new display

display-name

A simple script designed for windows that saves the name of the monitor that mpv is using into the display_name field of the shared_script_properties and user-data properties. This means that one can use conditional auto profiles with the name of the monitor. This is necessary on windows because the default display names that mpv uses are in the form \\.\DISPLAY#, which are completely useless for setting persistent profiles as the numbers can change between boots or display configurations.

This script uses the lsdvd commandline utility to allow users to view and select titles for DVDs from directly within mpv. The browser is interractive and allows for both playing the selected title, or appending it to the playlist. It is designed to be used stand-alone, or as an addon for file-browser. It also has automatic playlist support for DVDs.

editions-notification

Prints a message on the OSD if editions are found in the file, and temporarily switches the osd-playing-message to the editions-list property when switching. This makes it easier to tell the number and names while navigating editions.

A universal no-dependency file browser that uses mpv's OSD.

ftp-compat

Changes some options when using the ftp protocol for better user experience

keep-session

Automatically saves the current playlist on exit and allows the user to reload it next time they boot mpv

music-mode

Switches to a music profile when an audio file is being played and switches back when a non-audio file is played

onedrive-hook

Automatically converts a onedrive share link into a direct path which mpv can play, Windows only.

Uses mpv-user-input to create an open file dialogue box.

ordered-chapters-playlist

A script to point the player towards an ordered chapters playlist for devices which don't have direct access to their file systems.

pause-indicator

Prints a pause icon in the middle of the screen when mpv is paused

playlist-shuffle

shuffles the playlist and moves the current file to the start of the playlist

profile-command

Parses a script-opt and sends it as a command. Allows input commands to be sent via profiles.

An API that provides scripts with a universal interface for reading text files. This allows scripts to easily read network files over a variety of protocols.

save-playlist

A script for saving m3u playlists based on mpvs current internal playlist. Users can set the name and directory to save the file in the initial script message, or can enter custom strings in the osd.

This script requires mpv-user-input.

A lua module to easily allow the creation of interactive scrollable lists.

Allows in-player searching of keybinds, commands, properties, and options, and displays the results on the OSD.

Requires scroll-list and user-input.

Implements support for Matroska hard segment linking. This is not the same as ordered chapters, which mpv already supports natively.

show-errors

Prints error messages onto the OSD

Allows you to configure advanced subtitle track selection based on the current audio track and the names and language of the subtitle tracks.

syncplay-compat

Changes some settings to work well with Syncplay. Currently designed to provide support for local playlists.

temp-profiles

Allows you to apply a profile with a timeout, after which another profile is called to revert the changes. Works well with osc layout changes.

An API to allow scripts to request text input from the user over the OSD. The script is built around a modified version of mpv's console.lua, so supports almost all the same keybinds.

A script that allows the mpv user-data property to be set at launch and runtime using config files and script-opts.

vars

This script allows users to set custom variables which can be used in commands and profiles.

youtube-search

A script that allows users to search and open youtube results from within mpv. Requires scroll-list, user-input, curl, and a youtube API key. Alternatively, an Invidious frontend can be used for searches without requiring an API key. See the file header for details.

mpv-scripts's People

Contributors

cogentredtester avatar keiviv 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

mpv-scripts's Issues

keep-session.lua: "auto_load" resize mpv window although mpv.conf "auto-window-resize=no"

Without keep-session.lua :

  1. Set auto-window-resize=no in mpv.conf
  2. Open mpv
  3. Set whatever size for mpv window
  4. Load a video file
  5. mpv window keep his original size

With keep-session.lua :

  1. Set auto-window-resize=no in mpv.conf
  2. Add keep-session.lua to "scripts" path
  3. Open mpv
  4. Set whatever size for mpv window
  5. Load a video file
  6. mpv keep his original window size
  7. Close mpv
  8. Open mpv
  9. keep-session.lua auto_load the last video file
  10. mpv change his window size

Expected behaviour
10. mpv keep his original window size

Please complete the following information:

  • OS: Windows 11
  • mpv version v0.36.0-707-gcb2b579f
  • log.log

New cycle-commands syntax error with sharp sign

Hello,

I'm trying to convert my cycle-commands syntax to the new format, but it seems to fail each time a sharp sign must be used:

V script-message cycle-commands [ [["set","sub-font","Sans"],["set","sub-color","#FFFF00"]] , [["set","sub-font","Sans"],["set","sub-color","#000000"]] ]

It gives the following error:

[cycle_commands] command syntax incorrect for string: [ [["set","sub-font","Sans"],["set","sub-color","
[cycle_commands] if you see quotes around the above string then try removing them from input.conf

Thanx for the help,
Best regards,
Dupond

script-name: keep-session.lua

#####################################
Is this a request for a brand new script?
If so please make a Discussion thread instead
#####################################

Is your feature request related to a problem? Please describe.
No

Describe the solution you'd like

  1. Keep multiple sessions (playlist)
  2. add two commmand:
    session-prev: Jumps to the previous playlist of history, if there is one.
    session-next: Jumps to the next playlist of history, if there is one.

YouTube Search Feature Request: use Invidious/Newpipe API as an option?

I have succesfully made MPV into my personal dream player, thanks in no small part to plug-in developers like you. YouTube Search was a plugin I initially avoided because I didn't want to log into my Gmail Account just to fetch the required YT API, but one day I decided "to heck with it" because I do most of my YT Viewing through MPV anyway. So I opened a temporary container in FireFox, got my API Key, plugged it into this plugin, and I've been happily using it since.

....But here's the thing. What if I wanted to reccomend this plugin to someone who doesn't have a Google/GMail account, and is unwilling to make one just to use this plugin? What workaround could they use?

My proposal? Invidious API. Or Newpipe API. Either option works. I don't know if it could work with this plugin, but I can only presume it's possible because of the many apllications that offer to use Invidious/Newpipe API as well as Google API, including FreeTube, NewPipe (duh), etc.

[cycle-profile] How to restore default profile?

I've added this to mpv.conf:

[profile1]
profile-restore=copy
brightness=-50
contrast=100

And this to input.conf:

W   script-message cycle-profiles "profile1"

And it applies the custom profile correctly, but how do I restore the default one?
I've tried script-message cycle-profiles "profile1;profile1 restore" with no success.

`command-timeout.lua`: `screenshot` command doesn't print any OSD message

Describe the bug
Running script-message command-timeout ["screenshot"] 2 works but the OSD message (Screenshot - /path/to/screenshot) isn't printed, unlike when running screenshot.

To Reproduce

  1. Open any video file with $ mpv --no-really-quiet /path/to/video
  2. In mpv, open the console
  3. Run screenshot
  4. Screenshot - /path/to/screenshot, is displayed in the terminal, in mpv console and as an OSD message
  5. Run script-message command-timeout ["screenshot"] 2
  6. The screenshot message is only printed in the terminal and in mpv console, not in the OSD

Expected behaviour
script-message command-timeout ["screenshot"] 2 should behave like screenshot (the OSD message should be printed in mpv window)

Please complete the following information:

  • OS: Linux
  • mpv version: mpv 0.35.0

youtube-search: curl not returning search list

Describe the bug
The script is not getting search results after giving user input.

To Reproduce
Steps to reproduce the behaviour:

  1. Enter a search query
  2. Press enter

Expected behaviour
Returns a list of search results.

Please complete the following information:

  • OS: Linux (Nobara Linux 37)
  • mpv version: 0.35.0
  • mpv.log

Additional context
I get this in my console.

[youtube_search] Search did not return a results list
[youtube_search] search failed - attempting fallback
[youtube_search] curl: (3) URL using bad/illegal format or missing URL
[youtube_search]
[youtube_search] Search did not return a results list

curl version:

curl 7.85.0 (x86_64-redhat-linux-gnu) libcurl/7.85.0 OpenSSL/3.0.8 zlib/1.2.12 libidn2/2.3.4 nghttp2/1.51.0
Release-Date: 2022-08-31
Protocols: file ftp ftps http https 
Features: alt-svc AsynchDNS GSS-API HSTS HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz SPNEGO SSL threadsafe UnixSockets

Edit: added curl version if needed

[profile-command.lua] How to use?

I haven't understoond well what's the purpose of this script; does it allow the use of profile-conditional key bindings? So I could use RIGHT/LEFT arrow while viewing image files to go to the next /previous file? Currentlu I use the following auto profile:

[image-only]
profile-cond=p['current-tracks/video/image'] and not p['current-tracks/video/albumart']
profile-restore=copy
osd-playing-msg=

My long-time desire would be to use different key bindings depending on the profile I'm using. Does this script make this possible?

How to specify no-osd with the new syntax of the Cycle-commands plugin?

Hello!

Thanx for keeping maintaining and improving your code; the new syntax is great! Easier to read, and efficient!

Since the command is now written to the OSD, is there a way to set "no-osd"?
The problem is that only the last subset of the command is written to the OSD, so it doesn't always make sense; for example let's say that you have the following command:

V script-message cycle-commands "set sub-font Sans ; set sub-color \"#FFFF00\" ; set sub-ass-override force" "set sub-font Sans ; set sub-color \"#000000\" ; set sub-ass-override force"

It will write set sub-ass-override force to the OSD (and only that, not the previous parts of the command) each time you press the V key. It's not very helpful ;) So I would prefer that it doesn't write anything at all. Is this possible?

I've tried to add no-osd at diferent places in the command, but it never had any effect...

Thanx for the help!

youtube-search: Update Description in README.md

#####################################
Is this a request for a brand new script?
If so please make a Discussion thread instead
#####################################

Is your feature request related to a problem? Please describe.
Not too long ago I requested an option to use Invidious API in the Youtube-Search.lua script, and that suggestion has since been made into a reality. In the README for this repo, however, it still says a YouTube API Key is needed to use the script. Obviously, this is outdated.

Describe the solution you'd like
An updated README that specifies that either a YouTube API Key or the URL for a working Invidious Instance is required (as opposed to just the YouTube API Key).

Additional context
That's all. Not much else to say. Thanks for making these excellent MPV Scripts, and for being responsive to bug reports, feature requests etc.

All the ' are displayed as ' when searching

This is really a minor issue but I'm wondering if there is a way I could fix it. Is it something I can fix editing the files or it's not possible?

Edit: I'm talking about the youtube-search script btw.

Youtube-search: Need help to change the keybind in input.conf

There's no problem for me using ctrl+y and shift+y. But I want to put it in a menu for uosc and add a control button for it. But I don't know what to put in input.conf to make it work. script-binding or script-message-to youtube-search open_search_input doesn't do anything and I don't know if it should do anything either. Any way I can make entering search or showing search suggestion a menu for uosc ?

keep-session: add an option to remeber only the last opened video, not the playlist

Is your feature request related to a problem? Please describe.
I want to use this script together with autoload.lua, autoload.lua loads all files inside a folder into the playlist anytime you open a video, but keep-session.lua seems to override this playlist with its own (possibly outdated) playlist. This is a problem when I add new videos to a folder, that weren't there before when I last "saved my session".

Describe the solution you'd like
I would like an option in the config, which makes it so that the script only remembers the last video, not the whole playlist.

Describe alternatives you've considered
use a diffirent script for remembering the last video

Additional context
I'm not a programmer, so the reason for these 2 scripts not working together could be completely diffirent

[Feature Request] Exclude folders in keep-session.lua

I already created my own version of this:

Relevant code:

local excluded = false

--Excluded directories (Also excludes subdirectories)
local excludes = {
    '/mnt/storage/excluded/folder',
    'C:\excluded\folder' --might need double backslashes to work
}

local function exclude()
    local path, _ = utils.split_path(utils.join_path(mp.get_property('working-directory'), mp.get_property('path')))
	for _, exclusion in pairs(excludes) do
		if string.find(path, exclusion) then
			excluded = true
		end
	end
end

--the above are entirely new lines which can be placed pretty much anywhere, below are edits to existing lines or lines that must be placed at a specific location.

local function shutdown()
    if o.auto_save and not excluded then --this is the line that changed
        save_playlist()
    end
end

mp.register_script_message('save-session', save_playlist)
mp.register_script_message('reload-session', load_prev_session)
mp.register_event('file-loaded', exclude) --this is the line that was added
mp.register_event('shutdown', shutdown)
Whole file
--[[
    This script automatically saves the current playlist and can reload it if the player is started in idle mode (specifically
    if there are 0 files in the playlist), or if the correct command is sent via script-messages.
    It remembers the playlist position the player was in when shutdown and reloads the playlist at that entry.
    This can be disabled with script-opts

    The script saves a text file containing the previous session playlist in the watch_later directory (changeable via opts)
    This file is saved in plaintext with the exact file paths of each playlist entry.
    Note that since it uses the same file, only the latest mpv window to be closed will be saved

    The script attempts to correct relative playlist paths using the utils.join_path function. I've tried to automatically
    detect when any non-files are loaded (if it has the sequence :// in the path), so that it'll work with URLs

    You can disable the automatic stuff and use script messages to load/save playlists as well

    script-message save-session [session-file]
    script-message reload-session [session-file] [load_playlist]

    If not included `session-file` will use the default file specified in script-opts.
    `load_playlist` controls whether the whole playlist should be restored or just the one file,
    the value can be `yes` or `no`. If not included it defaults to the value of the `load_playlist` script opt.

    available at: https://github.com/CogentRedTester/mpv-scripts
]]--

local mp = require 'mp'
local utils = require 'mp.utils'
local opt = require 'mp.options'
local msg = require 'mp.msg'

local excluded = false

local o = {
    --automatically save the prev session
    auto_save = true,

    --runs the script automatically when started in idle mode and no files are in the playlist
    auto_load = true,

    --reloads the full playlist from the previous session
    --can be individually overwritten when sending script-messages
    load_playlist = true,

    --file path of the default session file
    --save it as a .pls file to be able to open directly (though it will not maintain the playlist positions)
    session_file = "",

    --maintain position in the playlist
    --does nothing if load_playlist is disabled
    maintain_pos = true,
}

--Excluded directories (Also excludes subdirectories)
local excludes = {
    '/mnt/storage/excluded/folder',
    'C:\excluded\folder'
}

local function exclude()
    local path, _ = utils.split_path(utils.join_path(mp.get_property('working-directory'), mp.get_property('path')))
	for _, exclusion in pairs(excludes) do
		if string.find(path, exclusion) then
			excluded = true
		end
	end
end

opt.read_options(o, 'keep_session', function() end)

--sets the default session file to the watch_later directory or ~~/watch_later/
if o.session_file == "" then
    local watch_later = mp.get_property('watch-later-directory', "")
    if watch_later == "" then watch_later = "~~state/watch_later/" end
    if not watch_later:find("[/\\]$") then watch_later = watch_later..'/' end

    o.session_file = watch_later.."prev-session"
end
local save_file = mp.command_native({"expand-path", o.session_file})

--saves the current playlist as a json string
local function save_playlist(file)
    if not file then file = save_file end
    msg.verbose('saving current session to', file)

    local playlist = mp.get_property_native('playlist')

    if #playlist == 0 then
        msg.verbose('session empty, aborting save')
        return
    end

    local session = io.open(file, 'w')
    if not session then return msg.error("Failed to write to file", file) end

    session:write("[playlist]\n")
    session:write(mp.get_property('playlist-pos') .. "\n")

    local working_directory = mp.get_property('working-directory')
    for _, v in ipairs(playlist) do
        msg.debug('adding ' .. v.filename .. ' to playlist')

        --if the file is available then it attempts to expand the path in-case of relative playlists
        --presumably if the file contains a protocol then it shouldn't be expanded
        if not v.filename:find("^%a*://") then
            v.filename = utils.join_path(working_directory, v.filename)
            msg.debug('expanded path: ' .. v.filename)
        end

        session:write("File=" .. v.filename .. "\n")
    end
    session:close()
end

--turns the previous json string into a table and adds all the files to the playlist
local function load_prev_session(file, load_playlist)
    if not file or file == '' then file = save_file end

    if load_playlist == 'yes' then load_playlist = true
    elseif load_playlist == 'no' then load_playlist = false
    else load_playlist = o.load_playlist end

    --loads the previous session file
    msg.verbose('loading previous session from', file)
    local session = io.open(file, "r+")

    --this should only occur when loading the script for the first time,
    --or if someone manually deletes the previous session file
    if session == nil or session:read() ~= "[playlist]" then
        msg.verbose('no previous session, cancelling load')
        if session then session:close() end
        return
    end

    local previous_playlist_pos = session:read('*n')

    if load_playlist then
        msg.debug('reloading playlist')

        if not o.maintain_pos then
            mp.commandv('loadlist', file)
        else
            local prev_playlist_start = mp.get_property('playlist-start')
            msg.verbose("restoring playlist position", previous_playlist_pos)
            mp.set_property_number('playlist-start', previous_playlist_pos)

            mp.commandv('loadlist', file)

            -- restore the original value unless the `playlist-start` property has been otherwise modified
            if mp.get_property_number('playlist-start') ~= previous_playlist_pos then
                mp.set_property('playlist-start', prev_playlist_start)
            end
        end
    else
        msg.debug('discarding playlist')
        local files = {}
        for line in session:lines() do
            table.insert(files, string.match(line, 'File=(.+)'))
        end

        -- mpv and keep-session uses 0 based array indices, but lua uses 1-based
        mp.commandv('loadfile', files[previous_playlist_pos+1])
    end

    session:close()
end

local function shutdown()
    if o.auto_save and not excluded then
        save_playlist()
    end
end

mp.register_script_message('save-session', save_playlist)
mp.register_script_message('reload-session', load_prev_session)
mp.register_event('file-loaded', exclude)
mp.register_event('shutdown', shutdown)

--Load the previous session if auto_load is enabled and the playlist is empty
--the function is not called until the first property observation is triggered to let everything initialise
--otherwise modifying playlist-start becomes unreliable
if o.auto_load and (mp.get_property_number('playlist-count', 0) == 0) then
    local function temp()
        load_prev_session()
        mp.unobserve_property(temp)
    end
    mp.observe_property("idle", "string", temp)
end

I'm too lazy to make a PR but it would be cool if this, or a version of this were included in the official file.

Latest commit on keep-session.lua forces mpv to play first video on playlist.

Even from starting in file manager if I choose video 20 mpv starts video 1.
Also somehow the script is not being automatically loaded even after adding this line
scripts-opts-append=keep_session-auto_load=yes
in mpv.conf.
It was working but somehow broke after update.
After reverting to the past .lua without the latest commit the problem with the first video in playlist being forced to play is solved but mpv still doesn't load automatically so I don't know why if it's a problem with my mpv or with the script.

youtube-search: ctrl+i breaks searching.

Another MPV Plugin (Youtube+Download) uses the crtl+i command (by default) for downloading videos with subtitles embedded. While testing this command for conflicts, I found it summoned the search bar for youtube-search, which is weird because the default command for that (and likewise how I have it set) is ctrl+y. I tried ctrl+y, and it wouldn't summon the search bar. Tried ctrl+i again, and nothing happened. Tried shift+y twice, nothing happened. Closed MPV and restarted it, and shift+y and ctrl+y worked as intended, but using ctrl+i again would summon the search bar randomly, and break the plugin as previously detailed until I refreshed MPV. Likewise, if I use ctr+i before using any other youtube-search commands, no Search Bar will appear, but the ctrl+y command will be broken anyway.

tldr: using the ctrl+i keyboard command breaks the plugin and makes it impossible to use the search functionality without closing the session and starting a new one.

Looking at the plugin's code, nowhere is Ctrl+i ever mentioned, which makes this even weirder.

youtube-search.lua: Use yt-dlp instead of curl+API

#####################################
Is this a request for a brand new script?
If so please make a Discussion thread instead
#####################################

Is your feature request related to a problem? Please describe.
I like the idea of being able to search and browse Youtube infinitely, but having to enter API key is a bit rough, yt-dlp support searching out-of-the-box and doens't require API at all.

Describe the solution you'd like
Use yt-dlp instead, with --flat-playlist it's very very fast:

yt-dlp ytsearch10:SEARCH --get-id --get-title --flat-playlist

10 = number of result, of course it must be used with --flat-playlist to increase searching speed by ten forth.

Describe alternatives you've considered
yt-dlp instead of curl+API

Additional context
Should be good enough information.

Documentation: Unclear on how to set it up

Hi, I am a bit new to this. I moved all the scripts to C:\Users\User1\AppData\Roaming\mpv\mpv\scripts and I opened mpv. I can't seem to use all the features you've mentioned in the readme and I'd really appreciate it if you could tell me on how to set it up

image

Add it mplug script directory

#####################################
Is this a request for a brand new script?
If so please make a Discussion thread instead
#####################################

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
Addition to mplug repo
Describe the solution you'd like
A clear and concise description of what you want to happen.
Mplug is like a package manager but for mpv scripts.
Please add your script there (if already not there) and also installation instructions in manifest - https://github.com/Nudin/mpv-script-directory/blob/master/HOWTO_ADD_INSTALL_INSTRUCTIONS.md
Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

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.