Compare commits

..

86 Commits

Author SHA1 Message Date
Nguyễn Gia Phong 30c0f00b86 Clean up 2017-08-29 15:48:51 +07:00
Nguyễn Gia Phong 7575707703 Make compatible with python-mpv 0.3 2017-08-07 14:23:02 +07:00
Nguyễn Gia Phong cee431495d Change mpv and youtube-dl links 2017-07-04 23:58:18 +07:00
Nguyễn Gia Phong d70dcf5166 Update documentation and drop gettext support for argparse help strings 2017-07-04 23:51:05 +07:00
Nguyễn Gia Phong e824f80116 Fix JSON playlist dumper and edit keybindings 2017-07-04 15:51:24 +07:00
Nguyễn Gia Phong 31c6532956 Update documentation 2017-07-03 20:52:53 +07:00
Nguyễn Gia Phong f0b95cfa86 Fix class calling 2017-07-03 20:46:08 +07:00
Nguyễn Gia Phong 408e3c869f Fix youtube-dl stderr garbage printing and edit installation installation instruction 2017-06-25 20:34:10 +07:00
Nguyễn Gia Phong 0066d60a4e Update documentations and clean up (?) 2017-06-25 17:38:19 +07:00
Nguyễn Gia Phong 21937f383c Update Vietnamese translation 2017-06-14 12:32:22 +07:00
Nguyễn Gia Phong b37b4440ac Improve info extractor 2017-06-14 12:21:54 +07:00
Nguyễn Gia Phong 27d0c98469 Improve printing (slightly better performance) 2017-06-12 13:37:42 +07:00
Nguyễn Gia Phong 4928ee1c6e Clean up everything 2017-06-10 22:14:15 +07:00
Nguyễn Gia Phong fd853b559e Code clean-up 2017-05-29 20:38:09 +07:00
Nguyễn Gia Phong 5be79fd930 Begin to create an independant back-end object 2017-05-28 21:02:34 +07:00
Nguyễn Gia Phong 8bdee9017e Sync with jaseg/python-mpv 2017-05-15 11:02:56 +07:00
Nguyễn Gia Phong a038e355e7 Add search function 2017-05-07 15:59:54 +07:00
Nguyễn Gia Phong 1cd22a5ce9 Refine codebase 2017-05-01 16:19:49 +07:00
Nguyễn Gia Phong 63a3267b6e Refactor partially (now more things are broken) 2017-04-27 21:36:10 +07:00
Nguyễn Gia Phong ae7618c807 Create an object to avoid using global variable 2017-04-24 11:13:12 +07:00
Nguyễn Gia Phong bdd2ac61cf Fix the bug when the playlist is shorter than the screen 2017-04-10 20:53:17 +07:00
Nguyễn Gia Phong d01194223d Import youtube-dl 2017-04-08 20:53:48 +07:00
Nguyễn Gia Phong 894fa77fd6 Add save playlist function and complete Vietnamese translation 2017-04-06 23:02:54 +07:00
Nguyễn Gia Phong c224c24b84 Add Youtube playlist argument, seek function and patial support for translation using gettext 2017-04-05 11:39:04 +07:00
Nguyễn Gia Phong 36f73c36f1 Update documentation and setup 2017-04-04 11:37:29 +07:00
Nguyễn Gia Phong 8a79cafddd Add repeat and shuffle mode 2017-04-03 20:52:59 +07:00
Nguyễn Gia Phong fefa352ae1 Add playing title to statusline and audio, video toggle feature 2017-04-02 21:04:57 +07:00
Nguyễn Gia Phong 14a8c21c24 Add pause function and time-pos 2017-03-30 11:14:51 +07:00
Nguyễn Gia Phong 861c2e4612 Standardize function calls 2017-03-26 15:26:37 +07:00
Nguyễn Gia Phong 082cdc3222 Refactor to not use curses.wrapper 2017-03-24 21:57:19 +07:00
Nguyễn Gia Phong 202280f760 Add bottom panel and improve data structure 2017-03-23 21:37:52 +07:00
Nguyễn Gia Phong f5ea68509e Initial comp commit 2017-03-21 20:33:11 +07:00
jaseg 8b9411ec54 Simplify initialization logic somewhat 2017-01-06 14:41:46 +01:00
Frechdachs 9cc3a25c73 Make sure _mpv_initialize is called before _mpv_terminate_destroy 2017-01-06 13:52:50 +01:00
jaseg 9fa18058ad Make so/DLL loading more robust
* Print a proper error message if shared object not found on unix
* Abide by local conventions and look for DLL in script's directory on windows
2017-01-04 12:46:17 +01:00
jaseg eb8b6a05d7 Clarify event thread handling in the README 2016-11-23 10:20:49 +01:00
jaseg efbf182723 Fix MPV.terminate so it can be called from event handlers 2016-09-15 02:00:28 +02:00
jaseg 667ec6f180 Fix property access in README example 2016-08-25 07:41:34 +02:00
jaseg b71e8a4a55 Small refactoring of with usage in tests 2016-08-24 11:42:17 +02:00
jaseg 1ee4361bdd Pimp loadfile to accept per-file options 2016-08-20 11:20:20 +02:00
jaseg 669c4bbfec BREAKING 💥 Improve property handling 2016-08-20 09:54:53 +02:00
jaseg be8d6897eb Fix key binding handling for duplicate bindings and commands 2016-08-19 18:52:37 +02:00
jaseg aaddc52da4 Set LC_NUMERIC to C to avoid segfaults on some platforms 2016-08-19 17:54:32 +02:00
jaseg ab8b8b5477 Improve event handling, add message handling, add key binding foo 2016-08-17 23:21:19 +02:00
jaseg de7b671103 Finally add node handling, fix ALL THE THINGS
* New node handling
 * Add remaining properties
 * Improve property type handling (no more ynbool!)
 * Add pröper option access
 * Add a whole bunch of tests
2016-08-13 19:14:14 +02:00
jaseg 4d6c17d342 WIP 2016-08-13 18:19:39 +02:00
jaseg 97d929e27f Add loads of new properties 2016-08-13 01:30:03 +02:00
jaseg a8be9bd534 More tests 2016-08-13 01:29:35 +02:00
jaseg adfe131be9 Improve flag handling 2016-08-13 01:28:33 +02:00
Frechdachs b6d2b514d5 Fix spaces/tabs inconsistency
Since 230f0078fe python throws a TabError exception.
2016-08-12 23:09:18 +02:00
jaseg 230f0078fe Fix filesystem encoding on windows 2016-08-09 23:38:21 +02:00
jaseg 1feab17c18 💥 breaking: Call observe_property handler with correct type arg 2016-08-07 19:49:55 +02:00
jaseg 1bde43a4d8 Update README with more instructions 2016-08-07 19:49:37 +02:00
jaseg 1acc39885f Keep wait_for_playback in waiting state during PAUSE
...to allow proper playback of network resources even in case of
buffering
2016-08-07 19:40:28 +02:00
jaseg dc1c4d89a5 💥 breaking: Pass individual arguments to log_handler 2016-08-07 19:14:26 +02:00
jaseg 14bb2c3223 Use utf-8 facing the API
In libmpv's client.h it is said that the API uses utf-8 for everything
except for filenames, for which it uses the system's default filename
encoding. We now try to honor this.
2016-08-07 19:02:29 +02:00
jaseg 2bff338c83 Add filesystem encoding handling 2016-08-07 18:56:07 +02:00
jaseg 7c1343f03d Remove trailing whitespace in mpv.py 2016-08-05 02:35:07 +02:00
jaseg a960a2ed4e Add GL API stuff 2016-08-05 02:35:00 +02:00
jaseg dfdc201ac7 Fix video_params list access 2016-08-05 02:17:39 +02:00
jaseg 24c70a5ac8 Some more lowercase for readability 2016-08-05 02:17:10 +02:00
jaseg 47919b0ca8 Fix event callback foo 2016-08-04 17:06:34 +02:00
jaseg 0c5530aa27 Fix log handling 2016-08-04 16:38:25 +02:00
jaseg 588d74938c Make video-pan-x and video-pan-y floats as they should be 2016-08-04 16:23:44 +02:00
jaseg 73f2e87f66 Fix property observe handler hash handling 2016-08-04 16:19:36 +02:00
jaseg ae8770df30 Make ynbool python2-compatible 2016-08-02 13:45:58 +02:00
jaseg 35de5ecc93 Make this python2 compatible 2016-07-31 21:29:03 +02:00
jaseg 8d8b061fcc Use ctypes.util.find_library to find libmpv. Thanks to @mozbugbox 2016-07-01 13:12:02 +02:00
jaseg f3cab6cdf1 Add tests 2016-02-19 15:34:34 +01:00
jaseg 51869a9184 Properly release resources after use
Namely, let the event handler thread terminate itself cleanly and remove
reference leak so __del__ can do its job.
2016-02-19 14:06:59 +01:00
Frechdachs 1dd8329a42 Fix DEFAULT_ERROR_HANDLER 2016-02-19 08:33:48 +01:00
Frechdachs ac30a66ed4 Implement '==' and '!=' for ynbool
'ynbool(True) == "yes"', 'ynbool(True) == True' and 'ynbool(True) == ynbool(True)' were not working.
2016-02-19 08:30:53 +01:00
Frechdachs c42fe539ab Improve Windows support
Windows users don't have to edit the source code anymore.
2016-02-12 14:09:21 +01:00
Frechdachs bcd8166829 Fix property getter for non-available properties
Properties which are not currently available weren't handled properly: The getter for a property that is not available with proptype 'str' would return "None" (as a string) instead of None. Trying to retrieve a non-available property with proptype 'int' would raise a TypeError, because the getter tries to call 'int(None)'. An alternative would be to raise some kind of exception for non-available properties, but I would prefer the getter to return None. For example None should be a valid value for the property 'path' if no video is loaded yet.
2016-02-12 14:09:21 +01:00
Frechdachs ee8316a282 Fix percent-pos property
The 'percent-pos' property has to be float. Trying to retrieve this property was raising an exception, because e.g. 'int("0.0000")' raises a ValueError.
2016-02-12 14:09:21 +01:00
jaseg 5605380fcb Improved log handling 2016-02-12 14:07:12 +01:00
Frechdachs ec91bf3e57 Fix unobserve_property
There were three problems:

1. MPV.unobserve_property called _mpv_observe_property instead of _mpv_unobserve_property.
2. _mpv_unobserve_property returns the number of properties that were assigned to the handler that is being removed. Because the return value is not 0 in such a case, ErrorCode.raise_for_ec tries to raise an error. To fix that, I changed the ErrorCode.raise_for_ec functon not to raise an error if ec is larger than 0. (If there is a positive return value, there should not have been an error anyway, if I'm not mistaken.)
3. Calling MPV.unobserve_property for a handle that is not currently been used, should not result in a KeyError being raised.

An alternative to 2. would be to add a restype to the _handle_func of 'mpv_unobserve_property' and returning that value in MPV.unobserve_property. (That way, raise_for_ec is never called.)  But I don't think this value is useful in any way. Even the built in lua interface does not return that value.
2016-02-09 22:53:52 +01:00
jaseg 694a2c2c62 Add observe_property support 2016-02-08 23:50:45 +01:00
jaseg a1c170d6ff Remove asyncio for windows support 2016-01-04 13:20:42 +01:00
jaseg fb1704c4dc Fixed accidental python2-ism in ynbool. Fixes #1
Thanks to u8sand from github for this.
2015-11-01 18:22:54 +01:00
jaseg 28b761627f Small beautifications 2015-05-10 17:55:06 +02:00
jaseg df80c10799 Added vim swap files to the .gitignore 2014-08-14 00:34:12 +02:00
jaseg b19b37708b Fixed encoding handling to use the system's default encoding 2014-08-14 00:15:17 +02:00
jaseg f74f0a3191 Removed a bunch of asserts and added some doc
Now leaving command parameter value checking to libmpv.
2014-08-14 00:01:21 +02:00
jaseg baee4f4e22 Added support for yet-unknown error codes 2014-08-13 23:51:07 +02:00
jaseg 2e6516aa32 Added optional loading of liblua 2014-08-13 23:28:28 +02:00
10 changed files with 458 additions and 730 deletions

View File

@ -73,196 +73,80 @@ Open a Youtube playlist with video height lower than 720::
Keyboard control
----------------
Bindings inherited from mpv
^^^^^^^^^^^^^^^^^^^^^^^^^^^
For convenience purpose, I try to mimic **mpv** default keybindings, but many
are slightly different from **mpv** exact behaviour (mainly because of the lack
of keys which are unsupported by ``curses``). So I will list all of them here
for you to `compare <https://github.com/mpv-player/mpv/blob/master/DOCS/man/mpv.rst#keyboard-control>`_:
Left and Right
Seek backward/forward 5 seconds. Shifted arrow does a 1 second seek.
Up and Down
Seek backward/forward 1 minute.
``[`` and ``]``
Decrease/increase current playback speed by 10%.
``{`` and ``}``
Halve/double current playback speed.
Backspace
Reset playback speed to normal.
``<`` and ``>``
Go backward/forward in the playlist.
Return
Start playing.
Space / ``p``
Pause (pressing again unpauses).
Space, ``p``
Toggle pause.
``.``
Step forward. Pressing once will pause, every consecutive press will play
one frame and then go into pause mode again.
``,``
Step backward. Pressing once will pause, every consecutive press will play
one frame in reverse and then go into pause mode again.
``q``
Stop playing and quit.
``/`` / ``9`` and ``*`` / ``0``
Decrease/increase volume.
``m``
Mute sound.
``_``
Cycle through the available video tracks.
``#``
Cycle through the available audio tracks.
``f``
Toggle fullscreen.
``T``
Toggle stay-on-top.
``w`` and ``e``
Decrease/increase pan-and-scan range.
``o`` / ``P``
Show progression bar, elapsed time and total duration on the OSD.
``O``
Toggle OSD states between normal and playback time/duration.
``v``
Toggle subtitle visibility.
``j`` and ``J``
Cycle through the available subtitles.
``x`` and ``z``
Adjust subtitle delay by +/- 0.1 seconds.
``l``
Set/clear A-B loop points.
``L``
Toggle infinite looping.
Ctrl-``+`` and Ctrl-``-``
Adjust audio delay (A/V sync) by +/- 0.1 seconds.
``u``
Switch between applying no style overrides to SSA/ASS subtitles, and
overriding them almost completely with the normal subtitle style.
``V``
Toggle subtitle VSFilter aspect compatibility mode.
``r`` and ``t``
Move subtitles up/down.
``s``
Take a screenshot.
``S``
Take a screenshot, without subtitles.
Alt-``s``
Take a screenshot each frame.
Page Up and Page Down
Seek to the beginning of the previous/next chapter.
``d``
Activate/deactivate deinterlacer.
``A``
Cycle aspect ratio override.
``1`` and ``2``
Adjust contrast.
``3`` and ``4``
Adjust brightness.
``5`` and ``6``
Adjust gamma.
``7`` and ``8``
Adjust saturation.
Alt-``0``
Resize video window to half its original size.
Alt-``1``
Resize video window to its original size.
Alt-``2``
Resize video window to double its original size.
``E``
Cycle through editions.
Movements and selections
^^^^^^^^^^^^^^^^^^^^^^^^
The following keybindings are Emacs-like since most characters are taken by
**mpv**.
Ctrl-``p`` and Ctrl-``n``
Move a single line up/down.
Alt-``v`` and Ctrl-``v``
Move a single page up/down.
Home / Ctrl-``<`` and End / Ctrl-``>``
Move to the beginning/end of the playlist.
Ctrl-Space
Deselect/reselect the current entry and move down a line.
Playlist manipulation
^^^^^^^^^^^^^^^^^^^^^
Ctrl-``o``
Open playlist.
Ctrl-``i``
Insert playlist.
Ctrl-``f`` and Alt-``f``
``/``, ``?``
Search forward/backward for a pattern.
Alt-``m``
Cycle through playing modes.
``<``, ``>``
Go backward/forward in the playlist.
Delete
``A``
Toggle mute.
``D``
Delete the current entry.
``N``
Repeat previous search in reverse direction.
``V``
Toggle video.
``W``
Save the current playlist under JSON format.
``d``
Deselect/reselect the current entry.
``i``
Insert playlist.
``m``, ``M``
Cycle forward/backward through playing modes.
``n``
Repeat previous search.
``o``
Open playlist.
Up, ``k``
Move a single line up.
Down, ``j``
Move a single line down.
Left, ``h``
Seek backward 5 seconds.
Right, ``l``
Seek forward 5 seconds.
Home
Move to the beginning of the playlist.
End
Move to the end of the playlist.
Page Up
Move a single page up.
Page Down
Move a single page down.
F5
Redraw the screen content.
``:``
Execute a **mpv** command.
Configuration files
-------------------
If not specified by the ``--config``, (user-specific) configuration file is
``~/.config/comp/settings.ini``. Default configurations
``~/.config/mpv/settings.ini``. Default configurations
are listed below::
[comp]
@ -272,20 +156,16 @@ are listed below::
play-mode = play-current
[mpv]
# Options to be parsed to mpv. See OPTIONS section on mpv(1) man pages for
# its complete list of available options.
# For example:
#vo = xv
#ontop = yes
#border = no
#force-window = yes
#autofit = 500x280
#geometry = -15-50
# Initial video channel. auto selects the default, no disables video.
video = auto
# Specify the video output backend to be used. See VIDEO OUTPUT DRIVERS in
# mpv(1) man page for details and descriptions of available drivers.
video-output =
[youtube-dl]
# Video format/quality to be passed to youtube-dl. See FORMAT SELECTION in
# youtube-dl(1) man page for more details and descriptions.
format = bestvideo+bestaudio
format = best
Bugs

329
comp
View File

@ -16,22 +16,21 @@
#
# Copyright (C) 2017 Nguyễn Gia Phong <vn.mcsinyx@gmail.com>
__version__ = '0.4.6'
import curses
import json
import re
from argparse import ArgumentParser
from collections import deque
from configparser import ConfigParser
from curses.ascii import ctrl, alt
from functools import reduce
from gettext import bindtextdomain, gettext as _, textdomain
from os.path import expanduser
from os import makedirs
from os.path import abspath, dirname, expanduser, expandvars
from threading import Thread
from traceback import print_exception
from mpv import MPV
from pkg_resources import resource_filename
from youtube_dl import YoutubeDL
from omp import extract_info, Omp
@ -67,18 +66,19 @@ class Comp(Omp):
playing (int): index of playing track in played
playlist (iterator): iterator of tracks according to mode
reading (bool): flag show if user input is being read
search_str (str): regex search string
search_res (iterator): title-searched results
scr (curses WindowObject): curses window object
start (int): index of the first track to be printed on screen
vid (str): flag show if video output is enabled
y (int): the current y-coordinate
"""
def __new__(cls, entries, json_file, mode, mpv_args, ytdlf):
def __new__(cls, entries, json_file, mode, mpv_vid, mpv_vo, ytdlf):
self = object.__new__(cls)
self.play_backward, self.reading = False, False
self.playing, self.start, self.y = -1, 0, 1
self.json_file, self.mode = json_file, mode
self.json_file, self.mode, self.vid = json_file, mode, mpv_vid
self.entries, self.played = entries, []
self.playlist, self.search_str = iter(()), ''
self.playlist, self.search_res = iter(()), deque()
self.mp = MPV(input_default_bindings=True, input_vo_keyboard=True,
ytdl=True, ytdl_format=ytdlf)
self.scr = curses.initscr()
@ -90,7 +90,6 @@ class Comp(Omp):
previously on the display.
"""
if self.reading: return
curses.update_lines_cols()
y %= curses.LINES
x %= curses.COLS
length = X % curses.COLS - x + (y != curses.LINES - 1)
@ -104,8 +103,8 @@ class Comp(Omp):
if self.mp.osd.duration is not None:
self.played[self.playing]['duration'] = self.mp.osd.duration
add_status_str(':', X=5, lpad=3)
if self.mp.video: add_status_str('V', x=1, X=2)
if self.mp.audio: add_status_str('A', X=1)
if self.vid != 'no': add_status_str('V', x=1, X=2)
if not self.mp.mute: add_status_str('A', X=1)
add_status_str(self.mp.osd.time_pos or '00:00:00', x=4, X=12)
add_status_str('/', x=13, X=14)
add_status_str(self.mp.osd.duration or '00:00:00', x=15, X=23)
@ -116,7 +115,6 @@ class Comp(Omp):
self.scr.refresh()
def print_msg(self, message, error=False):
"""Print the given message, in red is it's an error."""
attributes = curses.color_pair(1) if error else curses.A_NORMAL
self.adds(message, curses.LINES-1, attr=attributes, lpad=0)
self.scr.refresh()
@ -124,13 +122,15 @@ class Comp(Omp):
def setno(self, *keys):
"""Set all keys of each entry in entries to False."""
for entry in self.entries:
for key in keys: entry[key] = False
for key in keys:
entry[key] = False
def play(self, force=False):
"""Play the next track."""
def mpv_play(entry, force):
self.setno('playing')
entry['playing'] = True
self.mp.vid = self.vid
try:
self.mp.play(entry['filename'])
except:
@ -198,7 +198,7 @@ class Comp(Omp):
def property_handler(self, name, val): self.update_status()
def __init__(self, entries, json_file, mode, mpv_args, ytdlf):
def __init__(self, entries, json_file, mode, mpv_vid, mpv_vo, ytdlf):
curses.noecho()
curses.cbreak()
self.scr.keypad(True)
@ -208,11 +208,24 @@ class Comp(Omp):
for i in range(1, 8): curses.init_pair(i, i, -1)
curses.init_pair(8, -1, 7)
for i in range(1, 7): curses.init_pair(i + 8, -1, i)
Omp.__init__(self, entries, json_file, mode, mpv_args, ytdlf)
Omp.__init__(self, entries, json_file, mode, mpv_vid, mpv_vo, ytdlf)
self.refresh()
def __enter__(self): return self
def idx(self, entry=None):
"""Return the index of the current entry."""
if entry is None:
return self.start + self.y - 1
return self.entries.index(entry)
def current(self):
"""Return the current entry."""
try:
return self.entries[self.idx()]
except:
return {}
def read_input(self, prompt):
"""Print the prompt string at the bottom of the screen then read
from standard input.
@ -254,21 +267,24 @@ class Comp(Omp):
def search(self, backward=False):
"""Prompt then search for a pattern."""
s = self.read_input(_("Search {}ward [{{}}]: ".format(
'back' if backward else 'for')).format(self.search_str))
if s: self.search_str = s
pattern = re.compile(self.search_str, re.IGNORECASE)
p = re.compile(self.read_input('/'), re.IGNORECASE)
entries = deque(self.entries)
if backward:
entries.rotate(-self.idx())
entries.reverse()
entries.rotate(-self.idx())
self.search_res = deque(filter(
lambda entry: p.search(entry['title']) is not None, entries))
if backward: self.search_res.reverse()
if self.search_res:
self.move(self.idx(self.search_res[0]) - self.idx())
else:
entries.rotate(-self.idx() - 1)
for entry in entries:
if pattern.search(entry['title']) is not None:
self.move(self.idx(entry) - self.idx())
return
self.print_msg(_("'{}' not found").format(self.search_str), error=True)
self.print_msg(_("Pattern not found"), error=True)
def next_search(self, backward=False):
"""Repeat previous search."""
if self.search_res:
self.search_res.rotate(1 if backward else -1)
self.move(self.idx(self.search_res[0]) - self.idx())
else:
self.print_msg(_("Pattern not found"), error=True)
def resize(self):
curses.update_lines_cols()
@ -299,19 +315,20 @@ class Comp(Omp):
curses.echo()
curses.endwin()
Omp.__exit__(self, exc_type, exc_value, traceback)
if exc_value is not None:
print_exception(exc_type, exc_value, traceback)
parser = ArgumentParser(description='Curses Omni Media Player')
parser.add_argument('-v', '--version', action='version',
version='%(prog)s {}'.format(__version__))
version='%(prog)s 0.3.11')
parser.add_argument('-e', '--extractor', default='youtube-dl',
choices=('json', 'mpv', 'youtube-dl'), required=False,
help='playlist extractor, default is youtube-dl')
parser.add_argument('playlist', help='path or URL to the playlist')
parser.add_argument('-c', '--config', default=USER_CONFIG, required=False,
help='path to the configuration file')
parser.add_argument('--vid', required=False,
help='initial video channel. auto selects the default, no\
disables video')
parser.add_argument('--vo', required=False, metavar='DRIVER',
help='specify the video output backend to be used. See\
VIDEO OUTPUT DRIVERS in mpv(1) for details and\
@ -326,54 +343,22 @@ if entries is None:
json_file = args.playlist if args.extractor == 'json' else ''
config = ConfigParser()
config.read(args.config)
vid = args.vid or config.get('mpv', 'video', fallback='auto')
vo = args.vo or config.get('mpv', 'video-output', fallback=None)
mode = config.get('comp', 'play-mode', fallback='play-current')
mpv_args = dict(config['mpv']) if 'mpv' in config else {}
if args.vo is not None: mpv_args['vo'] = args.vo
ytdlf = args.format or config.get('youtube-dl', 'format',
fallback='bestvideo+bestaudio')
ytdlf = args.format or config.get('youtube-dl', 'format', fallback='best')
with Comp(entries, json_file, mode, mpv_args, ytdlf) as comp:
while True:
c = comp.scr.get_wch()
comp.print_msg('')
# mpv keybindings
if c == curses.KEY_LEFT:
comp.seek(-5, precision='exact')
elif c == curses.KEY_RIGHT:
comp.seek(5, precision='exact')
elif c == curses.KEY_SLEFT: # Shifted Left-arrow
comp.seek(-1, precision='exact')
elif c == curses.KEY_SRIGHT: # Shifted Right-arrow
comp.seek(1, precision='exact')
elif c == curses.KEY_UP:
comp.seek(-60, precision='exact')
elif c == curses.KEY_DOWN:
comp.seek(60, precision='exact')
elif c == curses.KEY_PPAGE:
comp.add('chapter', 1)
elif c == curses.KEY_NPAGE:
comp.add('chapter', -1)
elif c == '[':
comp.multiply('speed', 0.9091)
elif c == ']':
comp.multiply('speed', 1.1)
elif c == '{':
comp.multiply('speed', 0.5)
elif c == '}':
comp.multiply('speed', 2.0)
elif c == curses.KEY_BACKSPACE:
comp.mp.speed = 1.0
elif c == 'q':
comp.print_msg(_("Save playlist? [Y/n]"))
if comp.scr.get_wch() not in _("Nn"): comp.dump_json()
break
elif c in ('p', ' '):
comp.cycle('pause')
elif c == '.':
comp.mp.frame_step()
elif c == ',':
comp.mp.frame_back_step()
elif c == '<':
with Comp(entries, json_file, mode, vid, vo, ytdlf) as comp:
c = comp.scr.getch()
while c != 113: # letter q
if c == 10: # curses.KEY_ENTER doesn't work
comp.update_playlist()
comp.next(force=True)
elif c in (32, 112): # space or letter p
comp.mp.pause ^= True
elif c == 47: # /
comp.search()
elif c == 60: # <
try:
if comp.mp.time_pos < 1:
comp.next(backward=True)
@ -381,123 +366,34 @@ with Comp(entries, json_file, mode, mpv_args, ytdlf) as comp:
comp.seek(0, 'absolute')
except:
pass
elif c == '>':
elif c == 62: # >
comp.next()
elif c == '\n': # curses.KEY_ENTER doesn't work
comp.update_playlist()
comp.next(force=True)
elif c == 'O':
comp.mp.command('cycle-values', 'osd-level', 3, 1)
elif c in ('o', 'P'):
comp.mp.show_progress()
elif c == 'z':
comp.add('sub-delay', -0.1)
elif c == 'x':
comp.add('sub-delay', 0.1)
elif c == ctrl('+'):
comp.add('audio-delay', 0.1)
elif c == ctrl('-'):
comp.add('audio-delay', -0.1)
elif c in ('/', '9'):
comp.add('volume', -2)
elif c in ('*', '0'):
comp.add('volume', 2)
elif c == 'm':
comp.cycle('mute')
elif c == '1':
comp.add('contrast', -1)
elif c == '2':
comp.add('contrast', 1)
elif c == '3':
comp.add('brightness', -1)
elif c == '4':
comp.add('brightness', 1)
elif c == '5':
comp.add('gamma', -1)
elif c == '6':
comp.add('gamma', 1)
elif c == '7':
comp.add('saturation', -1)
elif c == '8':
comp.add('saturation', 1)
elif c == alt('0'):
comp.mp.window_scale = 0.5
elif c == alt('1'):
comp.mp.window_scale = 1.0
elif c == alt('2'):
comp.mp.window_scale = 2.0
elif c == 'd':
comp.cycle('deinterlace')
elif c == 'r':
comp.add('sub-pos', -1)
elif c == 't':
comp.add('sub-pos', 1)
elif c == 'v':
comp.cycle('sub-visibility')
elif c == 'V':
comp.cycle('sub-ass-vsfilter-aspect-compat')
elif c == 'u':
comp.mp.command('cycle-values', 'sub-ass-override', 'force', 'no')
elif c == 'j':
comp.cycle('sub', 'up')
elif c == 'J':
comp.cycle('sub', 'down')
elif c == '#':
comp.cycle('audio')
elif c == '_':
comp.cycle('video')
elif c == 'T':
comp.cycle('ontop')
elif c == 'f':
comp.cycle('fullscreen')
elif c == 's':
comp.mp.screenshot()
elif c == 'S':
comp.mp.screenshot(includes='')
elif c == alt('s'):
comp.mp.screenshot(mode='each-frame')
elif c == 'w':
comp.add('panscan', -0.1)
elif c == 'e':
comp.add('panscan', 0.1)
elif c == 'A':
comp.mp.command('cycle-values', 'video-aspect',
'16:9', '4:3', '2.35:1', '-1')
elif c == 'E':
comp.cycle('edition')
elif c == 'l':
comp.mp.command('ab-loop')
elif c == 'L':
comp.mp.command('cycle-values', 'loop-file', 'inf', 'no')
# Emacs keybindings
elif c == ctrl('p'):
comp.move(-1)
elif c == ctrl('n'):
comp.move(1)
elif c == alt('v'):
comp.move(4 - curses.LINES)
elif c == ctrl('v'):
comp.move(curses.LINES - 4)
elif c in (ctrl('<'), curses.KEY_HOME):
comp.move(-len(comp.entries))
elif c in (ctrl('>'), curses.KEY_END):
comp.move(len(comp.entries))
elif c == ctrl(' '):
elif c == 63: # ?
comp.search(backward=True)
elif c == 65: # letter A
comp.mp.mute ^= True # hack to toggle bool value
elif c == 68: # letter D
comp.entries.pop(comp.idx())
if 1 < len(comp.entries) - curses.LINES + 4 == comp.start:
comp.start -= 1
elif comp.idx() == len(comp.entries):
comp.y -= 1
comp.refresh()
elif c == 77: # letter M
comp.mode = MODES[(MODES.index(comp.mode) - 1) % 8]
comp.update_status()
elif c == 78: # letter N
comp.next_search(backward=True)
elif c == 86: # letter V
comp.vid = 'auto' if comp.vid == 'no' else 'no'
comp.mp.vid = comp.vid
comp.update_status()
elif c == 87: # letter W
comp.dump_json()
elif c == 100: # letter d
comp.current()['selected'] = not comp.current().get('selected')
comp.move(1)
elif c == ctrl('o'):
extractor = comp.read_input(_("Playlist extractor: "))
filename = comp.read_input(_("Open: "))
entries = extract_info(filename, extractor)
if entries is None:
comp.print_msg(
_("'{}': Can't extract playlist").format(filename))
else:
comp.entries, comp.start, comp.y = entries, 0, 1
comp.refresh()
elif c == ctrl('i'):
elif c == 105: # letter i
extractor = comp.read_input(_("Playlist extractor: "))
filename = comp.read_input(_("Insert: "))
entries = extract_info(filename, extractor)
@ -510,26 +406,37 @@ with Comp(entries, json_file, mode, mpv_args, ytdlf) as comp:
comp.entries.extend(entries)
comp.entries.extend(bottom)
comp.refresh()
elif c == ctrl('f'):
comp.search()
elif c == alt('f'):
comp.search(backward=True)
elif c == alt('m'):
elif c == 109: # letter m
comp.mode = MODES[(MODES.index(comp.mode) + 1) % 8]
comp.update_status()
elif c == curses.KEY_DC:
comp.entries.pop(comp.idx())
if 1 < len(comp.entries) - curses.LINES + 4 == comp.start:
comp.start -= 1
elif comp.idx() == len(comp.entries):
comp.y -= 1
comp.refresh()
elif c == 'W':
comp.dump_json()
elif c == 110: # letter n
comp.next_search()
elif c == 111: # letter o
extractor = comp.read_input(_("Playlist extractor: "))
filename = comp.read_input(_("Open: "))
entries = extract_info(filename, extractor)
if entries is None:
comp.print_msg(
_("'{}': Can't extract playlist").format(filename))
else:
comp.entries, comp.start, comp.y = entries, 0, 1
comp.refresh()
elif c in (curses.KEY_UP, 107): # up arrow or letter k
comp.move(-1)
elif c in (curses.KEY_DOWN, 106): # down arrow or letter j
comp.move(1)
elif c in (curses.KEY_LEFT, 104): # left arrow or letter h
comp.seek(-5, precision='exact')
elif c in (curses.KEY_RIGHT, 108): # right arrow or letter l
comp.seek(5, precision='exact')
elif c == curses.KEY_HOME: # home
comp.move(-len(comp.entries))
elif c == curses.KEY_END: # end
comp.move(len(comp.entries))
elif c == curses.KEY_NPAGE: # page down
comp.move(curses.LINES - 4)
elif c == curses.KEY_PPAGE: # page up
comp.move(4 - curses.LINES)
elif c in (curses.KEY_F5, curses.KEY_RESIZE):
comp.resize()
elif c == ':':
try:
comp.mp.command(*comp.read_input(':').split())
except:
comp.print_msg(_("Failed to execute command"), error=True)
c = comp.scr.getch()

View File

@ -1,7 +1,7 @@
.\" Process this file with
.\" groff -man -Tutf8 comp.1
.\"
.TH COMP 1 2018-01-25 comp
.TH COMP 1 2017-06-17 comp
.SH NAME
comp \- Curses Omni Media Player
.SH SYNOPSIS
@ -11,10 +11,8 @@ comp \- Curses Omni Media Player
.SH DESCRIPTION
\fBcomp\fR is a
.BR mpv (1)
front-end using
.BR curses (3).
It has basic media player functions and can to extract playlists from multiple
sources such as media sites supported by
front-end using curses. It has basic media player functions and can to extract
playlists from multiple sources such as media sites supported by
.BR youtube-dl (1),
local and direct URL to video/audio and its own JSON playlist format.
.SH OPTIONS
@ -49,192 +47,75 @@ for details and descriptions of available drivers
.B -f \fIYTDL_FORMAT\fR, \fB--format \fIYTDL_FORMAT
video format/quality to be passed to youtube-dl
.SH KEYBOARD CONTROL
.SS Bindings inherited from mpv
For convenience purpose, I try to mimic
.BR mpv (1)
default keybindings, but many are slightly different from
.BR mpv (1)
exact behaviour (mainly because of the lack of keys which are unsupported by
.BR curses (3)).
So I will list all of them here for you to compare:
.TP
.B Left and Right
Seek backward/forward 5 seconds. Shifted arrow does a 1 second seek.
.TP
.B Up and Down
Seek backward/forward 1 minute.
.TP
.B [ and ]
Decrease/increase current playback speed by 10%.
.TP
.B { and }
Halve/double current playback speed.
.TP
.B Backspace
Reset playback speed to normal.
.TP
.B < and >
Go backward/forward in the playlist.
.TP
.B Return
Start playing.
.TP
.B Space / p
Pause (pressing again unpauses).
.B Space, p
Toggle pause.
.TP
.B .
Step forward. Pressing once will pause, every consecutive press will play
one frame and then go into pause mode again.
.TP
.B ,
Step backward. Pressing once will pause, every consecutive press will play
one frame in reverse and then go into pause mode again.
.TP
.B q
Stop playing and quit.
.TP
.B / and *
Decrease/increase volume.
.TP
.B 9 and 0
Decrease/increase volume.
.TP
.B m
Mute sound.
.TP
.B _
Cycle through the available video tracks.
.TP
.B #
Cycle through the available audio tracks.
.TP
.B f
Toggle fullscreen.
.TP
.B T
Toggle stay-on-top.
.TP
.B w and e
Decrease/increase pan-and-scan range.
.TP
.B o or P
Show progression bar, elapsed time and total duration on the OSD.
.TP
.B O
Toggle OSD states between normal and playback time/duration.
.TP
.B v
Toggle subtitle visibility.
.TP
.B j and J
Cycle through the available subtitles.
.TP
.B x and z
Adjust subtitle delay by +/- 0.1 seconds.
.TP
.B l
Set/clear A-B loop points.
.TP
.B L
Toggle infinite looping.
.TP
.B Ctrl-+ and Ctrl--
Adjust audio delay (A/V sync) by +/- 0.1 seconds.
.TP
.B u
Switch between applying no style overrides to SSA/ASS subtitles, and
overriding them almost completely with the normal subtitle style.
.TP
.B V
Toggle subtitle VSFilter aspect compatibility mode.
.TP
.B r and t
Move subtitles up/down.
.TP
.B s
Take a screenshot.
.TP
.B S
Take a screenshot, without subtitles.
.TP
.B Alt-s
Take a screenshot each frame.
.TP
.B Page Up and Page Down
Seek to the beginning of the previous/next chapter.
.TP
.B d
Activate/deactivate deinterlacer.
.TP
.B A
Cycle aspect ratio override.
.TP
.B 1 and 2
Adjust contrast.
.TP
.B 3 and 4
Adjust brightness.
.TP
.B 5 and 6
Adjust gamma.
.TP
.B 7 and 8
Adjust saturation.
.TP
.B Alt-0
Resize video window to half its original size.
.TP
.B Alt-1
Resize video window to its original size.
.TP
.B Alt-2
Resize video window to double its original size.
.TP
.B E
Cycle through editions.
.SS Movements and selections
The following keybindings are Emacs-like since most characters are taken by
.BR mpv (1).
.TP
.B Ctrl-p and Ctrl-n
Move a single line up/down.
.TP
.B Alt-v and Ctrl-v
Move a single page up/down.
.TP
.B Ctrl-< and Ctrl->
Move to the beginning/end of the playlist.
.TP
.B Home and End
Move to the beginning/end of the playlist.
.TP
.B Ctrl-Space
Deselect/reselect the current entry and move down a line.
.SS Playlist manipulation
.TP
.B Ctrl-o
Open playlist.
.TP
.B Ctrl-i
Insert playlist.
.TP
.B Ctrl-f and Alt-f
.B /, ?
Search forward/backward for a pattern.
.TP
.B Alt-m
Cycle through playing modes.
.B <, >
Go backward/forward in the playlist.
.TP
.B Delete
.B A
Toggle mute.
.TP
.B D
Delete the current entry.
.TP
.B N
Repeat previous search in reverse direction.
.TP
.B V
Toggle video.
.TP
.B W
Save the current playlist under JSON format.
.TP
.B d
Deselect/reselect the current entry.
.TP
.B i
Insert playlist.
.TP
.B m, M
Cycle forward/backward through playing modes.
.TP
.B n
Repeat previous search.
.TP
.B o
Open playlist.
.TP
.B Up, k
Move a single line up.
.TP
.B Down, j
Move a single line down.
.TP
.B Left, h
Seek backward 5 seconds.
.TP
.B Right, l
Seek forward 5 seconds.
.TP
.B Home
Move to the beginning of the playlist.
.TP
.B End
Move to the end of the playlist.
.TP
.B Page Up
Move a single page up.
.TP
.B Page Down
Move a single page down.
.TP
.B F5
Redraw the screen content.
.TP
.B :
Execute a mpv command.
.SH FILES
.TP
.I ~/.config/comp/settings.ini

Binary file not shown.

View File

@ -1,6 +1,6 @@
# Vietnamese translation for Omp front-ends.
# Copyright (C) 2018 Nguyễn Gia Phong
# Nguyễn Gia Phong <vn.mcsinyx@gmail.com>, 2018
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
@ -50,12 +50,6 @@ msgstr "Tiêu đề"
msgid "Current size: {}x{}. Minimum size: {}x4."
msgstr "Kích thước hiện tại: {}x{}. Kích thước tối thiểu: {}x4."
msgid "Save playlist? [Y/n]"
msgstr "Lưu danh sách phát? [C/k]"
msgid "Nn"
msgstr "Kk"
msgid "Save playlist to [{}]: "
msgstr "Lưu danh sách phát tại [{}]: "

View File

@ -27,7 +27,6 @@ from mpv import MPV
DEFAULT_ENTRY = {'filename': '', 'title': '', 'duration': '00:00:00',
'error': False, 'playing': False, 'selected': False}
JSON_KEYS = 'filename', 'title', 'duration', 'error', 'selected'
class YoutubeDLLogger:

View File

@ -16,37 +16,27 @@
#
# Copyright (C) 2017 Nguyễn Gia Phong <vn.mcsinyx@gmail.com>
import curses
import json
import re
from bisect import bisect_left as bisect
from collections import deque
from gettext import bindtextdomain, gettext as _, textdomain
from itertools import cycle
from os import makedirs
from os.path import abspath, dirname, expanduser, expandvars
from os.path import abspath, dirname, expanduser, expandvars, isfile
from random import choice
from sys import exc_info
from time import gmtime, sleep, strftime
from urllib import request
from youtube_dl import YoutubeDL
from pkg_resources import resource_filename
from mpv import MPV
from mpv import MPV, MpvFormat
from .ie import JSON_KEYS
from .ie import extract_info
# Init gettext
bindtextdomain('omp', resource_filename('omp', 'locale'))
textdomain('omp')
def shuffle_init(a):
"""Return in iterator which yield random elements from a,
and always begin with its first element.
"""
if a:
yield a[0]
while True: yield choice(a)
class Omp(object):
"""Omni Media Player meta object.
@ -61,6 +51,7 @@ class Omp(object):
playing (int): index of playing track in played
playlist (iterator): iterator of tracks according to mode
search_res (iterator): title-searched results
vid (str): flag show if video output is enabled
I/O handlers (defined by front-end):
print_msg(message, error=False): print a message
@ -68,23 +59,19 @@ class Omp(object):
read_input(prompt): prompt for user input
refresh(): update interface content
"""
def __new__(cls, entries, json_file, mode, mpv_args, ytdlf):
self = object.__new__(cls)
def __new__(cls, entries, json_file, mode, mpv_vid, mpv_vo, ytdlf):
self = super(Comp, cls).__new__(cls)
self.play_backward, self.reading = False, False
self.playing = -1
self.json_file, self.mode = json_file, mode
self.json_file, self.mode, self.vid = json_file, mode, mpv_vid
self.entries, self.played = entries, []
self.playlist, self.search_res = iter(()), deque()
self.mp = MPV(input_default_bindings=True, input_vo_keyboard=True,
ytdl=True, ytdl_format=ytdlf)
return self
def __init__(self, entries, json_file, mode, mpv_args, ytdlf):
for arg, val in mpv_args.items():
try:
self.mp[arg] = val
except:
self.__exit__(*exc_info())
def __init__(self, entries, json_file, mode, mpv_vid, mpv_vo, ytdlf):
if mpv_vo is not None: self.mp['vo'] = mpv_vo
@self.mp.property_observer('mute')
@self.mp.property_observer('pause')
@self.mp.property_observer('time-pos')
@ -93,88 +80,43 @@ class Omp(object):
def __enter__(self): return self
def seek(self, amount, reference='relative', precision='default-precise'):
"""Wrap a try clause around mp.seek to avoid crashing when
nothing is being played.
"""
try:
self.mp.seek(amount, reference, precision)
except:
self.print_msg(_("Failed to seek"), error=True)
def add(self, name, value=1):
"""Wrap a try clause around mp.property_add."""
try:
self.mp.property_add(name, value)
except:
self.print_msg(
_("Failed to add {} to '{}'").format(value, name), error=True)
def multiply(self, name, factor):
"""Wrap a try clause around mp.property_multiply."""
try:
self.mp.property_multiply(name, factor)
except:
self.print_msg(
_("Failed to multiply '{}' with {}").format(name, factor),
error=True)
def cycle(self, name, direction='up'):
"""Wrap a try clause around mp.cycle."""
try:
self.mp.cycle(name, direction='up')
except:
self.print_msg(
_("Failed to cycle {} '{}'").format(direction, name),
error=True)
def idx(self, entry=None):
"""Return the index of the current entry."""
if entry is None:
return self.start + self.y - 1
return self.entries.index(entry)
def current(self):
"""Return the current entry."""
try:
return self.entries[self.idx()]
except:
return {}
def update_play_list(self, pick):
"""Update the list of entries to be played."""
if pick == 'current':
self.play_list = [self.current()]
elif pick == 'all':
self.play_list = deque(self.entries)
self.play_list.rotate(-self.idx())
else:
self.play_list = [i for i in self.entries if i.get('selected')]
def update_playlist(self):
"""Update the playlist to be used by play function."""
action, pick = self.mode.split('-')
if pick == 'current':
self.play_list = deque([self.current()])
elif pick == 'all':
self.play_list = deque(self.entries)
self.play_list.rotate(-self.idx())
elif pick == 'selected':
self.play_list = deque([entry for entry in self.entries
if entry.get('selected')])
indexes = [i for i, entry in enumerate(self.entries)
if entry.get('selected')]
idx = indexes[bisect(indexes, self.idx())]
self.play_list.rotate(-self.play_list.index(self.entries[idx]))
self.update_play_list(pick)
if action == 'play':
self.playlist = iter(self.play_list)
elif action == 'repeat':
self.playlist = cycle(self.play_list)
elif action == 'shuffle':
self.playlist = shuffle_init(self.play_list)
else:
self.playlist = iter(lambda: choice(self.play_list), None)
if self.playing < -1: self.played = self.played[:self.playing+1]
def next(self, force=False, backward=False):
"""Go forward/backward in the playlist.
If forced, this will also unpause the player.
def seek(self, amount, reference='relative', precision='default-precise'):
"""Wrap mp.seek with a try clause to avoid crash when nothing is
being played.
"""
try:
self.mp.seek(amount, reference, precision)
except:
pass
def next(self, force=False, backward=False):
self.play_backward = backward
if self.mp.idle_active:
self.play(force)
else:
self.mp.time_pos = self.mp.duration
self.seek(100, 'absolute-percent')
if force: self.mp.pause = False
def search(self, backward=False):
@ -199,22 +141,19 @@ class Omp(object):
self.update_status(_("Pattern not found"), curses.color_pair(1))
def dump_json(self):
"""Read user input needed to save the playlist."""
s = self.read_input(
_("Save playlist to [{}]: ").format(self.json_file))
self.json_file = abspath(expanduser(expandvars(s or self.json_file)))
entries = [{k: v for k, v in entry.items() if k in JSON_KEYS}
for entry in self.entries]
try:
makedirs(dirname(self.json_file), exist_ok=True)
with open(self.json_file, 'w') as f:
json.dump(entries, f, ensure_ascii=False, indent=2,
sort_keys=True)
except:
errmsg = _("'{}': Can't open file for writing").format(
self.json_file)
self.print_msg(errmsg, error=True)
else:
with open(self.json_file, 'w') as f:
json.dump(self.entries, f, ensure_ascii=False,
indent=2, sort_keys=True)
self.print_msg(_("'{}' written").format(self.json_file))
def __exit__(self, exc_type, exc_value, traceback):

View File

@ -5,17 +5,13 @@
play-mode = play-current
[mpv]
# Options to be parsed to mpv. See OPTIONS section on mpv(1) man pages for
# its complete list of available options.
# For example:
#vo = xv
#ontop = yes
#border = no
#force-window = yes
#autofit = 500x280
#geometry = -15-50
# Initial video channel. auto selects the default, no disables video.
video = auto
# Specify the video output backend to be used. See VIDEO OUTPUT DRIVERS in
# mpv(1) man page for details and descriptions of available drivers.
video-output =
[youtube-dl]
# Video format/quality to be passed to youtube-dl. See FORMAT SELECTION in
# youtube-dl(1) man page for more details and descriptions.
format = bestvideo+bestaudio
format = best

View File

@ -7,7 +7,7 @@ with open('README.rst') as f:
setup(
name='comp',
version='0.4.6',
version='0.3.12',
description=('Curses Omni Media Player'),
long_description=long_description,
url='https://github.com/McSinyx/comp',

View File

@ -3,6 +3,7 @@
"duration": "00:05:21",
"error": false,
"filename": "https://youtu.be/weeI1G46q0o",
"playing": false,
"selected": true,
"title": "DJ Khaled - I'm the One ft. Justin Bieber, Quavo, Chance the Rapper, Lil Wayne"
},
@ -10,55 +11,127 @@
"duration": "00:04:23",
"error": false,
"filename": "https://youtu.be/JGwWNGJdvx8",
"playing": false,
"selected": true,
"title": "Ed Sheeran - Shape of You [Official Video]"
},
{
"duration": "00:03:30",
"error": false,
"filename": "https://youtu.be/PMivT7MJ41M",
"playing": false,
"selected": true,
"title": "Bruno Mars - Thats What I Like [Official Video]"
},
{
"duration": "00:04:46",
"error": false,
"filename": "https://youtu.be/CTFtOOh47oo",
"playing": false,
"selected": false,
"title": "French Montana - Unforgettable ft. Swae Lee"
},
{
"duration": "00:04:45",
"error": false,
"filename": "https://youtu.be/NLZRYQMLDW4",
"playing": false,
"selected": false,
"title": "Kendrick Lamar - DNA."
},
{
"duration": "00:04:07",
"error": false,
"filename": "https://youtu.be/FM7MFYoylVs",
"playing": false,
"selected": true,
"title": "The Chainsmokers & Coldplay - Something Just Like This (Lyric)"
},
{
"duration": "00:03:48",
"error": false,
"filename": "https://youtu.be/72UO0v5ESUo",
"playing": false,
"selected": true,
"title": "Luis Fonsi, Daddy Yankee - Despacito (Audio) ft. Justin Bieber"
},
{
"duration": "00:03:41",
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/D5drYkLiLI8",
"playing": false,
"selected": false,
"title": "Kygo, Selena Gomez - It Ain't Me (with Selena Gomez) (Audio)"
},
{
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/Zgmvg-zzctI",
"playing": false,
"selected": false,
"title": "Lil Uzi Vert - XO TOUR Llif3 (Produced By TM88)"
},
{
"duration": "00:12:53",
"error": false,
"filename": "test/gplv3.ogg",
"playing": false,
"selected": true,
"title": "gplv3.ogg"
},
{
"duration": "00:04:17",
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/xvZqHgFz51I",
"playing": false,
"selected": false,
"title": "Future - Mask Off"
},
{
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/8j9zMok6two",
"playing": false,
"selected": false,
"title": "Miley Cyrus - Malibu (Official Video)"
},
{
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/dPI-mRFEIH0",
"playing": false,
"selected": false,
"title": "Katy Perry - Bon Appétit (Official) ft. Migos"
},
{
"duration": "00:04:06",
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/aatr_2MstrI",
"playing": false,
"selected": false,
"title": "Clean Bandit - Symphony feat. Zara Larsson [Official Video]"
},
{
"duration": "00:04:16",
"duration": "00:34:38",
"error": false,
"filename": "https://www.tube8.com/teen/nicole-ray-and-james-deen/409802/",
"playing": false,
"selected": true,
"title": "Nicole Ray and James Deen"
},
{
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/7F37r50VUTQ",
"playing": false,
"selected": true,
"title": "ZAYN, Taylor Swift - I Dont Wanna Live Forever (Fifty Shades Darker)"
},
{
"duration": "00:04:57",
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/qFLhGq0060w",
"playing": false,
"selected": true,
"title": "The Weeknd - I Feel It Coming ft. Daft Punk"
},
@ -66,41 +139,55 @@
"duration": "00:02:29",
"error": false,
"filename": "http://www.html5videoplayer.net/videos/toystory.mp4",
"playing": false,
"selected": false,
"title": "toystory.mp4"
},
{
"duration": "00:05:13",
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/6ImFf__U6io",
"playing": false,
"selected": true,
"title": "Birdman - Dark Shades (Explicit) ft. Lil Wayne, Mack Maine"
},
{
"duration": "00:03:55",
"duration": "00:03:56",
"error": false,
"filename": "https://www.youtube.com/watch?v=3M3xfu0m5o4",
"playing": false,
"selected": false,
"title": "David Banner - Play (Dirty version)"
},
{
"duration": "00:04:14",
"error": false,
"filename": "https://www.youtube.com/watch?v=NmCFY1oYDeM",
"selected": false,
"title": "John Legend - Love Me Now (Video)"
},
{
"duration": "00:03:55",
"error": false,
"filename": "https://youtu.be/NGLxoKOvzu4",
"playing": false,
"selected": false,
"title": "Jason Derulo - Swalla (feat. Nicki Minaj & Ty Dolla $ign) (Official Music Video)"
},
{
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/Hm1YFszJWbQ",
"playing": false,
"selected": false,
"title": "Migos - Slippery feat. Gucci Mane [Official Video]"
},
{
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/SC4xMk98Pdc",
"playing": false,
"selected": false,
"title": "Post Malone - Congratulations ft. Quavo"
},
{
"duration": "00:03:51",
"error": false,
"filename": "https://youtu.be/nfs8NYg7yQM",
"playing": false,
"selected": false,
"title": "Charlie Puth - Attention [Official Video]"
},
@ -108,27 +195,39 @@
"duration": "00:04:10",
"error": false,
"filename": "https://www.youtube.com/watch?v=sRIkXM8S1J8",
"playing": false,
"selected": true,
"title": "Best Goat Song Versions Compilation Ever! (HD)"
},
{
"duration": "00:04:03",
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/Dst9gZkq1a8",
"playing": false,
"selected": false,
"title": "Travis Scott - goosebumps ft. Kendrick Lamar"
},
{
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/dMK_npDG12Q",
"playing": false,
"selected": false,
"title": "Lorde - Green Light"
},
{
"duration": "00:03:32",
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/h--P8HzYZ74",
"playing": false,
"selected": true,
"title": "Zedd, Alessia Cara - Stay (Lyric Video)"
},
{
"duration": "00:02:45",
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/Mdh2p03cRfw",
"playing": false,
"selected": false,
"title": "Sam Hunt - Body Like A Back Road (Audio)"
},
@ -136,6 +235,7 @@
"duration": "00:03:40",
"error": false,
"filename": "https://youtu.be/Fq0xEpRDL9Q",
"playing": false,
"selected": false,
"title": "Chris Brown - Privacy (Explicit Version)"
},
@ -143,55 +243,87 @@
"duration": "00:03:36",
"error": false,
"filename": "https://youtu.be/7wtfhZwyrcc",
"playing": true,
"selected": false,
"title": "Imagine Dragons - Believer"
},
{
"duration": "00:03:52",
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/A-Rn0iQEpc8",
"filename": "https://youtu.be/t_jHrUE5IOk",
"playing": false,
"selected": false,
"title": "Can't Stop the SUSE - (Can't Stop the Feeling parody)"
},
{
"duration": "00:04:28",
"error": false,
"filename": "https://youtu.be/SYRlTISvjww",
"selected": false,
"title": "Uptime Funk - (Uptown Funk parody)"
"title": "Maluma - Felices los 4 (Official Video)"
},
{
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/VNkDJk5_9eU",
"filename": "https://youtu.be/wzZWXrlDj-A",
"playing": false,
"selected": false,
"title": "What Does the Chameleon Say? (Ylvis - What Does the Fox Say parody)"
"title": "DNCE - Kissing Strangers ft. Nicki Minaj"
},
{
"duration": "00:03:46",
"duration": "00:03:18",
"error": false,
"filename": "https://youtu.be/M9bq_alk-sw",
"filename": "https://youtu.be/AEB6ibtdPZc",
"playing": false,
"selected": false,
"title": "SUSE. Yes Please. (Maroon 5 - Sugar parody)"
"title": "Paramore: Hard Times [OFFICIAL VIDEO]"
},
{
"duration": "00:03:30",
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/oHNKTlz1lps",
"selected": true,
"title": "Linus Said - Music Parody (Momma Said)"
},
{
"duration": "00:03:58",
"error": false,
"filename": "https://youtu.be/4VrhlyIgo3M",
"filename": "https://youtu.be/vqW18C4plZ8",
"playing": false,
"selected": false,
"title": "25 Years - SUSE Music Video (7 Years parody)"
"title": "WizKid - Come Closer ft. Drake"
},
{
"duration": "00:03:52",
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/A7xzXDStQnk",
"playing": false,
"selected": false,
"title": "Shawn Mendes - There's Nothing Holdin' Me Back (Lyric Video)"
},
{
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/FG9M0aEpJGE",
"playing": false,
"selected": false,
"title": "G-Eazy & Kehlani - Good Life (from The Fate of the Furious: The Album) [MUSIC VIDEO]"
},
{
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/vp8VZe5kqEM",
"playing": false,
"selected": false,
"title": "Lady Gaga - The Cure (Audio)"
},
{
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/eP4eqhWc7sI",
"playing": false,
"selected": false,
"title": "Lana Del Rey - Lust For Life (Official Video) ft. The Weeknd"
},
{
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/5qJp6xlKEug",
"playing": false,
"selected": false,
"title": "Gorillaz - Saturnz Barz (Spirit House)"
},
{
"duration": "00:00:00",
"error": false,
"filename": "https://youtu.be/9sg-A-eS6Ig",
"playing": false,
"selected": true,
"title": "Enrique Iglesias - SUBEME LA RADIO (Official Video) ft. Descemer Bueno, Zion & Lennox"
}