├── .gitignore ├── server.py ├── screenshot.png ├── chrome-extension ├── mpv-logo.png ├── manifest.json ├── options.html ├── options.js └── background.js ├── fair-use-extension ├── fair-use.png ├── manifest.json ├── options.html ├── options.js └── background.js ├── mkchromecast-extension ├── mkchromecast-logo.png ├── manifest.json └── background.js ├── LICENSE ├── setup.py ├── README.md └── play_with_mpv.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pem 2 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | play_with_mpv.py -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thann/play-with-mpv/HEAD/screenshot.png -------------------------------------------------------------------------------- /chrome-extension/mpv-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thann/play-with-mpv/HEAD/chrome-extension/mpv-logo.png -------------------------------------------------------------------------------- /fair-use-extension/fair-use.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thann/play-with-mpv/HEAD/fair-use-extension/fair-use.png -------------------------------------------------------------------------------- /mkchromecast-extension/mkchromecast-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thann/play-with-mpv/HEAD/mkchromecast-extension/mkchromecast-logo.png -------------------------------------------------------------------------------- /mkchromecast-extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cast with MkChromecast", 3 | "version": "0.0.1", 4 | "manifest_version": 2, 5 | "description": "Casts videos in pages like youtube via MkChromecast", 6 | "homepage_url": "https://github.com/thann/play-with-mpv", 7 | "icons": { 8 | "128": "mkchromecast-logo.png" 9 | }, 10 | "page_action": { 11 | "default_icon": "mkchromecast-logo.png", 12 | "default_title": "Cast with MkChromecast" 13 | }, 14 | "background": { 15 | "scripts": ["background.js"], 16 | "persistent": false 17 | }, 18 | "permissions": [ 19 | "activeTab", 20 | "contextMenus", 21 | "http://localhost/" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /chrome-extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Play with MPV", 3 | "version": "0.1.0", 4 | "manifest_version": 2, 5 | "description": "Sends videos in pages like youtube to an external MPV player", 6 | "homepage_url": "https://github.com/thann/play-with-mpv", 7 | "icons": { 8 | "128": "mpv-logo.png" 9 | }, 10 | "page_action": { 11 | "default_icon": "mpv-logo.png", 12 | "default_title": "Play with MPV" 13 | }, 14 | "background": { 15 | "scripts": ["background.js"], 16 | "persistent": false 17 | }, 18 | "permissions": [ 19 | "storage", 20 | "activeTab", 21 | "contextMenus", 22 | "http://localhost/" 23 | ], 24 | "commands": { 25 | "launch": { 26 | "suggested_key": { 27 | "default": "Ctrl+Space" 28 | }, 29 | "description": "Play with MPV" 30 | } 31 | }, 32 | "options_ui": { 33 | "page": "options.html", 34 | "open_in_tab": false 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /fair-use-extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fair Use Download", 3 | "version": "0.1.0", 4 | "manifest_version": 2, 5 | "description": "Download videos in pages like youtube for Fair Use purposes.", 6 | "homepage_url": "https://github.com/thann/play-with-mpv", 7 | "icons": { 8 | "128": "fair-use.png" 9 | }, 10 | "page_action": { 11 | "default_icon": "fair-use.png", 12 | "default_title": "Fair Use Download" 13 | }, 14 | "background": { 15 | "scripts": ["background.js"], 16 | "persistent": false 17 | }, 18 | "permissions": [ 19 | "storage", 20 | "activeTab", 21 | "contextMenus", 22 | "http://localhost/" 23 | ], 24 | "commands": { 25 | "launch": { 26 | "suggested_key": { 27 | "default": "Ctrl+Shift+Space" 28 | }, 29 | "description": "Fair Use" 30 | } 31 | }, 32 | "options_ui": { 33 | "page": "options.html", 34 | "open_in_tab": false 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mkchromecast-extension/background.js: -------------------------------------------------------------------------------- 1 | 2 | // Show on all pages 3 | chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) { 4 | chrome.pageAction.show(tabId); 5 | }); 6 | 7 | // Send current tabs url to MPV server 8 | chrome.pageAction.onClicked.addListener(function(tab){ 9 | // console.info("Clicked!", tab.url) 10 | playUrl(tab.url); 11 | }); 12 | 13 | function playUrl(url) { 14 | var xhr = new XMLHttpRequest(); 15 | xhr.onreadystatechange = handleXHR; 16 | xhr.open("GET", "http://localhost:7531/?cast_url="+url, true); 17 | xhr.send(); 18 | } 19 | 20 | function handleXHR() { 21 | // console.log("XHR", arguments) 22 | } 23 | 24 | var cast = chrome.contextMenus.create({ 25 | "id": "thann.cast-with-mkchromecast", 26 | "title": "Cast with MkChromecast", 27 | "contexts": ["page", "link", "video", "audio"] 28 | }); 29 | 30 | chrome.contextMenus.onClicked.addListener(function(info, tab) { 31 | // console.log("item " + info.menuItemId + " was clicked"); 32 | // console.log("info: " + JSON.stringify(info)); 33 | // console.log("tab: " + JSON.stringify(tab)); 34 | 35 | playUrl(info["linkUrl"] || info["srcUrl"]|| info["pageUrl"]) 36 | }); 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /chrome-extension/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Play-With-MPV Options 5 | 6 | 7 | 11 | 12 |

Maximum Resolution:

13 | 21 | 22 |

Server location:

23 | 24 | 25 |

MPV Arguments:

26 | 27 |
28 | (one per line) 29 |   30 | docs 31 |
32 | 33 |

34 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /fair-use-extension/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fair Use Options 5 | 6 | 7 | 11 | 12 |

Server location:

13 | 14 | 15 |

Download location:

16 | 17 | 18 |

Youtube-DL Arguments:

19 | 20 |
21 | (one per line) 22 |   23 | 24 |
25 | 26 |

27 | 28 | (Icon credit to Dave Gandy 29 | 30 | 🔗 32 | ) 33 | 34 |

35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /chrome-extension/options.js: -------------------------------------------------------------------------------- 1 | // Saves options to chrome.storage 2 | 3 | function save_options() { 4 | const server_url = document.getElementById('server_url').value || null; 5 | const maxheight = document.getElementById('maxheight').value || null; 6 | const mpv_args = document.getElementById('mpv_args').value || null; 7 | chrome.storage.sync.set({ 8 | server_url, maxheight, mpv_args 9 | }, function() { 10 | // Update status to let user know options were saved. 11 | const status = document.getElementById('status'); 12 | status.textContent = 'Options saved.'; 13 | setTimeout(function() { 14 | status.textContent = ''; 15 | }, 1000); 16 | }); 17 | } 18 | 19 | // Restores select box and checkbox state using the preferences 20 | // stored in chrome.storage. 21 | function restore_options() { 22 | chrome.storage.sync.get({ 23 | server_url: null, 24 | maxheight: null, 25 | mpv_args: null, 26 | }, function(opts) { 27 | document.getElementById('server_url').value = opts.server_url; 28 | document.getElementById('maxheight').value = opts.maxheight || ''; 29 | document.getElementById('mpv_args').value = opts.mpv_args; 30 | 31 | // TODO: chrome seems to block this. 32 | // Check server connectivity 33 | // const xhr = new XMLHttpRequest(); 34 | // xhr.onreadystatechange = function(e) { 35 | // document.getElementById('server_status').textContent = ( 36 | // e.currentTarget.status? 37 | // 'Connected!': 38 | // 'Disconected!') 39 | // }; 40 | // xhr.open("GET", opts.server_url, true); 41 | // xhr.send(); 42 | }); 43 | } 44 | 45 | document.addEventListener('DOMContentLoaded', restore_options); 46 | document.getElementById('save').addEventListener('click', save_options); -------------------------------------------------------------------------------- /fair-use-extension/options.js: -------------------------------------------------------------------------------- 1 | // Saves options to chrome.storage 2 | 3 | function save_options() { 4 | const server_url = document.getElementById('server_url').value || null; 5 | const ytdl_args = document.getElementById('ytdl_args').value || null; 6 | const location = document.getElementById('location').value || null; 7 | chrome.storage.sync.set({ 8 | server_url, ytdl_args, location 9 | }, function() { 10 | // Update status to let user know options were saved. 11 | const status = document.getElementById('status'); 12 | status.textContent = 'Options saved.'; 13 | setTimeout(function() { 14 | status.textContent = ''; 15 | }, 1000); 16 | }); 17 | } 18 | 19 | // Restores select box and checkbox state using the preferences 20 | // stored in chrome.storage. 21 | function restore_options() { 22 | chrome.storage.sync.get({ 23 | server_url: null, 24 | ytdl_args: null, 25 | location: null, 26 | }, function(opts) { 27 | document.getElementById('server_url').value = opts.server_url; 28 | document.getElementById('ytdl_args').value = opts.ytdl_args; 29 | document.getElementById('location').value = opts.location || ''; 30 | 31 | // TODO: chrome seems to block this. 32 | // Check server connectivity 33 | // const xhr = new XMLHttpRequest(); 34 | // xhr.onreadystatechange = function(e) { 35 | // document.getElementById('server_status').textContent = ( 36 | // e.currentTarget.status? 37 | // 'Connected!': 38 | // 'Disconected!') 39 | // }; 40 | // xhr.open("GET", opts.server_url, true); 41 | // xhr.send(); 42 | }); 43 | } 44 | 45 | document.addEventListener('DOMContentLoaded', restore_options); 46 | document.getElementById('save').addEventListener('click', save_options); 47 | -------------------------------------------------------------------------------- /chrome-extension/background.js: -------------------------------------------------------------------------------- 1 | 2 | // Show on all pages 3 | chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) { 4 | chrome.pageAction.show(tabId); 5 | }); 6 | 7 | // Send current tabs url to MPV server 8 | chrome.pageAction.onClicked.addListener(function(tab){ 9 | // console.info("Clicked!", tab.url) 10 | playUrl(tab.url, true); 11 | }); 12 | 13 | function playUrl(url, pause) { 14 | chrome.storage.sync.get({ 15 | server_url: null, 16 | maxheight: null, 17 | mpv_args: null, 18 | }, function(opts) { 19 | if (!opts.server_url) 20 | opts.server_url = 'http://localhost:7531'; 21 | if (opts.mpv_args) { 22 | opts.mpv_args = opts.mpv_args.split(/\n/); 23 | } else { 24 | opts.mpv_args = []; 25 | } 26 | if (opts.maxheight) { 27 | opts.mpv_args.splice(0, 0, 28 | `--ytdl-format=bestvideo[height<=?${opts.maxheight}]+bestaudio/best`); 29 | } 30 | const query = (`?play_url=` + encodeURIComponent(url) + [''].concat( 31 | opts.mpv_args.map(encodeURIComponent)).join('&mpv_args=')); 32 | 33 | const xhr = new XMLHttpRequest(); 34 | xhr.onreadystatechange = handleXHR; 35 | xhr.open("GET", `${opts.server_url}/${query}`, true); 36 | xhr.send(); 37 | 38 | // Pause videos in tab 39 | pause && chrome.tabs.executeScript({code: ` 40 | for (const v of document.getElementsByTagName('video')) { 41 | v.pause(); 42 | }` 43 | }); 44 | }); 45 | } 46 | 47 | function handleXHR() { 48 | // console.log("XHR", arguments) 49 | } 50 | 51 | var parent = chrome.contextMenus.create({ 52 | "id": "thann.play-with-mpv", 53 | "title": "Play with MPV", 54 | "contexts": ["page", "link", "video", "audio"] 55 | }); 56 | 57 | chrome.contextMenus.onClicked.addListener(function(info, tab) { 58 | // console.log("item " + info.menuItemId + " was clicked"); 59 | // console.log("info: " + JSON.stringify(info)); 60 | // console.log("tab: " + JSON.stringify(tab)); 61 | 62 | playUrl(info["linkUrl"] || info["srcUrl"]|| info["pageUrl"], true); 63 | }); 64 | 65 | chrome.commands.onCommand.addListener(function(command) { 66 | chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) { 67 | playUrl(tabs[0].url, true); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | from setuptools import setup, find_packages 4 | 5 | description = "Chrome extension and python server that allows you to play videos in webpages with MPV instead." 6 | 7 | def read(fname): 8 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 9 | 10 | def get_version(): 11 | from subprocess import Popen, PIPE 12 | try: 13 | from subprocess import DEVNULL # py3 14 | except ImportError: 15 | import os 16 | DEVNULL = open(os.devnull, 'wb') 17 | 18 | def run(*cmd): 19 | return (Popen(cmd, stderr=DEVNULL, stdout=PIPE) 20 | .communicate()[0].decode('utf8').strip()) 21 | 22 | return(run('git', 'describe', '--tags').replace('-','.post',1).replace('-','+',1) 23 | or '0.0.0.post{}+g{}'.format( 24 | run('git', 'rev-list', '--count', 'HEAD'), 25 | run('git', 'rev-parse', '--short', 'HEAD'))) 26 | 27 | setup( 28 | name = "play-with-mpv", 29 | version = get_version(), 30 | author = "Jonathan Knapp", 31 | author_email = "jaknapp8@gmail.com", 32 | description = description, 33 | license = "MIT", 34 | keywords = "mpv video play chrome extension", 35 | url = "http://github.com/thann/play-with-mpv", 36 | long_description=read('README.md'), 37 | classifiers=[ 38 | "Development Status :: 3 - Alpha", 39 | "Topic :: Utilities", 40 | "License :: OSI Approved :: MIT License", 41 | ], 42 | 43 | py_modules=["play_with_mpv"], 44 | install_requires=['wheel', 'youtube-dl'], 45 | entry_points={ 46 | 'gui_scripts': [ 47 | 'play-with-mpv=play_with_mpv:start', 48 | ], 49 | }, 50 | setup_requires=['wheel', 'install_freedesktop>=0.2.0'], 51 | dependency_links=[ 52 | "https://github.com/thann/install_freedesktop/tarball/master#egg=install_freedesktop-0.2.0" 53 | ], 54 | desktop_entries={ 55 | 'play-with-mpv': { 56 | 'filename': 'thann.play-with-mpv', 57 | 'Name': 'Play With MPV (server)', 58 | 'Categories': 'AudioVideo;Audio;Video;Player;TV', 59 | 'Comment': description, 60 | 'Icon': 'mpv', 61 | }, 62 | }, 63 | ) 64 | -------------------------------------------------------------------------------- /fair-use-extension/background.js: -------------------------------------------------------------------------------- 1 | 2 | // Show on all pages 3 | chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) { 4 | chrome.pageAction.show(tabId); 5 | }); 6 | 7 | // Send current tabs url to MPV server 8 | chrome.pageAction.onClicked.addListener(function(tab){ 9 | // console.info("Clicked!", tab.url) 10 | fairuseUrl(tab.url, true); 11 | }); 12 | 13 | function fairuseUrl(url, pause) { 14 | chrome.storage.sync.get({ 15 | server_url: null, 16 | ytdl_args: null, 17 | location: null, 18 | }, function(opts) { 19 | if (!opts.server_url) 20 | opts.server_url = 'http://localhost:7531'; 21 | if (opts.ytdl_args) { 22 | opts.ytdl_args = opts.ytdl_args.split(/\n/); 23 | } else { 24 | opts.ytdl_args = []; 25 | } 26 | const params = { 27 | fairuse_url: url, 28 | location: opts.location, 29 | ytdl_args: opts.ytdl_args, 30 | } 31 | // build query-string 32 | const query = Object.entries(params) 33 | .map(([k,v]) => { 34 | k = encodeURIComponent(k); 35 | if ([null, undefined].includes(v)) { 36 | v = ''; 37 | } else if (v.map) { // arrays 38 | v = v.map(encodeURIComponent).join(`&${k}=`); 39 | } else { 40 | v = encodeURIComponent(v) ; 41 | } 42 | return `${k}=${v}`; 43 | }) 44 | .join('&'); 45 | const xhr = new XMLHttpRequest(); 46 | xhr.onreadystatechange = handleXHR; 47 | xhr.open("GET", `${opts.server_url}/?${query}`, true); 48 | xhr.send(); 49 | }); 50 | } 51 | 52 | function handleXHR() { 53 | // console.log("XHR", arguments) 54 | } 55 | 56 | var parent = chrome.contextMenus.create({ 57 | "id": "thann.fair-use-download", 58 | "title": "Download video for Fair Use", 59 | "contexts": ["page", "link", "video", "audio"] 60 | }); 61 | 62 | chrome.contextMenus.onClicked.addListener(function(info, tab) { 63 | // console.log("item " + info.menuItemId + " was clicked"); 64 | // console.log("info: " + JSON.stringify(info)); 65 | // console.log("tab: " + JSON.stringify(tab)); 66 | 67 | fairuseUrl(info["linkUrl"] || info["srcUrl"]|| info["pageUrl"], true); 68 | }); 69 | 70 | chrome.commands.onCommand.addListener(function(command) { 71 | chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) { 72 | fairuseUrl(tabs[0].url, true); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Play with MPV 2 | Chrome extension and python server that allows you to play videos in webpages with MPV instead. 3 | Works on [hundreds of sites](https://rg3.github.io/youtube-dl/supportedsites.html) thanks to youtube-dl, 4 | and even torrents if you install [peerflix](https://github.com/mafintosh/peerflix). 5 | 6 | ## Installation 7 | 1. Install [MPV](https://mpv.io/installation/) 8 | 2. Install [python 2 or 3](https://www.python.org/downloads/) and [pip](https://pip.pypa.io/en/stable/installing/) 9 | 3. Install [chrome extension](https://chrome.google.com/webstore/detail/play-with-mpv/hahklcmnfgffdlchjigehabfbiigleji) 10 | 4. Run `pip install git+git://github.com/thann/play-with-mpv --user` 11 | 5. Start server by running `play-with-mpv` (or use the Linux _free desktop_ shortcut) 12 | 13 | (optional) Install [fair-use](https://chrome.google.com/webstore/detail/fair-use-download/fhokdginneihphnneihijgbhbdoehjaj) extension. 14 | (optional) Install [peerflix](https://github.com/mafintosh/peerflix) to stream torrents. 15 | (optional) Install [mkchromecast](http://mkchromecast.com/) `pip install git+git://github.com/muammar/mkchromecast --user` 16 | and [extension](https://chrome.google.com/webstore/detail/edeepcccaejnnodlpmcoackkdgaijakg). 17 | (recommended) Install youtube-dl through your package manager for frequent updates. 18 | (Arch Linux) [aur package](https://aur.archlinux.org/packages/play-with-mpv-git) available. 19 | 20 | ## Usage 21 | Right-click [this link](https://www.youtube.com/watch?v=dQw4w9WgXcQ) and select "Play with MPV". 22 | MPV should popup and start playing the video. (Ctrl+Space also works) 23 | 24 | ![screenshot](https://github.com/thann/play-with-mpv/raw/master/screenshot.png) 25 | 26 | ## Autostart 27 | - Linux: `cp {/usr,~/.local}/share/applications/thann.play-with-mpv.desktop ~/.config/autostart` 28 | - MacOS: [instructions](https://stackoverflow.com/questions/29338066/mac-osx-execute-a-python-script-at-startup) 29 | - Windows [instructions](https://stackoverflow.com/questions/4438020/how-to-start-a-python-file-while-windows-starts) 30 | 31 | ## Protips 32 | MPV is [highly configurable](https://mpv.io/manual/stable/), this is just how I like to use it. 33 | 34 | To start in the corner, have no border, and stay on top: edit `~/.config/mpv/mpv.conf` 35 | ``` 36 | ontop=yes 37 | border=no 38 | window-scale=0.4 39 | geometry=100%:100% 40 | ``` 41 | 42 | In order to resize the window without borders, add keybinds: edit `~/.config/mpv/input.conf` 43 | ``` 44 | ` cycle border 45 | ALT+UP add window-scale 0.05 46 | ALT+DOWN add window-scale -0.05 47 | ``` 48 | -------------------------------------------------------------------------------- /play_with_mpv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Plays MPV when instructed to by a chrome extension =] 3 | 4 | import sys 5 | import argparse 6 | from subprocess import Popen 7 | 8 | if sys.version_info[0] < 3: # python 2 9 | import BaseHTTPServer 10 | import urlparse 11 | class CompatibilityMixin: 12 | def send_body(self, msg): 13 | self.wfile.write(msg+'\n') 14 | self.wfile.close() 15 | 16 | else: # python 3 17 | import http.server as BaseHTTPServer 18 | import urllib.parse as urlparse 19 | class CompatibilityMixin: 20 | def send_body(self, msg): 21 | self.wfile.write(bytes(msg+'\n', 'utf-8')) 22 | 23 | 24 | class Handler(BaseHTTPServer.BaseHTTPRequestHandler, CompatibilityMixin): 25 | def respond(self, code, body=None): 26 | self.send_response(code) 27 | self.send_header("Content-type", "text/plain") 28 | self.end_headers() 29 | if body: 30 | self.send_body(body) 31 | 32 | def do_GET(self): 33 | try: 34 | url = urlparse.urlparse(self.path) 35 | query = urlparse.parse_qs(url.query) 36 | except: 37 | query = {} 38 | if query.get('mpv_args'): 39 | print("MPV ARGS:", query.get('mpv_args')) 40 | if "play_url" in query: 41 | urls = str(query["play_url"][0]) 42 | if urls.startswith('magnet:') or urls.endswith('.torrent'): 43 | try: 44 | pipe = Popen(['peerflix', '-k', urls, '--', '--force-window'] + 45 | query.get("mpv_args", [])) 46 | except FileNotFoundError as e: 47 | missing_bin('peerflix') 48 | else: 49 | try: 50 | pipe = Popen(['mpv', urls, '--force-window'] + 51 | query.get("mpv_args", [])) 52 | except FileNotFoundError as e: 53 | missing_bin('mpv') 54 | self.respond(200, "playing...") 55 | elif "cast_url" in query: 56 | urls = str(query["cast_url"][0]) 57 | if urls.startswith('magnet:') or urls.endswith('.torrent'): 58 | print(" === WARNING: Casting torrents not yet fully supported!") 59 | try: 60 | with Popen(['mkchromecast', '--video', 61 | '--source-url', 'http://localhost:8888']): 62 | pass 63 | except FileNotFoundError as e: 64 | missing_bin('mkchromecast') 65 | pipe.terminate() 66 | else: 67 | try: 68 | pipe = Popen(['mkchromecast', '--video', '-y', urls]) 69 | except FileNotFoundError as e: 70 | missing_bin('mkchromecast') 71 | self.respond(200, "casting...") 72 | 73 | elif "fairuse_url" in query: 74 | urls = str(query["fairuse_url"][0]) 75 | location = query.get("location", ['~/Downloads/'])[0] 76 | if "%" not in location: 77 | location += "%(title)s.%(ext)s" 78 | print("downloading ", urls, "to", location) 79 | if urls.startswith('magnet:') or urls.endswith('.torrent'): 80 | msg = " === ERROR: Downloading torrents not yet supported!" 81 | print(msg) 82 | self.respond(400, msg) 83 | else: 84 | try: 85 | pipe = Popen(['youtube-dl', urls, '-o', location] + 86 | query.get('ytdl_args', [])) 87 | except FileNotFoundError as e: 88 | missing_bin('youtube-dl') 89 | self.respond(200, "downloading...") 90 | else: 91 | self.respond(400) 92 | 93 | 94 | def missing_bin(bin): 95 | print("======================") 96 | print(f"ERROR: {bin.upper()} does not appear to be installed correctly! please ensure you can launch '{bin}' in the terminal.") 97 | print("======================") 98 | 99 | 100 | def start(): 101 | parser = argparse.ArgumentParser(description='Plays MPV when instructed to by a browser extension.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) 102 | parser.add_argument('--port', type=int, default=7531, help='The port to listen on.') 103 | parser.add_argument('--public', action='store_true', help='Accept traffic from other computers.') 104 | args = parser.parse_args() 105 | hostname = '0.0.0.0' if args.public else 'localhost' 106 | httpd = BaseHTTPServer.HTTPServer((hostname, args.port), Handler) 107 | print("serving on {}:{}".format(hostname, args.port)) 108 | try: 109 | httpd.serve_forever() 110 | except KeyboardInterrupt: 111 | print(" shutting down...") 112 | httpd.shutdown() 113 | 114 | 115 | if __name__ == '__main__': 116 | start() 117 | 118 | --------------------------------------------------------------------------------