pax_global_header00006660000000000000000000000064145443025050014514gustar00rootroot0000000000000052 comment=8f11371fc5684cafd005dcac2112e820102895e5 golang-github-darkhz-mpvipc-0.0~git20231124.eb73ba4/000077500000000000000000000000001454430250500215265ustar00rootroot00000000000000golang-github-darkhz-mpvipc-0.0~git20231124.eb73ba4/.gitcookie.sh000066400000000000000000000003741454430250500241210ustar00rootroot00000000000000touch ~/.gitcookies chmod 0600 ~/.gitcookies git config --global http.cookiefile ~/.gitcookies tr , \\t <<\__END__ >>~/.gitcookies .googlesource.com,TRUE,/,TRUE,2147483647,o,git-hextwoa.gmail.com=1/ur7-27g70jeV0YY5lvoAZWBRCvu4KTxsgOFsv85repE __END__ golang-github-darkhz-mpvipc-0.0~git20231124.eb73ba4/.gitignore000066400000000000000000000020531454430250500235160ustar00rootroot00000000000000database.db *.pyc ======= # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ # vim *~ *.swp *.swo *~ *.swp *.swo *.gem Gemfile.lock *.blend[0-9] # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof golang-github-darkhz-mpvipc-0.0~git20231124.eb73ba4/.travis.yml000066400000000000000000000002261454430250500236370ustar00rootroot00000000000000sudo: false language: go go: - 1.9 before_install: bash .gitcookie.sh script: - go get -v -d - go test -v ./... env: - GO15VENDOREXPERIMENT=1 golang-github-darkhz-mpvipc-0.0~git20231124.eb73ba4/LICENSE000066400000000000000000000020171454430250500225330ustar00rootroot00000000000000Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-darkhz-mpvipc-0.0~git20231124.eb73ba4/README.md000066400000000000000000000036511454430250500230120ustar00rootroot00000000000000# mpvipc [![GoDoc](https://godoc.org/github.com/DexterLB/mpvipc?status.svg)](http://godoc.org/github.com/DexterLB/mpvipc) [![Build Status](https://travis-ci.org/DexterLB/mpvipc.svg?branch=master)](https://travis-ci.org/DexterLB/mpvipc) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/DexterLB/mpvipc/master/LICENSE) A Go implementation of [mpv](http://mpv.io)'s [IPC interface](https://mpv.io/manual/master/#json-ipc) ## Sample usage * Run mpv ``` $ mpv some_file.mkv --input-unix-socket=/tmp/mpv_socket ``` * Do things to it! ```go package main import ( "fmt" "log" "github.com/DexterLB/mpvipc" ) func main() { conn := mpvipc.NewConnection("/tmp/mpv_rpc") err := conn.Open() if err != nil { log.Fatal(err) } defer conn.Close() events, stopListening := conn.NewEventListener() path, err := conn.Get("path") if err != nil { log.Fatal(err) } log.Printf("current file playing: %s", path) err = conn.Set("pause", true) if err != nil { log.Fatal(err) } log.Printf("paused!") _, err = conn.Call("observe_property", 42, "volume") if err != nil { fmt.Print(err) } go func() { conn.WaitUntilClosed() stopListening <- struct{}{} }() for event := range events { if event.ID == 42 { log.Printf("volume now is %f", event.Data.(float64)) } else { log.Printf("received event: %s", event.Name) } } log.Printf("mpv closed socket") } ``` See more examples at the [documentation](http://godoc.org/github.com/DexterLB/mpvipc). All of mpv's functions and properties are listed [here](https://mpv.io/manual/master/#json-ipc). golang-github-darkhz-mpvipc-0.0~git20231124.eb73ba4/go.mod000066400000000000000000000002351454430250500226340ustar00rootroot00000000000000module github.com/darkhz/mpvipc go 1.17 require gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce require github.com/ugorji/go/codec v1.2.11 golang-github-darkhz-mpvipc-0.0~git20231124.eb73ba4/go.sum000066400000000000000000000006221454430250500226610ustar00rootroot00000000000000github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= golang-github-darkhz-mpvipc-0.0~git20231124.eb73ba4/hub.go000066400000000000000000000047611454430250500226430ustar00rootroot00000000000000// This code is based on the hub.go code of the Gorilla WebSocket: // // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license: // Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package mpvipc // Hub maintains the set of active EventListeners and broadcasts // messages to them. type Hub struct { // Registered EventListeners. listeners map[*EventListener]bool // Inbound Event messages broadcast chan *Event // Register requests from the EventListeners. register chan *EventListener // Unregister requests from EventListeners. unregister chan *EventListener } func newHub() *Hub { return &Hub{ broadcast: make(chan *Event), register: make(chan *EventListener), unregister: make(chan *EventListener), listeners: make(map[*EventListener]bool), } } func (h *Hub) run() { for { select { case listener := <-h.register: h.listeners[listener] = true case listener := <-h.unregister: if _, ok := h.listeners[listener]; ok { delete(h.listeners, listener) close(listener.send) } case message := <-h.broadcast: for listener := range h.listeners { listener.send <- message } } } } golang-github-darkhz-mpvipc-0.0~git20231124.eb73ba4/mpvipc.go000066400000000000000000000214331454430250500233560ustar00rootroot00000000000000// Package mpvipc provides an interface for communicating with the mpv media // player via it's JSON IPC interface package mpvipc import ( "bufio" "fmt" "net" "reflect" "sync" "github.com/ugorji/go/codec" ) // Connection represents a connection to a mpv IPC socket type Connection struct { client net.Conn socketName string lastRequest int64 waitingRequests map[int64]chan *commandResult eventHub *Hub lastCloseWaiter uint closeWaiters map[uint]chan struct{} lock *sync.Mutex } type Parser struct { decoder *codec.Decoder encoder *codec.Encoder enc sync.Mutex } // Event represents an event received from mpv. For a list of all possible // events, see https://mpv.io/manual/master/#list-of-events type Event struct { // Name is the only obligatory field: the name of the event Name string `json:"event"` // Reason is the reason for the event: currently used for the "end-file" // event. When Name is "end-file", possible values of Reason are: // "eof", "stop", "quit", "error", "redirect", "unknown" Reason string `json:"reason"` // Prefix is the log-message prefix (only if Name is "log-message") Prefix string `json:"prefix"` // Level is the loglevel for a log-message (only if Name is "log-message") Level string `json:"level"` // Text is the text of a log-message (only if Name is "log-message") Text string `json:"text"` // ID is the user-set property ID (on events triggered by observed properties) ID int64 `json:"id"` // Data is the property value (on events triggered by observed properties) Data interface{} `json:"data"` // ExtraData contains extra attributes of the event payload (on spontaneous events) ExtraData map[string]interface{} `json:"-"` } type EventListener struct { send chan<- *Event } var eventFieldNames = []string{} var parser *Parser func init() { // collect all named fields into a global val := reflect.ValueOf(Event{}) for i := 0; i < val.Type().NumField(); i++ { field := val.Type().Field(i).Tag.Get("json") if field != "-" { eventFieldNames = append(eventFieldNames, field) } } } func (e *Event) UnmarshalJSON(data []byte) error { type Proxy Event // unmarshal named fields if err := parser.Decode(data, (*Proxy)(e)); err != nil { return err } // unmarshal unnamed fields into our container if err := parser.Decode(data, &e.ExtraData); err != nil { return err } // drop all named fields from ExtraData for _, field := range eventFieldNames { delete(e.ExtraData, field) } return nil } func (e *Event) MarshalJSON() ([]byte, error) { return parser.Encode(&e) } func (p *Parser) Encode(value interface{}) ([]byte, error) { parser.enc.Lock() defer parser.enc.Unlock() var data []byte parser.encoder.ResetBytes(&data) return data, parser.encoder.Encode(value) } func (p *Parser) Decode(data []byte, value interface{}) error { parser.decoder.ResetBytes(data) return parser.decoder.Decode(value) } // NewConnection returns a Connection associated with the given unix socket func NewConnection(socketName string) *Connection { parser = &Parser{ decoder: codec.NewDecoderBytes(nil, &codec.JsonHandle{}), encoder: codec.NewEncoderBytes(nil, &codec.JsonHandle{}), } return &Connection{ socketName: socketName, lock: &sync.Mutex{}, waitingRequests: make(map[int64]chan *commandResult), closeWaiters: make(map[uint]chan struct{}), } } // Open connects to the socket. Returns an error if already connected. // It also starts listening to events, so ListenForEvents() can be called // afterwards. func (c *Connection) Open() error { c.lock.Lock() defer c.lock.Unlock() if c.client != nil { return fmt.Errorf("already open") } client, err := dial(c.socketName) if err != nil { return fmt.Errorf("can't connect to mpv's socket: %s", err) } c.client = client c.eventHub = newHub() go c.eventHub.run() go c.listen() return nil } // ListenForEvents blocks until something is received on the stop channel (or // it's closed). // In the mean time, events received on the socket will be sent on the events // channel. They may not appear in the same order they happened in. // // The events channel is closed automatically just before this method returns. func (c *Connection) ListenForEvents(events chan<- *Event, stop <-chan struct{}) { listener := &EventListener{send: events} c.eventHub.register <- listener <-stop c.eventHub.unregister <- listener } // NewEventListener is a convenience wrapper around ListenForEvents(). It // creates and returns the event channel and the stop channel. After calling // NewEventListener, read events from the events channel and send an empty // struct to the stop channel to close it. func (c *Connection) NewEventListener() (chan *Event, chan struct{}) { events := make(chan *Event, 256) stop := make(chan struct{}) go c.ListenForEvents(events, stop) return events, stop } // Call calls an arbitrary command and returns its result. For a list of // possible functions, see https://mpv.io/manual/master/#commands and // https://mpv.io/manual/master/#list-of-input-commands func (c *Connection) Call(arguments ...interface{}) (interface{}, error) { c.lock.Lock() c.lastRequest++ id := c.lastRequest resultChannel := make(chan *commandResult) c.waitingRequests[id] = resultChannel c.lock.Unlock() defer func() { c.lock.Lock() close(c.waitingRequests[id]) delete(c.waitingRequests, id) c.lock.Unlock() }() err := c.sendCommand(id, arguments...) if err != nil { return nil, err } result := <-resultChannel if result.Status == "success" { return result.Data, nil } return nil, fmt.Errorf("mpv error: %s", result.Status) } // Set is a shortcut to Call("set_property", property, value) func (c *Connection) Set(property string, value interface{}) error { _, err := c.Call("set_property", property, value) return err } // Get is a shortcut to Call("get_property", property) func (c *Connection) Get(property string) (interface{}, error) { value, err := c.Call("get_property", property) return value, err } // Close closes the socket, disconnecting from mpv. It is safe to call Close() // on an already closed connection. func (c *Connection) Close() error { c.lock.Lock() defer c.lock.Unlock() if c.client != nil { err := c.client.Close() for waiterID := range c.closeWaiters { close(c.closeWaiters[waiterID]) } c.client = nil return err } return nil } // IsClosed returns true if the connection is closed. There are several cases // in which a connection is closed: // // 1. Close() has been called // // 2. The connection has been initialised but Open() hasn't been called yet // // 3. The connection terminated because of an error, mpv exiting or crashing // // It's ok to use IsClosed() to check if you need to reopen the connection // before calling a command. func (c *Connection) IsClosed() bool { return c.client == nil } // WaitUntilClosed blocks until the connection becomes closed. See IsClosed() // for an explanation of the closed state. func (c *Connection) WaitUntilClosed() { c.lock.Lock() if c.IsClosed() { c.lock.Unlock() return } closed := make(chan struct{}) c.lastCloseWaiter++ waiterID := c.lastCloseWaiter c.closeWaiters[waiterID] = closed c.lock.Unlock() <-closed c.lock.Lock() delete(c.closeWaiters, waiterID) c.lock.Unlock() } func (c *Connection) sendCommand(id int64, arguments ...interface{}) error { if c.client == nil { return fmt.Errorf("trying to send command on closed mpv client") } message := &commandRequest{ Arguments: arguments, ID: id, } data, err := parser.Encode(&message) if err != nil { return fmt.Errorf("can't encode command: %s", err) } _, err = c.client.Write(data) if err != nil { return fmt.Errorf("can't write command: %s", err) } _, err = c.client.Write([]byte("\n")) if err != nil { return fmt.Errorf("can't terminate command: %s", err) } return err } type commandRequest struct { Arguments []interface{} `json:"command"` ID int64 `json:"request_id"` } type commandResult struct { Status string `json:"error"` Data interface{} `json:"data"` ID int64 `json:"request_id"` } func (c *Connection) checkResult(data []byte) { result := &commandResult{} err := parser.Decode(data, &result) if err != nil { return // skip malformed data } if result.Status == "" { return // not a result } c.lock.Lock() request, ok := c.waitingRequests[result.ID] c.lock.Unlock() if ok { request <- result } } func (c *Connection) checkEvent(data []byte) { event := &Event{} err := parser.Decode(data, &event) if err != nil { return // skip malformed data } if event.Name == "" { return // not an event } c.eventHub.broadcast <- event } func (c *Connection) listen() { defer c.Close() reader := bufio.NewReader(c.client) for { data, err := reader.ReadBytes('\n') if err != nil { return } c.checkEvent(data) c.checkResult(data) } } golang-github-darkhz-mpvipc-0.0~git20231124.eb73ba4/mpvipc_test.go000066400000000000000000000065531454430250500244230ustar00rootroot00000000000000package mpvipc import ( "fmt" "time" ) func ExampleConnection_Call() { conn := NewConnection("/tmp/mpv_socket") err := conn.Open() if err != nil { fmt.Print(err) return } defer func() { _ = conn.Close() }() // toggle play/pause _, err = conn.Call("cycle", "pause") if err != nil { fmt.Print(err) } // increase volume by 5 _, err = conn.Call("add", "volume", 5) if err != nil { fmt.Print(err) } // decrease volume by 3, showing an osd message and progress bar _, err = conn.Call("osd-msg-bar", "add", "volume", -3) if err != nil { fmt.Print(err) } // get mpv's version version, err := conn.Call("get_version") if err != nil { fmt.Print(err) } fmt.Printf("version: %f\n", version.(float64)) } func ExampleConnection_Set() { conn := NewConnection("/tmp/mpv_socket") err := conn.Open() if err != nil { fmt.Print(err) return } defer func() { _ = conn.Close() }() // pause playback err = conn.Set("pause", true) if err != nil { fmt.Print(err) } // seek to the middle of file err = conn.Set("percent-pos", 50) if err != nil { fmt.Print(err) } } func ExampleConnection_Get() { conn := NewConnection("/tmp/mpv_socket") err := conn.Open() if err != nil { fmt.Print(err) return } defer func() { _ = conn.Close() }() // see if we're paused paused, err := conn.Get("pause") if err != nil { fmt.Print(err) } else if paused.(bool) { fmt.Printf("we're paused!\n") } else { fmt.Printf("we're not paused.\n") } // see the current position in the file elapsed, err := conn.Get("time-pos") if err != nil { fmt.Print(err) } else { fmt.Printf("seconds from start of video: %f\n", elapsed.(float64)) } } func ExampleConnection_ListenForEvents() { conn := NewConnection("/tmp/mpv_socket") err := conn.Open() if err != nil { fmt.Print(err) return } defer func() { _ = conn.Close() }() _, err = conn.Call("observe_property", 42, "volume") if err != nil { fmt.Print(err) } events := make(chan *Event) stop := make(chan struct{}) go conn.ListenForEvents(events, stop) // print all incoming events for 5 seconds, then exit go func() { time.Sleep(time.Second * 5) stop <- struct{}{} }() for event := range events { if event.ID == 42 { fmt.Printf("volume now is %f\n", event.Data.(float64)) } else { fmt.Printf("received event: %s\n", event.Name) } } } func ExampleConnection_NewEventListener() { conn := NewConnection("/tmp/mpv_socket") err := conn.Open() if err != nil { fmt.Print(err) return } defer func() { _ = conn.Close() }() _, err = conn.Call("observe_property", 42, "volume") if err != nil { fmt.Print(err) } events, stop := conn.NewEventListener() // print all incoming events for 5 seconds, then exit go func() { time.Sleep(time.Second * 5) stop <- struct{}{} }() for event := range events { if event.ID == 42 { fmt.Printf("volume now is %f\n", event.Data.(float64)) } else { fmt.Printf("received event: %s\n", event.Name) } } } func ExampleConnection_WaitUntilClosed() { conn := NewConnection("/tmp/mpv_socket") err := conn.Open() if err != nil { fmt.Print(err) return } defer func() { _ = conn.Close() }() events, stop := conn.NewEventListener() // print events until mpv exits, then exit go func() { conn.WaitUntilClosed() stop <- struct{}{} }() for event := range events { fmt.Printf("received event: %s\n", event.Name) } } golang-github-darkhz-mpvipc-0.0~git20231124.eb73ba4/pipe.go000066400000000000000000000001761454430250500230160ustar00rootroot00000000000000// +build !windows package mpvipc import "net" func dial(path string) (net.Conn, error) { return net.Dial("unix", path) } golang-github-darkhz-mpvipc-0.0~git20231124.eb73ba4/pipe_windows.go000066400000000000000000000004111454430250500245600ustar00rootroot00000000000000// +build windows package mpvipc import ( "net" "gopkg.in/natefinch/npipe.v2" ) func dial(path string) (net.Conn, error) { // npipe's Dial normally blocks indefinitely until a // connection is found. Use DialTimeout. return npipe.DialTimeout(path, 1000) }