]>
git.wh0rd.org - home.git/blob - .bin/vlc-rc
2 # -*- coding: utf-8 -*-
4 """Tool to control VLC via remote network interface.
6 All excess args are sent as a one shot command.
9 from __future__
import print_function
23 def setup_logging(logfile
=None, base
=None, debug
=False, stdout
=False):
24 """Set up the logging module."""
25 fmt
= '%(asctime)s: %(levelname)-7s: '
27 fmt
+= '%(filename)s:%(funcName)s: '
29 # 'Sat, 05 Oct 2013 18:58:50 -0400 (EST)'
30 tzname
= time
.strftime('%Z', time
.localtime())
31 datefmt
= '%a, %d %b %Y %H:%M:%S ' + tzname
33 level
= logging
.DEBUG
if debug
else logging
.INFO
36 handler
= logging
.StreamHandler(stream
=sys
.stdout
)
40 logfile
= os
.path
.join(os
.path
.dirname(os
.path
.dirname(
41 os
.path
.abspath(__file__
))), 'logs', base
)
42 handler
= logging
.handlers
.RotatingFileHandler(
43 logfile
, maxBytes
=(10 * 1024 * 1024), backupCount
=1)
45 formatter
= logging
.Formatter(fmt
, datefmt
)
46 handler
.setFormatter(formatter
)
48 logger
= logging
.getLogger()
49 logger
.addHandler(handler
)
50 logger
.setLevel(level
)
53 @contextlib.contextmanager
54 def connection(host
, port
, quiet
=False):
55 """Connect to |host| on |port| and return the socket."""
56 s
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
59 print('Connecting to %s:%s ... ' %(host
, port
), end
='')
62 s
.connect((host
, port
))
63 except socket
.error
as e
:
64 logging
.fatal('%s', e
)
75 class VlcConsole(code
.InteractiveConsole
):
76 """VLC command line console."""
78 def __init__(self
, *args
, **kwargs
):
79 self
.server
= kwargs
.pop('server')
81 code
.InteractiveConsole
.__init
__(self
, *args
, **kwargs
)
83 def _drain_socket(self
):
84 """Process any remaining status from the server."""
87 self
.write(self
.server
.recv(1024 * 1024))
88 except socket
.timeout
:
91 def _send_command(self
, cmd
):
92 """Send |cmd| to the server."""
93 logging
.debug('Sending command: %s', cmd
)
94 self
.server
.send(cmd
+ '\n')
96 def _run_command(self
, cmd
, passthru
=False):
97 """Run |cmd| and return its output."""
99 self
._send
_command
(cmd
)
103 result
+= self
.server
.recv(1024 * 1024)
104 except socket
.timeout
:
111 def _load_custom_parser(self
, cmd
):
112 """Find custom parser for |cmd| as needed."""
113 parser_key
= '_custom_parser_%s' % (cmd
,)
114 parser
= getattr(self
, parser_key
, None)
116 new_parser
= argparse
.ArgumentParser(prog
=cmd
, add_help
=False)
117 parser
= getattr(self
, '_custom_getparser_%s' % (cmd
,))(new_parser
)
118 setattr(self
, parser_key
, parser
)
121 def _get_current_position(self
):
122 """Get current position in the playback (in seconds).
127 return int(self
._run
_command
('get_time').splitlines()[-1])
129 def _get_stracks(self
):
130 """Get all available tracks.
133 +----[ Subtitle Track ]
135 | 3 - Track 1 - [English]
136 | 4 - Track 2 - [Arabic] *
137 | 5 - Track 3 - [Dutch]
138 +----[ end of Subtitle Track ]
142 for line
in self
._run
_command
('strack').splitlines():
143 if line
.startswith('| '):
150 logging
.debug('Current track: %s', current
)
151 logging
.debug('Found tracks: %r', tracks
)
152 return (current
, tracks
)
154 def _get_current_volume(self
):
155 """Return current volume.
158 status change: ( audio volume: 115 )
160 vol_re
= re
.compile(r
'^status change: \( audio volume: ([0-9]+) \)')
161 for line
in self
._run
_command
('volume').splitlines():
162 m
= vol_re
.match(line
)
164 return int(m
.group(1))
167 def _custom_getparser_help(self
, parser
):
168 parser
.add_argument('command', nargs
='?')
171 # def _custom_help(self, opts):
177 def _custom_getparser_seek(self
, parser
):
178 parser
.add_argument('position',
179 help='Either a time in seconds, '
180 'or "b" for back or "f" for forward')
183 def _custom_seek(self
, opts
):
184 """Implement relative seek.
186 VLC only supports absolute seeking.
188 OFFSETS
= (0, 5, 15, 30, 60)
190 if (opts
.position
.replace('b', '') == '' or
191 opts
.position
.replace('f', '') == ''):
192 current_position
= self
._get
_current
_position
()
193 offset
= OFFSETS
[len(opts
.position
)]
194 if opts
.position
[0] == 'b':
196 position
= current_position
+ offset
199 position
= int(opts
.position
)
201 logging
.error('seek takes an int, not "%s"', opts
.position
)
204 self
._run
_command
('seek %s' % (position
,), passthru
=True)
206 def _custom_getparser_strack(self
, parser
):
207 parser
.add_argument('track', nargs
='?',
208 help='Use "n" for next track, '
209 'or a number to select')
212 def _custom_strack(self
, opts
):
213 if opts
.track
== 'n':
215 current
, tracks
= self
._get
_stracks
()
216 pos
= tracks
.index(current
) + 1
217 if pos
== len(tracks
):
221 track
= '' if opts
.track
is None else opts
.track
222 self
._run
_command
('strack %s' % (track
,), passthru
=True)
224 def _custom_getparser_vol(self
, parser
):
225 parser
.add_argument('direction', choices
=('down', 'up', 'set'))
226 parser
.add_argument('level', nargs
='?', default
='10', type=int,
227 help='{down,up}: Volume level to adjust '
229 'set: Volume level to set (percentage)')
232 def _custom_vol(self
, opts
):
233 """Implement relative volume.
235 VLC only supports absolute volume.
237 vol
= self
._get
_current
_volume
()
239 self
.write('Unknown current volume :(\n')
241 if opts
.direction
== 'up':
243 elif opts
.direction
== 'down':
246 vol
= int(256 * (opts
.level
/ 100.0))
247 self
._send
_command
('volume %s' % (vol
,))
251 # 'help': _custom_help,
252 'seek': _custom_seek
,
253 'strack': _custom_strack
,
259 """List of commands the server knows about.
262 +----[ Remote control commands ]
264 | add XYZ . . . . . . . . . . . . add XYZ to playlist
266 | quit . . . . . . . . . . . . . . . . . . . quit vlc
271 return self
._commands
273 #logging.debug('Help output: {{{%s}}}', text)
274 text
= self
._run
_command
('help')
276 for line
in text
.splitlines():
277 if line
.startswith('| '):
280 commands
.append(split
[1].rstrip('.'))
281 logging
.debug('parsed commands: %s', commands
)
282 self
._commands
= sorted(set(commands
+ self
.CUSTOM_COMMANDS
.keys()))
283 return self
._commands
285 def _completer(self
, text
, state
):
286 """Complete |text|."""
287 start
= readline
.get_begidx()
289 # Only complete commands atm, not their args.
292 if state
== 0 and text
== '':
295 for i
, command
in enumerate(self
.commands
):
296 out
+= '%-20s' % command
297 if ((i
+ 1) % 4) == 0:
298 self
.write(out
+ '\n')
301 self
.write(out
+ '\n')
303 elif state
or text
== '':
307 for command
in self
.commands
:
308 if command
.startswith(text
):
310 logging
.debug('matches: %r', ret
)
318 def completer(self
, text
, state
):
319 """Complete |text|."""
321 logging
.debug('completer: state:%s text:{%s}', state
, text
)
322 return self
._completer
(text
, state
)
324 logging
.exception('completer failed:')
326 def init_console(self
):
327 """Set up the command line interface."""
330 readline
.parse_and_bind('tab: complete')
331 readline
.set_completer(self
.completer
)
333 def runsource(self
, source
, filename
=None, symbol
=None):
334 """Run the |source| (vlc command) the user wants."""
335 logging
.debug('source=%r, filename=%r, symbol=%r',
336 source
, filename
, symbol
)
339 custom_command
= self
.CUSTOM_COMMANDS
.get(cmd
[0])
340 if not custom_command
is None:
341 if isinstance(custom_command
, str):
342 logging
.debug('command alias found: %s', custom_command
)
343 source
= ' '.join([custom_command
] + cmd
[1:])
345 logging
.debug('custom command found: %s', custom_command
)
346 parser
= self
._load
_custom
_parser
(cmd
[0])
348 opts
= parser
.parse_args(cmd
[1:])
349 custom_command(self
, opts
)
354 self
._send
_command
(source
)
355 if source
in ('logout', 'quit'):
361 """Get a command line parser."""
362 parser
= argparse
.ArgumentParser(description
=__doc__
)
363 parser
.add_argument('-H', '--host', default
='vapier',
364 help='Server to talk to')
365 parser
.add_argument('-p', '--port', default
=8001,
366 help='Port to connect to')
367 parser
.add_argument('--debug', default
=False, action
='store_true',
368 help='Enable debug mode')
373 """The main script entry point"""
374 parser
= get_parser()
375 (opts
, args
) = parser
.parse_known_args(argv
)
376 setup_logging(debug
=opts
.debug
, stdout
=True)
378 with
connection(opts
.host
, opts
.port
, quiet
=bool(args
)) as server
:
379 console
= VlcConsole(server
=server
)
381 console
.push(' '.join(args
))
383 console
.init_console()
384 console
.interact(banner
='VLC interactive console; run "help".')
387 if __name__
== '__main__':
388 exit(main(sys
.argv
[1:]))