Add Youtube playlist argument, seek function and patial support for translation using gettext
This commit is contained in:
parent
36f73c36f1
commit
c224c24b84
35
README.rst
35
README.rst
|
@ -25,22 +25,24 @@ this moment, I'd suggest you to use ``git`` to get the software::
|
||||||
|
|
||||||
git clone https://github.com/McSinyx/comp.git
|
git clone https://github.com/McSinyx/comp.git
|
||||||
cd comp
|
cd comp
|
||||||
./setup.py install --user
|
sudo ./setup.py install --user
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Command line arguments::
|
::
|
||||||
|
|
||||||
$ comp -h
|
$ comp --help
|
||||||
usage: comp [-h] [-j JSON_PLAYLIST]
|
usage: comp [-h] [-j JSON_PLAYLIST]
|
||||||
|
|
||||||
console/curses online media mp
|
Curses Online Media Player
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-j JSON_PLAYLIST, --json-playlist JSON_PLAYLIST
|
-j JSON_PLAYLIST, --json-playlist JSON_PLAYLIST
|
||||||
path to playlist in JSON format
|
path to playlist in JSON format
|
||||||
|
-y YOUTUBE_PLAYLIST, --youtube-playlist YOUTUBE_PLAYLIST
|
||||||
|
URL to an playlist on Youtube
|
||||||
|
|
||||||
Keyboard control
|
Keyboard control
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
@ -60,6 +62,10 @@ Keyboard control
|
||||||
+--------------+-------------------------------+
|
+--------------+-------------------------------+
|
||||||
| End | Move to the end of the list |
|
| End | Move to the end of the list |
|
||||||
+--------------+-------------------------------+
|
+--------------+-------------------------------+
|
||||||
|
| Left | Seek backward 5 seconds |
|
||||||
|
+--------------+-------------------------------+
|
||||||
|
| Right | Seek forward 5 seconds |
|
||||||
|
+--------------+-------------------------------+
|
||||||
| ``c`` | Select the current track |
|
| ``c`` | Select the current track |
|
||||||
+--------------+-------------------------------+
|
+--------------+-------------------------------+
|
||||||
| ``p`` | Start playing |
|
| ``p`` | Start playing |
|
||||||
|
@ -73,24 +79,25 @@ Keyboard control
|
||||||
| ``V`` | Toggle video |
|
| ``V`` | Toggle video |
|
||||||
+--------------+-------------------------------+
|
+--------------+-------------------------------+
|
||||||
|
|
||||||
Configurations
|
Configuration files
|
||||||
--------------
|
-------------------
|
||||||
|
|
||||||
``comp`` uses INI format for its config file, placed in
|
The system-wide configuration file is ``/etc/comp/settings.ini``, the
|
||||||
``~/.config/comp/settings.ini``::
|
user-specific one is ``~/.config/mpv/settings.ini``. Default configurations
|
||||||
|
are listed below::
|
||||||
|
|
||||||
[comp]
|
[comp]
|
||||||
# Supported 8 modes: play-current, play-all, play-selected, repeat-current,
|
# Supported 8 modes: play-current, play-all, play-selected, repeat-current,
|
||||||
# repeat-all, repeat-selected, shuffle-all and shuffle-selected
|
# repeat-all, repeat-selected, shuffle-all and shuffle-selected.
|
||||||
play-mode = shuffle-selected
|
play-mode = play-current
|
||||||
|
|
||||||
[mpv]
|
[mpv]
|
||||||
# Set if video should be download and play, I only know 2 possible values:
|
# Set if video should be download and play, I only know 2 possible values:
|
||||||
# auto and no. This can be changed later interactively.
|
# auto and no. This can be changed later interactively.
|
||||||
video = no
|
video = auto
|
||||||
# Read more on VIDEO OUTPUT DRIVERS section in mpv man page
|
# Read more on VIDEO OUTPUT DRIVERS section in mpv man page.
|
||||||
video-output = xv
|
video-output =
|
||||||
|
|
||||||
[youtube-dl]
|
[youtube-dl]
|
||||||
# Read more on youtube-dl man page
|
# Read more on FORMAT SELECTION section in youtube-dl man page.
|
||||||
format = best
|
format = best
|
||||||
|
|
187
comp
187
comp
|
@ -18,32 +18,65 @@
|
||||||
|
|
||||||
import curses
|
import curses
|
||||||
import json
|
import json
|
||||||
|
import subprocess
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
|
from datetime import datetime
|
||||||
|
from gettext import bindtextdomain, gettext, textdomain
|
||||||
|
from io import StringIO
|
||||||
from itertools import cycle
|
from itertools import cycle
|
||||||
from os.path import expanduser
|
from os import linesep, makedirs
|
||||||
|
from os.path import dirname, expanduser, isfile
|
||||||
from random import choice
|
from random import choice
|
||||||
from time import gmtime, strftime
|
from time import gmtime, strftime
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
from mpv import MPV
|
from mpv import MPV
|
||||||
|
|
||||||
|
# Init gettext
|
||||||
|
bindtextdomain('comp', 'locale')
|
||||||
|
textdomain('comp')
|
||||||
|
_ = gettext
|
||||||
|
|
||||||
|
GLOBAL_CONFIG = '/etc/comp/settings.ini'
|
||||||
|
USER_CONFIG = expanduser('~/.config/comp/settings.ini')
|
||||||
|
MPV_LOG = expanduser('~/.cache/comp/mpv.log')
|
||||||
MODES = ('play-current', 'play-all', 'play-selected', 'repeat-current',
|
MODES = ('play-current', 'play-all', 'play-selected', 'repeat-current',
|
||||||
'repeat-all', 'repeat-selected', 'shuffle-all', 'shuffle-selected')
|
'repeat-all', 'repeat-selected', 'shuffle-all', 'shuffle-selected')
|
||||||
|
# I ain't found the correct way to do this yet
|
||||||
|
_MODES = {'play-current': _('play-current'),
|
||||||
|
'play-all': _('play-all'),
|
||||||
|
'play-selected': _('play-selected'),
|
||||||
|
'repeat-current': _('repeat-current'),
|
||||||
|
'repeat-all': _('repeat-all'),
|
||||||
|
'repeat-selected': _('repeat-selected'),
|
||||||
|
'shuffle-all': _('shuffle-all'),
|
||||||
|
'shuffle-selected': _('shuffle-selected')}
|
||||||
|
MODE_STR_LEN = max(len(mode) for mode in _MODES.values())
|
||||||
|
|
||||||
|
|
||||||
def setno(data, keys):
|
def mpv_logger(loglevel, component, message):
|
||||||
"""Set all keys of each track in data to False."""
|
mpv_log = '{} [{}] {}: {}{}'.format(datetime.isoformat(datetime.now()),
|
||||||
|
loglevel, component, message, linesep)
|
||||||
|
with open(MPV_LOG, 'a') as f:
|
||||||
|
f.write(mpv_log)
|
||||||
|
|
||||||
|
|
||||||
|
def setno(entries, keys):
|
||||||
|
"""Set all keys of each track in entries to False."""
|
||||||
for key in keys:
|
for key in keys:
|
||||||
for track in data:
|
for track in entries:
|
||||||
track[key] = False
|
track[key] = False
|
||||||
|
|
||||||
|
|
||||||
def playlist(mode):
|
def playlist(mode):
|
||||||
"""Return a generator of tracks to be played."""
|
"""Return a generator of tracks to be played."""
|
||||||
action, choose_from = mode.split('-')
|
action, choose_from = mode.split('-')
|
||||||
if choose_from == 'all': tracks = data
|
if choose_from == 'all':
|
||||||
else: tracks = [track for track in data if track[choose_from]]
|
tracks = entries
|
||||||
|
else:
|
||||||
|
tracks = [track for track in entries
|
||||||
|
if track.setdefault(choose_from, False)]
|
||||||
# Somehow yield have to be used instead of returning a generator
|
# Somehow yield have to be used instead of returning a generator
|
||||||
if action == 'play':
|
if action == 'play':
|
||||||
for track in tracks: yield track
|
for track in tracks: yield track
|
||||||
|
@ -55,11 +88,13 @@ def playlist(mode):
|
||||||
|
|
||||||
def play():
|
def play():
|
||||||
for track in playlist(mode):
|
for track in playlist(mode):
|
||||||
setno(data, ['playing'])
|
entries[entries.index(track)]['playing'] = True
|
||||||
data[data.index(track)]['playing'] = True
|
reprint(stdscr, entries[start : start+curses.LINES-3])
|
||||||
reprint(stdscr, data[start : start+curses.LINES-3])
|
# Gross hack
|
||||||
mp.play('https://youtu.be/' + track['url'])
|
mp.play('https://youtu.be/' + track['url'])
|
||||||
mp.wait_for_playback()
|
mp.wait_for_playback()
|
||||||
|
entries[entries.index(track)]['playing'] = False
|
||||||
|
reprint(stdscr, entries[start : start+curses.LINES-3])
|
||||||
|
|
||||||
|
|
||||||
def secpair2hhmmss(pos, duration):
|
def secpair2hhmmss(pos, duration):
|
||||||
|
@ -77,7 +112,7 @@ def secpair2hhmmss(pos, duration):
|
||||||
def update_status_line(stdscr, mp):
|
def update_status_line(stdscr, mp):
|
||||||
left = ' ' + secpair2hhmmss(mp._get_property('time-pos', int),
|
left = ' ' + secpair2hhmmss(mp._get_property('time-pos', int),
|
||||||
mp._get_property('duration', int))
|
mp._get_property('duration', int))
|
||||||
right = ' {} {}{} '.format(mode,
|
right = ' {} {}{} '.format(_MODES[mode],
|
||||||
' ' if mp._get_property('mute', bool) else 'A',
|
' ' if mp._get_property('mute', bool) else 'A',
|
||||||
' ' if mp._get_property('vid') == 'no' else 'V')
|
' ' if mp._get_property('vid') == 'no' else 'V')
|
||||||
if left != ' ':
|
if left != ' ':
|
||||||
|
@ -96,12 +131,12 @@ def update_status_line(stdscr, mp):
|
||||||
|
|
||||||
|
|
||||||
def reattr(stdscr, y, track):
|
def reattr(stdscr, y, track):
|
||||||
invert = 8 if track['current'] else 0
|
invert = 8 if track.setdefault('current', False) else 0
|
||||||
if track['error']:
|
if track.setdefault('error', False):
|
||||||
stdscr.chgat(y, 0, curses.color_pair(1 + invert) | curses.A_BOLD)
|
stdscr.chgat(y, 0, curses.color_pair(1 + invert) | curses.A_BOLD)
|
||||||
elif track['playing']:
|
elif track.setdefault('playing', False):
|
||||||
stdscr.chgat(y, 0, curses.color_pair(3 + invert) | curses.A_BOLD)
|
stdscr.chgat(y, 0, curses.color_pair(3 + invert) | curses.A_BOLD)
|
||||||
elif track['selected']:
|
elif track.setdefault('selected', False):
|
||||||
stdscr.chgat(y, 0, curses.color_pair(5 + invert) | curses.A_BOLD)
|
stdscr.chgat(y, 0, curses.color_pair(5 + invert) | curses.A_BOLD)
|
||||||
elif invert:
|
elif invert:
|
||||||
stdscr.chgat(y, 0, curses.color_pair(12) | curses.A_BOLD)
|
stdscr.chgat(y, 0, curses.color_pair(12) | curses.A_BOLD)
|
||||||
|
@ -109,12 +144,12 @@ def reattr(stdscr, y, track):
|
||||||
stdscr.chgat(y, 0, curses.color_pair(0) | curses.A_NORMAL)
|
stdscr.chgat(y, 0, curses.color_pair(0) | curses.A_NORMAL)
|
||||||
|
|
||||||
|
|
||||||
def reprint(stdscr, data2print):
|
def reprint(stdscr, tracks2print):
|
||||||
stdscr.clear()
|
stdscr.clear()
|
||||||
stdscr.addstr(0, curses.COLS-12, 'URL')
|
stdscr.addstr(0, curses.COLS-12, _('URL'))
|
||||||
stdscr.addstr(0, 1, 'Title')
|
stdscr.addstr(0, 1, _('Title'))
|
||||||
stdscr.chgat(0, 0, curses.color_pair(10) | curses.A_BOLD)
|
stdscr.chgat(0, 0, curses.color_pair(10) | curses.A_BOLD)
|
||||||
for i, track in enumerate(data2print):
|
for i, track in enumerate(tracks2print):
|
||||||
y = i + 1
|
y = i + 1
|
||||||
stdscr.addstr(y, 0, track['url'].rjust(curses.COLS - 1))
|
stdscr.addstr(y, 0, track['url'].rjust(curses.COLS - 1))
|
||||||
stdscr.addstr(y, 1, track['title'][:curses.COLS-14])
|
stdscr.addstr(y, 1, track['title'][:curses.COLS-14])
|
||||||
|
@ -122,64 +157,74 @@ def reprint(stdscr, data2print):
|
||||||
update_status_line(stdscr, mp)
|
update_status_line(stdscr, mp)
|
||||||
|
|
||||||
|
|
||||||
def move(stdscr, data, y, delta):
|
def move(stdscr, entries, y, delta):
|
||||||
global start
|
global start
|
||||||
if start + y + delta < 1:
|
if start + y + delta < 1:
|
||||||
if start + y == 1:
|
if start + y == 1:
|
||||||
return 1
|
return 1
|
||||||
start = 0
|
start = 0
|
||||||
setno(data, ['current'])
|
setno(entries, ['current'])
|
||||||
data[0]['current'] = True
|
entries[0]['current'] = True
|
||||||
reprint(stdscr, data[:curses.LINES-3])
|
reprint(stdscr, entries[:curses.LINES-3])
|
||||||
return 1
|
return 1
|
||||||
elif start + y + delta > len(data):
|
elif start + y + delta > len(entries):
|
||||||
if start + y == len(data):
|
if start + y == len(entries):
|
||||||
return curses.LINES - 3
|
return curses.LINES - 3
|
||||||
start = len(data) - curses.LINES + 3
|
start = len(entries) - curses.LINES + 3
|
||||||
y = curses.LINES - 3
|
y = curses.LINES - 3
|
||||||
setno(data, ['current'])
|
setno(entries, ['current'])
|
||||||
data[-1]['current'] = True
|
entries[-1]['current'] = True
|
||||||
reprint(stdscr, data[-curses.LINES+3:])
|
reprint(stdscr, entries[-curses.LINES+3:])
|
||||||
return y
|
return y
|
||||||
|
|
||||||
if y + delta < 1:
|
if y + delta < 1:
|
||||||
start += y + delta - 1
|
start += y + delta - 1
|
||||||
y = 1
|
y = 1
|
||||||
setno(data, ['current'])
|
setno(entries, ['current'])
|
||||||
data[start]['current'] = True
|
entries[start]['current'] = True
|
||||||
reprint(stdscr, data[start : start+curses.LINES-3])
|
reprint(stdscr, entries[start : start+curses.LINES-3])
|
||||||
elif y + delta > curses.LINES - 3:
|
elif y + delta > curses.LINES - 3:
|
||||||
start += y + delta - curses.LINES + 3
|
start += y + delta - curses.LINES + 3
|
||||||
y = curses.LINES - 3
|
y = curses.LINES - 3
|
||||||
setno(data, ['current'])
|
setno(entries, ['current'])
|
||||||
data[start + curses.LINES - 4]['current'] = True
|
entries[start + curses.LINES - 4]['current'] = True
|
||||||
reprint(stdscr, data[start : start+curses.LINES-3])
|
reprint(stdscr, entries[start : start+curses.LINES-3])
|
||||||
else:
|
else:
|
||||||
data[start + y - 1]['current'] = False
|
entries[start + y - 1]['current'] = False
|
||||||
reattr(stdscr, y, data[start + y - 1])
|
reattr(stdscr, y, entries[start + y - 1])
|
||||||
y = y + delta
|
y = y + delta
|
||||||
data[start + y - 1]['current'] = True
|
entries[start + y - 1]['current'] = True
|
||||||
reattr(stdscr, y, data[start + y - 1])
|
reattr(stdscr, y, entries[start + y - 1])
|
||||||
stdscr.refresh()
|
stdscr.refresh()
|
||||||
return y
|
return y
|
||||||
|
|
||||||
|
|
||||||
parser = ArgumentParser(description="console/curses online media mp")
|
parser = ArgumentParser(description="Curses Online Media Player")
|
||||||
parser.add_argument('-j', '--json-playlist', required=False,
|
parser.add_argument('-j', '--json-playlist', required=False,
|
||||||
help='path to playlist in JSON format')
|
help=_('path to playlist in JSON format'))
|
||||||
|
parser.add_argument('-y', '--youtube-playlist', required=False,
|
||||||
|
help=_('URL to an playlist on Youtube'))
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
config = ConfigParser()
|
config = ConfigParser()
|
||||||
config.read(expanduser('~/.config/comp/settings.ini'))
|
config.read(USER_CONFIG if isfile(USER_CONFIG) else GLOBAL_CONFIG)
|
||||||
mode = config.get('comp', 'play-mode', fallback='play-all')
|
mode = config.get('comp', 'play-mode', fallback='play-current')
|
||||||
video = config.get('mpv', 'video', fallback='auto')
|
video = config.get('mpv', 'video', fallback='auto')
|
||||||
video_output = config.get('mpv', 'video-output', fallback='')
|
video_output = config.get('mpv', 'video-output', fallback=None)
|
||||||
ytdlf = config.get('youtube-dl', 'format', fallback='best')
|
ytdlf = config.get('youtube-dl', 'format', fallback='best')
|
||||||
|
|
||||||
with open(args.json_playlist) as f:
|
if args.json_playlist:
|
||||||
data = json.load(f)
|
with open(args.json_playlist) as f:
|
||||||
setno(data, ['error', 'playing', 'selected', 'current'])
|
entries = json.load(f)['entries']
|
||||||
|
elif args.youtube_playlist:
|
||||||
|
# Extremely gross hack
|
||||||
|
raw_json = subprocess.run(['youtube-dl', '--flat-playlist',
|
||||||
|
'--dump-single-json', args.youtube_playlist],
|
||||||
|
stdout=subprocess.PIPE).stdout
|
||||||
|
entries = json.load(StringIO(raw_json.decode()))['entries']
|
||||||
|
#setno(entries, ['error', 'playing', 'selected', 'current'])
|
||||||
|
|
||||||
|
# Init curses screen
|
||||||
stdscr = curses.initscr()
|
stdscr = curses.initscr()
|
||||||
curses.noecho()
|
curses.noecho()
|
||||||
curses.cbreak()
|
curses.cbreak()
|
||||||
|
@ -202,8 +247,10 @@ curses.init_pair(12, -1, 4)
|
||||||
curses.init_pair(13, -1, 5)
|
curses.init_pair(13, -1, 5)
|
||||||
curses.init_pair(14, -1, 6)
|
curses.init_pair(14, -1, 6)
|
||||||
|
|
||||||
|
# Init mpv
|
||||||
|
makedirs(expanduser(dirname(MPV_LOG)), exist_ok=True)
|
||||||
mp = MPV(input_default_bindings=True, input_vo_keyboard=True,
|
mp = MPV(input_default_bindings=True, input_vo_keyboard=True,
|
||||||
ytdl=True, ytdl_format=ytdlf)
|
log_handler=mpv_logger, ytdl=True, ytdl_format=ytdlf)
|
||||||
if video_output: mp['vo'] = video_output
|
if video_output: mp['vo'] = video_output
|
||||||
mp._set_property('vid', video)
|
mp._set_property('vid', video)
|
||||||
mp.observe_property('mute', lambda foo: update_status_line(stdscr, mp))
|
mp.observe_property('mute', lambda foo: update_status_line(stdscr, mp))
|
||||||
|
@ -214,33 +261,46 @@ mp.observe_property('vid', lambda foo: update_status_line(stdscr, mp))
|
||||||
# Print initial content
|
# Print initial content
|
||||||
start = 0
|
start = 0
|
||||||
y = 1
|
y = 1
|
||||||
data[0]['current'] = True
|
entries[0]['current'] = True
|
||||||
reprint(stdscr, data[:curses.LINES-3])
|
reprint(stdscr, entries[:curses.LINES-3])
|
||||||
|
|
||||||
# mpv keys: []{}<>.,qQ/*90m-#fTweoPOvjJxzlLVrtsSIdA
|
|
||||||
# yuighkcbn
|
|
||||||
c = stdscr.getch()
|
c = stdscr.getch()
|
||||||
while c != 113: # letter q
|
while c != 113: # letter q
|
||||||
if c == curses.KEY_RESIZE:
|
if c == curses.KEY_RESIZE:
|
||||||
curses.update_lines_cols()
|
curses.update_lines_cols()
|
||||||
start += y - 1
|
if curses.COLS < MODE_STR_LEN + 42 or curses.LINES < 4:
|
||||||
y = 1
|
stdscr.clear()
|
||||||
reprint(stdscr, data[start : start+curses.LINES-3])
|
scr_size_warn = 'Current size: {}x{}. Minimum size: {}x4.'.format(
|
||||||
|
curses.COLS,
|
||||||
|
curses.LINES,
|
||||||
|
MODE_STR_LEN + 42
|
||||||
|
)
|
||||||
|
stdscr.addstr(0, 0, scr_size_warn)
|
||||||
|
else:
|
||||||
|
start += y - 1
|
||||||
|
y = 1
|
||||||
|
reprint(stdscr, entries[start : start+curses.LINES-3])
|
||||||
elif c in (107, curses.KEY_UP): # letter k or up arrow
|
elif c in (107, curses.KEY_UP): # letter k or up arrow
|
||||||
y = move(stdscr, data, y, -1)
|
y = move(stdscr, entries, y, -1)
|
||||||
elif c in (106, curses.KEY_DOWN): # letter j or down arrow
|
elif c in (106, curses.KEY_DOWN): # letter j or down arrow
|
||||||
y = move(stdscr, data, y, 1)
|
y = move(stdscr, entries, y, 1)
|
||||||
elif c == curses.KEY_PPAGE: # page up
|
elif c == curses.KEY_PPAGE: # page up
|
||||||
y = move(stdscr, data, y, 4 - curses.LINES)
|
y = move(stdscr, entries, y, 4 - curses.LINES)
|
||||||
elif c == curses.KEY_NPAGE: # page down
|
elif c == curses.KEY_NPAGE: # page down
|
||||||
y = move(stdscr, data, y, curses.LINES - 4)
|
y = move(stdscr, entries, y, curses.LINES - 4)
|
||||||
elif c == curses.KEY_HOME: # home
|
elif c == curses.KEY_HOME: # home
|
||||||
y = move(stdscr, data, y, -len(data))
|
y = move(stdscr, entries, y, -len(entries))
|
||||||
elif c == curses.KEY_END: # end
|
elif c == curses.KEY_END: # end
|
||||||
y = move(stdscr, data, y, len(data))
|
y = move(stdscr, entries, y, len(entries))
|
||||||
|
elif c == curses.KEY_LEFT: # left arrow
|
||||||
|
if mp._get_property('duration', int):
|
||||||
|
mp.seek(-2.5)
|
||||||
|
elif c == curses.KEY_RIGHT: # right arrow
|
||||||
|
if mp._get_property('duration', int):
|
||||||
|
mp.seek(2.5)
|
||||||
elif c == 99: # letter c
|
elif c == 99: # letter c
|
||||||
data[start + y - 1]['selected'] = not data[start + y - 1]['selected']
|
entries[start + y - 1]['selected'] = not entries[start + y - 1]['selected']
|
||||||
y = move(stdscr, data, y, 1)
|
y = move(stdscr, entries, y, 1)
|
||||||
elif c == 112: # letter p
|
elif c == 112: # letter p
|
||||||
mp._set_property('pause', False, bool)
|
mp._set_property('pause', False, bool)
|
||||||
play_thread = Thread(target=play)
|
play_thread = Thread(target=play)
|
||||||
|
@ -257,7 +317,8 @@ while c != 113: # letter q
|
||||||
elif c == 65: # letter A
|
elif c == 65: # letter A
|
||||||
mp._set_property('mute', not mp._get_property('mute', bool), bool)
|
mp._set_property('mute', not mp._get_property('mute', bool), bool)
|
||||||
elif c == 86: # letter V
|
elif c == 86: # letter V
|
||||||
mp._set_property('vid', 'auto' if mp._get_property('vid') == 'no' else 'no')
|
mp._set_property('vid',
|
||||||
|
'auto' if mp._get_property('vid') == 'no' else 'no')
|
||||||
c = stdscr.getch()
|
c = stdscr.getch()
|
||||||
|
|
||||||
curses.nocbreak()
|
curses.nocbreak()
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,66 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"POT-Creation-Date: 2017-04-05 11:00+0700\n"
|
||||||
|
"PO-Revision-Date: 2017-04-05 11:05+0700\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
"X-Generator: Poedit 1.8.11\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
|
"Language: vi_VN\n"
|
||||||
|
|
||||||
|
#: comp:42
|
||||||
|
msgid "play-current"
|
||||||
|
msgstr "chơi-một"
|
||||||
|
|
||||||
|
#: comp:43
|
||||||
|
msgid "play-all"
|
||||||
|
msgstr "chơi-tất-cả"
|
||||||
|
|
||||||
|
#: comp:44
|
||||||
|
msgid "play-selected"
|
||||||
|
msgstr "chơi-đã-chọn"
|
||||||
|
|
||||||
|
#: comp:45
|
||||||
|
msgid "repeat-current"
|
||||||
|
msgstr "lặp-một"
|
||||||
|
|
||||||
|
#: comp:46
|
||||||
|
msgid "repeat-all"
|
||||||
|
msgstr "lặp-tất-cả"
|
||||||
|
|
||||||
|
#: comp:47
|
||||||
|
msgid "repeat-selected"
|
||||||
|
msgstr "lặp-đã-chọn"
|
||||||
|
|
||||||
|
#: comp:48
|
||||||
|
msgid "shuffle-all"
|
||||||
|
msgstr "ngẫu-nhiên-tất-cả"
|
||||||
|
|
||||||
|
#: comp:49
|
||||||
|
msgid "shuffle-selected"
|
||||||
|
msgstr "ngẫu-nhiên-đã-chọn"
|
||||||
|
|
||||||
|
#: comp:144
|
||||||
|
msgid "URL"
|
||||||
|
msgstr "URL"
|
||||||
|
|
||||||
|
#: comp:145
|
||||||
|
msgid "Title"
|
||||||
|
msgstr "Tiêu đề"
|
||||||
|
|
||||||
|
#: comp:199
|
||||||
|
msgid "path to playlist in JSON format"
|
||||||
|
msgstr "đường dẫn đến playlist ở định dạng JSON"
|
||||||
|
|
||||||
|
#: comp:201
|
||||||
|
msgid "URL to an playlist on Youtube"
|
||||||
|
msgstr "URL của playlist trên Youtube"
|
12
settings.ini
12
settings.ini
|
@ -1,15 +1,15 @@
|
||||||
[comp]
|
[comp]
|
||||||
# Supported 8 modes: play-current, play-all, play-selected, repeat-current,
|
# Supported 8 modes: play-current, play-all, play-selected, repeat-current,
|
||||||
# repeat-all, repeat-selected, shuffle-all and shuffle-selected
|
# repeat-all, repeat-selected, shuffle-all and shuffle-selected.
|
||||||
play-mode = shuffle-selected
|
play-mode = play-current
|
||||||
|
|
||||||
[mpv]
|
[mpv]
|
||||||
# Set if video should be download and play, I only know 2 possible values:
|
# Set if video should be download and play, I only know 2 possible values:
|
||||||
# auto and no. This can be changed later interactively.
|
# auto and no. This can be changed later interactively.
|
||||||
video = no
|
video = auto
|
||||||
# Read more on VIDEO OUTPUT DRIVERS section in mpv man page
|
# Read more on VIDEO OUTPUT DRIVERS section in mpv man page.
|
||||||
video-output = xv
|
video-output =
|
||||||
|
|
||||||
[youtube-dl]
|
[youtube-dl]
|
||||||
# Read more on youtube-dl man page
|
# Read more on FORMAT SELECTION section in youtube-dl man page.
|
||||||
format = best
|
format = best
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -12,7 +12,7 @@ setup(name = 'comp', version = '0.1.1a1',
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
author = 'McSinyx', author_email = 'vn.mcsinyx@gmail.com',
|
author = 'McSinyx', author_email = 'vn.mcsinyx@gmail.com',
|
||||||
py_modules = ['mpv'], scripts=['comp'],
|
py_modules = ['mpv'], scripts=['comp'],
|
||||||
data_files=[(expanduser('~/.config/comp'), ['settings.ini'])],
|
data_files=[('/etc/comp', ['settings.ini'])],
|
||||||
classifiers = [
|
classifiers = [
|
||||||
'Development Status :: 3 - Alpha',
|
'Development Status :: 3 - Alpha',
|
||||||
'Environment :: Console :: Curses',
|
'Environment :: Console :: Curses',
|
||||||
|
|
Loading…
Reference in New Issue