From 0bfda782548132a4748e45172325f2078ad2f6b8 Mon Sep 17 00:00:00 2001 From: Fabio Ambauen <1833932+open-dynaMIX@users.noreply.github.com> Date: Sun, 6 Sep 2020 16:49:34 +0200 Subject: [PATCH] feat(api): add `loadfile` endpoint This commit implements a new POST API endpoint `loadfile`. This endpoint allows for adding new files to the current playlist. --- README.md | 74 ++++++++++++++++++++++++++------------------------ tests/tests.py | 48 ++++++++++++++++++++++++++++++-- webui.lua | 19 +++++++++++++ 3 files changed, 102 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 95d3f86a..599b9328 100644 --- a/README.md +++ b/README.md @@ -229,42 +229,44 @@ In order to have the notification work properly you need to at least once trigge ## Endpoints You can also directly talk to the endpoints: -| URI | Method | Parameter | Description | -| ---------------------------------- | ------ | ---------------------------------- | ----------------------------------------------------------------------- | -| /api/status | GET | | Returns JSON data about playing media --> see below | -| /api/play | POST | | Play media | -| /api/pause | POST | | Pause media | -| /api/toggle_pause | POST | | Toggle play/pause | -| /api/fullscreen | POST | | Toggle fullscreen | -| /api/quit | POST | | Quit the program | -| /api/add/:name/:value | POST | `string` and `int` or `float` | Add `:value` (default of `1`) to the `:name` property | -| /api/cycle/:name/:value | POST | `string` and `up` or `down` | Cycle `:name` by `:value` (default of `up`) | -| /api/multiply/:name/:value | POST | `string` and `int` or `float` | Multiply `:name` by `:value` | -| /api/set/:name/:value | POST | `string` and anything | Set `:name` to `:value` | -| /api/toggle/:name | POST | `string` | Toggle the boolean property | -| /api/seek/:seconds | POST | `int` or `float` (can be negative) | Seek | -| /api/set_position/:seconds | POST | | Go to position :seconds | -| /api/playlist_prev | POST | | Go to previous media in playlist | -| /api/playlist_next | POST | | Go to next media in playlist | -| /api/playlist_jump/:index | POST | `int` | Jump to playlist item at position `:index` | -| /api/playlist_move/:source/:target | POST | `int` and `int` | Move playlist item from position `:source` to position `:target` | -| /api/playlist_move_up/:index | POST | `int` | Move playlist item at position `:index` one position up | -| /api/playlist_remove/:index | POST | `int` | Remove playlist item at position `:index` | -| /api/playlist_shuffle | POST | | Shuffle the playlist | -| /api/loop_file/:mode | POST | `string` or `int` | Loop the current file. `:mode` accepts the same loop modes as mpv | -| /api/loop_playlist/:mode | POST | `string` or `int` | Loop the whole playlist `:mode` accepts the same loop modes as mpv | -| /api/add_chapter/:amount | POST | `int` (can be negative) | Jump `:amount` chapters in current media | -| /api/add_volume/:percent | POST | `int` or `float` (can be negative) | Add :percent% volume | -| /api/set_volume/:percent | POST | `int` or `float` | Set volume to :percent% | -| /api/add_sub_delay/:ms | POST | `int` or `float` (can be negative) | Add :seconds seconds subtitles delay | -| /api/set_sub_delay/:ms | POST | `int` or `float` (can be negative) | Set subtitles delay to :ms milliseconds | -| /api/add_audio_delay/:seconds | POST | `int` or `float` (can be negative) | Add :seconds seconds audio delay | -| /api/set_audio_delay/:seconds | POST | `int` or `float` (can be negative) | Set audio delay to :seconds milliseconds | -| /api/cycle_sub | POST | | Cycle trough available subtitles | -| /api/cycle_audio | POST | | Cycle trough available audio tracks | -| /api/cycle_audio_device | POST | | Cycle trough audio devices. [More information.](#audio-devices-string) | -| /api/speed_set/:speed | POST | `int` or `float` | Set playback speed to `:speed` (defaults to `1` for quick reset) | -| /api/speed_adjust/:amount | POST | `int` or `float` | Multiply playback speed by `:amount` (where `1.0` is no change) | +| URI | Method | Parameter | Description | +| ---------------------------------- | ------ | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | +| /api/status | GET | | Returns JSON data about playing media --> see below | +| /api/play | POST | | Play media | +| /api/pause | POST | | Pause media | +| /api/toggle_pause | POST | | Toggle play/pause | +| /api/fullscreen | POST | | Toggle fullscreen | +| /api/quit | POST | | Quit the program | +| /api/add/:name/:value | POST | `string` and `int` or `float` | Add `:value` (default of `1`) to the `:name` property | +| /api/cycle/:name/:value | POST | `string` and `up` or `down` | Cycle `:name` by `:value` (default of `up`) | +| /api/multiply/:name/:value | POST | `string` and `int` or `float` | Multiply `:name` by `:value` | +| /api/set/:name/:value | POST | `string` and anything | Set `:name` to `:value` | +| /api/toggle/:name | POST | `string` | Toggle the boolean property | +| /api/seek/:seconds | POST | `int` or `float` (can be negative) | Seek | +| /api/set_position/:seconds | POST | | Go to position :seconds | +| /api/playlist_prev | POST | | Go to previous media in playlist | +| /api/playlist_next | POST | | Go to next media in playlist | +| /api/playlist_jump/:index | POST | `int` | Jump to playlist item at position `:index` | +| /api/playlist_move/:source/:target | POST | `int` and `int` | Move playlist item from position `:source` to position `:target` | +| /api/playlist_move_up/:index | POST | `int` | Move playlist item at position `:index` one position up | +| /api/playlist_remove/:index | POST | `int` | Remove playlist item at position `:index` | +| /api/playlist_shuffle | POST | | Shuffle the playlist | +| /api/loop_file/:mode | POST | `string` or `int` | Loop the current file. `:mode` accepts the same loop modes as mpv | +| /api/loop_playlist/:mode | POST | `string` or `int` | Loop the whole playlist `:mode` accepts the same loop modes as mpv | +| /api/add_chapter/:amount | POST | `int` (can be negative) | Jump `:amount` chapters in current media | +| /api/add_volume/:percent | POST | `int` or `float` (can be negative) | Add :percent% volume | +| /api/set_volume/:percent | POST | `int` or `float` | Set volume to :percent% | +| /api/add_sub_delay/:ms | POST | `int` or `float` (can be negative) | Add :seconds seconds subtitles delay | +| /api/set_sub_delay/:ms | POST | `int` or `float` (can be negative) | Set subtitles delay to :ms milliseconds | +| /api/add_audio_delay/:seconds | POST | `int` or `float` (can be negative) | Add :seconds seconds audio delay | +| /api/set_audio_delay/:seconds | POST | `int` or `float` (can be negative) | Set audio delay to :seconds milliseconds | +| /api/cycle_sub | POST | | Cycle trough available subtitles | +| /api/cycle_audio | POST | | Cycle trough available audio tracks | +| /api/cycle_audio_device | POST | | Cycle trough audio devices. [More information.](#audio-devices-string) | +| /api/speed_set/:speed | POST | `int` or `float` | Set playback speed to `:speed` (defaults to `1` for quick reset) | +| /api/speed_adjust/:amount | POST | `int` or `float` | Multiply playback speed by `:amount` (where `1.0` is no change) | +| /api/loadfile /:url/:mode | POST | :url `string`
:mode `string` options: `replace` (default), `append`, `append-play` | Load file to playlist. Together with youtube-dl, this also works for URLs | + All POST endpoints return a JSON message. If successful: `{"message": "success"}`, otherwise, the message will contain information about the error. diff --git a/tests/tests.py b/tests/tests.py index 3f677429..bab98ec7 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +import urllib + import pytest import requests from requests.auth import HTTPBasicAuth @@ -36,10 +38,11 @@ def get_script_opts(options): return {"options": [f"--script-opts={','.join(option_strings)}"]} -def send(command, arg=None, expect=200, status=None): +def send(command, arg=None, arg2=None, expect=200, status=None): api = f"api/{command}" - if arg is not None: - api += f"/{arg}" + for a in [arg, arg2]: + if a is not None: + api += f"/{a}" resp = requests.post(get_uri(api)) assert resp.status_code == expect if status is not None: @@ -266,6 +269,45 @@ def shuffle(): assert get_order(status) == order[:2] +def test_loadfile(mpv_instance): + def send_loadfile(url, mode=None, expect=200): + return send( + "loadfile", + urllib.parse.quote(url, safe=""), + mode, + status="playlist", + expect=expect, + ) + + send("pause") + status = get_status() + assert status["playlist"][0]["current"] is True + assert len(status["playlist"]) == 3 + + playlist = send_loadfile("./environment/test_media/01 - dummy.mp3", "append-play") + + assert len(playlist) == 4 + assert playlist[-1]["filename"] == "./environment/test_media/01 - dummy.mp3" + + playlist = send_loadfile("./environment/test_media/01 - dummy.mp3", "append") + + assert len(playlist) == 5 + assert playlist[-2]["filename"] == "./environment/test_media/01 - dummy.mp3" + assert playlist[-1]["filename"] == "./environment/test_media/01 - dummy.mp3" + + playlist = send_loadfile("./environment/test_media/01 - dummy.mp3", "replace") + + assert len(playlist) == 1 + assert playlist[0]["filename"] == "./environment/test_media/01 - dummy.mp3" + + playlist = send_loadfile("./environment/test_media/01 - dummy.mp3") + + assert len(playlist) == 1 + assert playlist[0]["filename"] == "./environment/test_media/01 - dummy.mp3" + + send_loadfile("./environment/test_media/01 - dummy.mp3", "not a valid mode", 400) + + @pytest.mark.parametrize( "mpv_instance,status_code", [ diff --git a/webui.lua b/webui.lua index 54dd15c7..3ed88b84 100644 --- a/webui.lua +++ b/webui.lua @@ -386,6 +386,25 @@ local commands = { quit = function() return pcall(mp.commandv, 'osd-msg', 'quit') + end, + + loadfile = function(uri, mode) + if uri == nil or type(uri) ~= "string" then + return true, false, "No url provided!" + end + if mode ~= nil and + mode ~= "" and + mode ~= "replace" and + mode ~= "append" and + mode ~= "append-play" + then + print('Invalid mode: "' .. mode .. '"') + return true, false, "Invalid mode: '" .. mode .. "'" + end + if mode == nil or mode == "" then + mode = "replace" + end + return pcall(mp.commandv, "loadfile", uri, mode) end }