Syncplay / syncplay

Client/server to synchronize media playback on mpv/VLC/MPC-HC/MPC-BE on many computers
http://syncplay.pl/
Apache License 2.0
2.11k stars 214 forks source link

External interface to add one or multiple files to the playlist #232

Closed FichteFoll closed 3 years ago

FichteFoll commented 5 years ago

I would like to automate construction of the playlist based on a library and an a database of episodes I haven't watched yet. In order to do that, I need a CLI or IPC interface. Since syncplay already is a client and we have a way to modify the server's state with a different client without issues, I don't think providing an IPC interface through sockets or pipes would be worthwhile.

Afaik syncplay uses IRC as the underlying protocol, so the easiest solution for me would be to emulate the protocol and use an existing IRC library to add the files with a "bot" user that joins a room, sends the necessary commands and then quits. That would be enough for my case, but there are alternatives to consider.

A different use case would be to open a folder from CLI with syncplay and have all its files be added to the playlist, or provide a list of files through shell globbing. Then use the application as normally. You can already open syncplay with a file argument, but that does not modify the playlist and instead plays the file directly. If a command line argument to only add the files and then quit was added, this could be used by other programs freely without having to emulate the protocol.

FichteFoll commented 5 years ago

Here's what I ended up with when interfacing with syncplay directly as a library:

https://github.com/FichteFoll/dotfiles/blob/a595d72354363cc294103e822070ffd98aa6e684/tools/tools/trackma2syncplay.py#L85-L136

It's a bit hacky because I need to mock the interface and fiddle with twisted, but it works. Except for the player opening and me not having figured out yet how to close it after I'm done. Any hints for that would be appreciated, otherwise I'll look back at it in a few weeks.

Et0h commented 5 years ago

A few comments:

  1. Syncplay wasn't specifically designed to be hacked in that way, so your code was an interesting read.
  2. Syncplay's protocol is a custom JSON over TCP/IP protocol, not IRC.
  3. For details of the Syncplay protocol see https://syncplay.pl/about/protocol/ for details of the basics. This doesn't include an explanation of the playlist, but you can see how that works by looking at https://github.com/Syncplay/syncplay/blob/master/syncplay/protocols.py#L183 or running Syncplay in --debug mode and seeing the communication. It uses set commands for playlistIndex and playlistChange.
  4. Syncplay has a CLI (command line interface) mode which you can invoke using --no-gui as per https://syncplay.pl/guide/client/ - I don't use Linux, but I assume if you run a CLI app as a child then you can control the input/output.
  5. The only problem with CLI mode is that it doesn't currently allow you to control the playlist. At present console.py does not implement the setPlaylist command and does not invoke syncplayClient.playlist.changePlaylist. You could, for example, implement a CLI option to load and save the playlist to a text file.
Et0h commented 5 years ago

A few more comments:

A few notes on adding command line options to Syncplay:

  1. The command line options are specified in /syncplay/ui/ConfigurationGetter.py as part of the getConfiguration method using self._argparser.add_argument functions.
  2. See https://docs.python.org/3.7/library/argparse.html for more details on how argparser works, or figure it out from our code. The unusual bit is that we use getMessage() to get the help info for the argument, and this means it then needs to specify the name of a message associated with entries in /syncplay/messages_en.py, messages_it.py, etc.
  3. For each option, you create an associated entry in /syncplay/ui/ConfigurationGetter.py. This involves adding a variable in the ConfigurationGetter init which specifies self._config (for the default value), for non-strings specifying the type of variable in self._boolean etc, and then ensuring the command line options are picked up via _overrideConfigWithArgs by converting any difference between the command line option or metavar and the name of the variable in self._config (with hyphens converted to underscores in the args).
  4. You can now access the command line option via self._config[]
FichteFoll commented 5 years ago

Thanks for taking a look. I figured out most of the internals that my code touches because I had to understand them first, like the protocol being JSON (but also the protocol handler being interweaved with ui code and all that, making it hard to be used on its own). I considered writing a custom implementation of the protocol but decided reusing it would be faster to implement at least.

I'd probably take a look into --load-playlist-from-file command and maybe as add that functionality to the ui, depending on whether I find the time to do that.

Controlling the cli mode is interesting, but as you mentioned doesn't benefit me without a command to modify the Playlist.

For now, I intend to use this mainly as a one-shot command and not keep the syncplay process (and the video player) around.

Et0h commented 5 years ago

I've coded a --load-playlist-from-file command line argument which will load the playlist.

Furthermore, you drag a plaintext .txt, .m3u or .m3u8 file into the main part of the Syncplay window it will load it directly as a playlist. If you hold shift (to make it a move event) it will shuffle the loaded playlist.

If someone wants to add the functionality to load/save from the playlist context menu then feel free to do so, just ensure any strings are translatable. You could also process an .m3u/.m3u8 file to remove any superfluous path information and metadata if you want.

FichteFoll commented 5 years ago

Thanks. This doesn't seem to allow closing syncplay after the playlist was modified though, does it? That's the only inconvenience in my current workflow still using the script above.

FichteFoll commented 4 years ago

Sorry for the late reply, but I finally got around to testing this. The feature works, but I have a few suggestions:

  1. There is no encoding specified when reading the file, which means it will use the system's preferred encoding and may not support characters not in that character set. How about ubiquitously requiring UTF-8 encoding?
  2. I'd appreciate a "shoot and forget" operation mode where I can just launch syncplay with --load-playlist-from-file, have it change the playlist of the configured room, and then quit without opening the player. That way I could can update the playlist through a third-party application or script while syncplay is already running. I can skip the config dialog with --no-gui already.
FichteFoll commented 4 years ago

Interestingly, even when killing the spawned subprocess or sending SIGINT, the spawned mpv persists despite the process returning. However, actually pressing ctrl-c works.

    cmd = ["syncplay", "--no-gui", "--load-playlist-from-file", f.name]
    with subprocess.Popen(cmd) as proc:
        try:
            proc.wait(SYNCPLAY_WAIT)
        except subprocess.TimeoutExpired:
            # TODO Windows support
            proc.send_signal(signal.SIGINT)
FichteFoll commented 4 years ago

Actually, I realized that --load-playlist-from-file replaces the playlist instead of appending like my script did earlier. Idk what happens when you drag a text file on it, but when dragging a video file onto the playlist, it adds that and does not replace the entire playlist. It should do the same for playlist files, imo.

Reading from stdin would also be neat, but not necessary (for me) and just a bonus. You can work around that easily.

Et0h commented 3 years ago

Syncplay now supports adding items to the playlist using the CLI interface and so I am considering this matter closed. If someone wants to add an --append-playlist-from-file option then they can make a PR request with that feature.

FichteFoll commented 3 years ago

Unfortunately, for me the issue is not resolved, because the added fetaure does not meet my requirements of being able to append a certain list of file( name)s to the playlist through CLI, ideally without opening the video player and instead exiting immediately. I can set the playlist now, but I have no way of retrieving the current playlist to append to it before setting again, afaict.

Would you mind reopening the issue?

I am still interested in getting this resolved, but I haven't been able to spend time on refining what I have currently (which works but is slighly inconvenient).

Et0h commented 3 years ago

You can get the playlist with the /ql command and add to it with the /qa command.

FichteFoll commented 3 years ago

Hm, true, I guess I could read that from stdout in no-gui mode and then qa all the files I want to add individually. Edit: Actually, if I do this, I don't need to parse the existing playlist at all, since I only want to append anyway and syncplay already deduplicates the playlist.

Would you be interested in adding a --no-player mode? And a command to close syncplay? That would also prevent me from running into #322 all the time.

Et0h commented 3 years ago

A /x command to easily close Syncplay seems reasonable for a future release.

However, I think that supporting --no-player seems too much of an edge case to be worth all the quirks (and maintenance burden) it might create. If you want that functionality you might be better coding your own single purpose application which does exactly what you want.

FichteFoll commented 3 years ago

However, I think that supporting --no-player seems too much of an edge case to be worth all the quirks (and maintenance burden) it might create.

From how I remember the code being structured, I would agree. As I mentioned earlier, the ui and player code is quite tightly coupled with a lot of logic that does not seem worth to decouple just so I wouldn't have to see a player window pop up that I then close anyway.

I suppose the closing command would be enough to have this issue resolved, since the no-gui mode can be automated well enough, imo. Normally I would suggest \q, but that might be confusing next to the commands that affect the queue.

Edit: I want to nominate the more verbose \quit because a command of this magnitude deserves to be written in full.

Et0h commented 3 years ago

There might be some mpv command line switches which hide the player showing up, or you might be able to point Syncplay to a dummy exe with the same filename as a supported player. I don't know what quirks that could result in though.

daniel-123 commented 3 years ago

@FichteFoll if you want to "trick" mpv into playing a file while doing nothing you can just specify following options: mpv --ao=null --no-video. Though it will still need physical presence of the file not to freak out.

FichteFoll commented 3 years ago

Sounds like a good idea. However, syncplay --help doesn't list a parameter that allows overriding the player arguments, so I presume that would need to be added?

Et0h commented 3 years ago

https://syncplay.pl/guide/client/ states you can use -- as a last argument for Syncplay to prepend arguments that are meant to be passed to the media player. However, it's not something I've tested for a long time so I don't know if it still works.

FichteFoll commented 3 years ago

I ended up interfacing with the socket connection myself, which was much easier and simpler (kinda as anticipated). I figure a separate CLI executable for such tasks would be more beneficial for syncplay, if this was desired. As such, I'm closing this issue as my personal need has been solved. Thanks for the support.

https://github.com/FichteFoll/dotfiles/commit/adf9ea89a24cf201ed52c7393d124960bb457419