Create an object to avoid using global variable

This commit is contained in:
Nguyễn Gia Phong 2017-04-24 11:13:12 +07:00
parent bdd2ac61cf
commit ae7618c807

243
comp Executable file → Normal file
View file

@ -23,7 +23,6 @@ from configparser import ConfigParser
from curses.ascii import ctrl
from datetime import datetime
from gettext import bindtextdomain, gettext, textdomain
from io import StringIO
from itertools import cycle
from os import linesep, makedirs
from os.path import abspath, dirname, expanduser, isfile
@ -68,14 +67,97 @@ def getlink(entry):
ie_key=entry.get('ie_key')).get('webpage_url')
def choose_from(mode1):
def _secpair2hhmmss(pos, duration):
"""Quick hack to convert a pair of seconds to HHMMSS / HHMMSS
string as MPV.get_property_osd_string isn't available.
"""
if pos is None: return ''
postime, durationtime = gmtime(pos), gmtime(duration)
# Let's hope media durations are shorter than a day
timestr = '%M:%S' if duration < 3600 else '%H:%M:%S'
return ' {} / {}'.format(strftime(timestr, postime),
strftime(timestr, durationtime))
class Comp(object):
"""Meta object for drawing and playing."""
def __new__(cls, entries, mode, mpv_vo, mpv_vid, ytdlf):
self = object.__new__(cls)
self.start = 0
self.y = 1
self.mode = mode
self.entries = entries
self.playlist = (i for i in []) # an empty generator?
self.stdscr = curses.initscr()
self.mp = MPV(input_default_bindings=True, input_vo_keyboard=True,
log_handler=mpv_logger, ytdl=True, ytdl_format=ytdlf)
if mpv_vo: self.mp['vo'] = mpv_vo
self.mp._set_property('vid', mpv_vid)
return self
def __init__(self):
setno(entries, ['current', 'error', 'playing', 'selected'])
entries[0]['current'] = True
curses.noecho()
curses.cbreak()
self.stdscr.keypad(True)
curses.curs_set(False)
curses.start_color()
curses.use_default_colors()
curses.init_pair(1, 1, -1)
curses.init_pair(2, 2, -1)
curses.init_pair(3, 3, -1)
curses.init_pair(4, 4, -1)
curses.init_pair(5, 5, -1)
curses.init_pair(6, 6, -1)
curses.init_pair(7, 7, -1)
curses.init_pair(8, -1, 7)
curses.init_pair(9, -1, 1)
curses.init_pair(10, -1, 2)
curses.init_pair(11, -1, 3)
curses.init_pair(12, -1, 4)
curses.init_pair(13, -1, 5)
curses.init_pair(14, -1, 6)
self.mp._set_property('vid', mpv_vid)
def updatestt(void): self.update_status()
self.mp.observe_property('mute', updatestt)
self.mp.observe_property('pause', updatestt)
self.mp.observe_property('time-pos', updatestt)
self.mp.observe_property('vid', updatestt)
def update_status(self, message='', msgattr=curses.A_NORMAL):
right = ' {} {}{} '.format(
_(mode), ' ' if self.mp._get_property('mute', bool) else 'A',
' ' if mp._get_property('vid') == 'no' else 'V')
try:
left = _secpair2hhmmss(self.mp._get_property('time-pos', int),
self.mp._get_property('duration', int))
except:
left += ' | ' if self.mp._get_property('pause', bool) else ' > '
self.stdscr.addstr(curses.LINES - 2, 0, right.rjust(curses.COLS),
curses.color_pair(14))
else:
stdscr.addstr(curses.LINES - 2, 0, left, curses.color_pair(14))
title_len = curses.COLS - len(left + right)
center = self.entries.setdefault('title', self.mp._get_property('media-title')).ljust(title_len)[:title_len]
self.stdscr.addstr(curses.LINES - 2, len(left), center,
curses.color_pair(14) | curses.A_BOLD)
self.stdscr.addstr(curses.LINES - 2, len(left + center), right,
curses.color_pair(14))
finally:
self.stdscr.move(curses.LINES - 1, 0)
self.stdscr.clrtoeol()
self.stdscr.addstr(curses.LINES - 1, 0, message, msgattr)
self.stdscr.refresh()
def choose_from(mode1):
if mode1 == 'all': return entries
else: return [entry for entry in entries if entry.setdefault(mode1, False)]
def playlist(mode):
"""Return a generator of entries to be played."""
action = mode.split('-')[0]
def update_playlist(self):
action = self.mode.split('-')[0]
entries2play = choose_from(mode.split('-')[1])
# Somehow yield have to be used instead of returning a generator
if action == 'play':
@ -85,68 +167,29 @@ def playlist(mode):
elif entries2play:
while True: yield choice(entries2play)
def play():
for entry in playlist(mode):
def play(self):
for entry in playlist(self.mode):
setno(entries, ['playing'])
reprint(stdscr, entries[start : start+curses.LINES-3])
entries[entries.index(entry)]['playing'] = True
reprint(stdscr, entries[start : start+curses.LINES-3])
mp.play(getlink(entry))
mp.play(entry.setdefault('webpage_url', getlink(entry)))
mp.wait_for_playback()
def secpair2hhmmss(pos, duration):
"""Quick hack to convert a pair of seconds to HHMMSS / HHMMSS
string as MPV.get_property_osd_string isn't available.
"""
if pos is None: return ''
postime, durationtime = gmtime(pos), gmtime(duration)
# Let's hope media durations are shorter than a day
timestr = '%M:%S' if duration < 3600 else '%H:%M:%S'
return '{} / {}'.format(strftime(timestr, postime),
strftime(timestr, durationtime))
def update_status(stdscr, mp, message='', msgattr=curses.A_NORMAL):
left = ' ' + secpair2hhmmss(mp._get_property('time-pos', int),
mp._get_property('duration', int))
right = ' {} {}{} '.format(_(mode),
' ' if mp._get_property('mute', bool) else 'A',
' ' if mp._get_property('vid') == 'no' else 'V')
if left != ' ':
left += ' | ' if mp._get_property('pause', bool) else ' > '
stdscr.addstr(curses.LINES - 2, 0, left, curses.color_pair(14))
title_len = curses.COLS - len(left + right)
center = mp._get_property('media-title').ljust(title_len)[:title_len]
stdscr.addstr(curses.LINES - 2, len(left), center,
curses.color_pair(14) | curses.A_BOLD)
stdscr.addstr(curses.LINES - 2, len(left + center), right,
curses.color_pair(14))
else:
stdscr.addstr(curses.LINES - 2, 0, right.rjust(curses.COLS),
curses.color_pair(14))
stdscr.move(curses.LINES - 1, 0)
stdscr.clrtoeol()
stdscr.addstr(curses.LINES - 1, 0, message, msgattr)
stdscr.refresh()
def reattr(stdscr, y, entry):
def reattr(self, entry):
invert = 8 if entry.setdefault('current', False) else 0
if entry.setdefault('error', False):
stdscr.chgat(y, 0, curses.color_pair(1 + invert) | curses.A_BOLD)
self.stdscr.chgat(self.y, 0, curses.color_pair(1 + invert) | curses.A_BOLD)
elif entry.setdefault('playing', False):
stdscr.chgat(y, 0, curses.color_pair(3 + invert) | curses.A_BOLD)
self.stdscr.chgat(self.y, 0, curses.color_pair(3 + invert) | curses.A_BOLD)
elif entry.setdefault('selected', False):
stdscr.chgat(y, 0, curses.color_pair(5 + invert) | curses.A_BOLD)
self.stdscr.chgat(self.y, 0, curses.color_pair(5 + invert) | curses.A_BOLD)
elif invert:
stdscr.chgat(y, 0, curses.color_pair(12) | curses.A_BOLD)
self.stdscr.chgat(self.y, 0, curses.color_pair(12) | curses.A_BOLD)
else:
stdscr.chgat(y, 0, curses.color_pair(0))
self.stdscr.chgat(self.y, 0, curses.color_pair(0))
def reprint(stdscr, entries2print):
def reprint(stdscr, entries2print):
stdscr.clear()
stdscr.addstr(0, 1, _('Title'))
sitenamelen = max(max(len(entry['ie_key']) for entry in entries), 6)
@ -155,25 +198,12 @@ def reprint(stdscr, entries2print):
for i, entry in enumerate(entries2print):
y = i + 1
stdscr.addstr(y, 0, entry['ie_key'].rjust(curses.COLS - 1))
stdscr.addstr(y, 1, entry['title'][:curses.COLS-sitenamelen-3])
stdscr.addstr(y, 1, entry.get('title', '')[:curses.COLS-sitenamelen-3])
reattr(stdscr, y, entry)
update_status(stdscr, mp)
def initprint(stdscr, entries):
"""Print initial content."""
global start, y
start, y = 0, 1
if not entries:
return
setno(entries, ['current', 'error', 'playing', 'selected'])
entries[0]['current'] = True
reprint(stdscr, entries[:curses.LINES-3])
def move(stdscr, entries, y, delta):
global start
if start + y + delta < 1:
def move(self, delta):
if self.start + self.y + delta < 1:
if start + y == 1:
return 1
setno(entries, ['current'])
@ -210,20 +240,23 @@ def move(stdscr, entries, y, delta):
stdscr.refresh()
return y
def close(self):
curses.nocbreak()
self.stdscr.keypad(False)
curses.echo()
curses.endwin()
self.mp.terminate()
parser = ArgumentParser(description=_("Curses Online Media Player"))
parser.add_argument('-j', '--json-playlist', required=False,
parser.add_argument('-j', '--json-playlist', required=False, metavar='path',
help=_('path to playlist in JSON format'))
parser.add_argument('-y', '--youtube-playlist', required=False,
parser.add_argument('-u', '--online-playlist', required=False, metavar='URL',
help=_('URL to an playlist on Youtube'))
args = parser.parse_args()
config = ConfigParser()
config.read(USER_CONFIG if isfile(USER_CONFIG) else SYSTEM_CONFIG)
mode = config.get('comp', 'play-mode', fallback='play-current')
video = config.get('mpv', 'video', fallback='auto')
video_output = config.get('mpv', 'video-output', fallback=None)
ytdl_opts = {'format': config.get('youtube-dl', 'format', fallback='best')}
if args.json_playlist:
json_file = args.json_playlist
@ -232,49 +265,23 @@ if args.json_playlist:
elif args.youtube_playlist:
with YoutubeDL({'extract_flat': 'in_playlist', 'quiet': True}) as ytdl:
info = ytdl.extract_info(args.youtube_playlist, download=False)
entries = info.get('entries', {})
entries = info.get('entries', [])
json_file = ''
else:
entries = []
json_file = ''
# Init curses screen
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
stdscr.keypad(True)
curses.curs_set(False)
curses.start_color()
curses.use_default_colors()
curses.init_pair(1, 1, -1)
curses.init_pair(2, 2, -1)
curses.init_pair(3, 3, -1)
curses.init_pair(4, 4, -1)
curses.init_pair(5, 5, -1)
curses.init_pair(6, 6, -1)
curses.init_pair(7, 7, -1)
curses.init_pair(8, -1, 7)
curses.init_pair(9, -1, 1)
curses.init_pair(10, -1, 2)
curses.init_pair(11, -1, 3)
curses.init_pair(12, -1, 4)
curses.init_pair(13, -1, 5)
curses.init_pair(14, -1, 6)
makedirs(dirname(mpv_log), exist_ok=True)
# Init mpv
makedirs(expanduser(dirname(MPV_LOG)), exist_ok=True)
mp = MPV(input_default_bindings=True, input_vo_keyboard=True,
log_handler=mpv_logger, ytdl=True, ytdl_format=ytdl_opts['format'])
if video_output: mp['vo'] = video_output
mp._set_property('vid', video)
mp.observe_property('mute', lambda foo: update_status(stdscr, mp))
mp.observe_property('pause', lambda foo: update_status(stdscr, mp))
mp.observe_property('time-pos', lambda foo: update_status(stdscr, mp))
mp.observe_property('vid', lambda foo: update_status(stdscr, mp))
comp = Comp(
entries=entries,
mode=config.get('comp', 'play-mode', fallback='play-current'),
mpv_vo=config.get('mpv', 'video-output', fallback=None),
mpv_vid=config.get('mpv', 'video', fallback='auto'),
ytdlf=config.get('youtube-dl', 'format', fallback='best'))
initprint(stdscr, entries)
c = stdscr.getch()
while c != 113: # letter q
while True:
c = stdscr.getch()
if c == 10: # curses.KEY_ENTER doesn't work
if not entries: continue
mp._set_property('pause', False, bool)
@ -331,6 +338,8 @@ while c != 113: # letter q
elif c == 109: # letter m
mode = MODES[(MODES.index(mode) + 1) % 8]
update_status(stdscr, mp)
elif c == 113: # letter q
comp.close()
elif c == 119: # letter w
if not entries: continue
with YoutubeDL({'format': ytdlf}) as ytdl:
@ -375,11 +384,3 @@ while c != 113: # letter q
start += y - 1
y = 1
reprint(stdscr, entries[start : start+curses.LINES-3])
c = stdscr.getch()
curses.nocbreak()
stdscr.keypad(False)
curses.echo()
curses.endwin()
del mp