From 9de3b4d86f6f51f3224f601bbb361615fc80ca6e Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Sun, 29 Nov 2020 17:18:53 +0100 Subject: [PATCH] Add support for IINA player (#360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Ädd support for IINA * cleanup * Add start background image * Restore comment * Support custom player path * Update messages * Separate IINA changes from python_mpv_jsonipc * Do not show file info for our placeholder image in the UI * Fix mpv socket * Fix running IINA from frozen app Apparently, `iina-cli` gets confused when launched from a frozen app and automatically adds `--stdin` to its passed launch arguments. But then, it waits for a file to be piped and, because there is none, the player crashes almost immediately. Sending `--no-stdin` to the process resolves the ambiguity and does not cause any harm if Syncplay is started from sources. * Pass again environment to the subprocess.Popen call that opens mpv Related to: c07206c18992c1dca401b30a01b9f0fe54a71df5 --- syncplay/constants.py | 10 ++ syncplay/messages_de.py | 6 +- syncplay/messages_en.py | 6 +- syncplay/messages_es.py | 6 +- syncplay/messages_it.py | 6 +- syncplay/messages_pt_BR.py | 6 +- syncplay/messages_pt_PT.py | 6 +- syncplay/messages_ru.py | 6 +- syncplay/players/__init__.py | 7 +- syncplay/players/iina.py | 88 ++++++++++++++++++ syncplay/players/ipc_iina.py | 56 +++++++++++ syncplay/players/mpv.py | 15 ++- .../python_mpv_jsonipc/python_mpv_jsonipc.py | 48 ++++++---- syncplay/resources/IINA.png | Bin 0 -> 1513 bytes syncplay/resources/iina-bkg.png | Bin 0 -> 44656 bytes 15 files changed, 222 insertions(+), 44 deletions(-) create mode 100644 syncplay/players/iina.py create mode 100755 syncplay/players/ipc_iina.py create mode 100644 syncplay/resources/IINA.png create mode 100755 syncplay/resources/iina-bkg.png diff --git a/syncplay/constants.py b/syncplay/constants.py index 4762960..883d5a2 100755 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -139,6 +139,8 @@ USER_READY_MIN_VERSION = "1.3.0" SHARED_PLAYLIST_MIN_VERSION = "1.4.0" CHAT_MIN_VERSION = "1.5.0" FEATURE_LIST_MIN_VERSION = "1.5.0" + +IINA_PATHS = ['/Applications/IINA.app/Contents/MacOS/IINA'] MPC_PATHS = [ r"c:\program files (x86)\mpc-hc\mpc-hc.exe", r"c:\program files\mpc-hc\mpc-hc.exe", @@ -176,6 +178,7 @@ VLC_PATHS = [ ] VLC_ICONPATH = "vlc.png" +IINA_ICONPATH = "iina.png" MPLAYER_ICONPATH = "mplayer.png" MPV_ICONPATH = "mpv.png" MPVNET_ICONPATH = "mpvnet.png" @@ -250,6 +253,13 @@ MPV_ARGS = {'force-window': 'yes', 'keep-open-pause': 'yes' } +IINA_PROPERTIES = {'geometry': '25%+100+100', + 'idle': 'yes', + 'hr-seek': 'always', + 'input-terminal': 'no', + 'term-playing-msg': '\nANS_filename=${filename}\nANS_length=${=duration:${=length:0}}\nANS_path=${path}\n', + 'keep-open-pause': 'yes', + } MPV_NEW_VERSION = False MPV_OSC_VISIBILITY_CHANGE_VERSION = False diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index b1daf2e..1d6deed 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -113,7 +113,7 @@ de = { "mpv-version-error": "Syncplay ist nicht kompatibel mit dieser Version von mpv. Bitte benutze eine andere Version (z.B. Git HEAD).", "mpv-failed-advice": "The reason mpv cannot start may be due to the use of unsupported command line arguments or an unsupported version of mpv.", # TODO: Translate "player-file-open-error": "Fehler beim Öffnen der Datei durch den Player", - "player-path-error": "Ungültiger Player-Pfad. Unterstützte Player sind: mpv, mpv.net, VLC, MPC-HC, MPC-BE und mplayer2", + "player-path-error": "Ungültiger Player-Pfad. Unterstützte Player sind: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2 und IINA", "hostname-empty-error": "Hostname darf nicht leer sein", "empty-error": "{} darf nicht leer sein", # Configuration "media-player-error": "Player-Fehler: \"{}\"", # Error line @@ -124,7 +124,7 @@ de = { "unable-to-start-client-error": "Client kann nicht gestartet werden", - "player-path-config-error": "Player-Pfad ist nicht ordnungsgemäß gesetzt. Unterstützte Player sind: mpv, mpv.net, VLC, MPC-HC, MPC-BE und mplayer2", + "player-path-config-error": "Player-Pfad ist nicht ordnungsgemäß gesetzt. Unterstützte Player sind: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2 und IINA", "no-file-path-config-error": "Es muss eine Datei ausgewählt werden, bevor der Player gestartet wird.", "no-hostname-config-error": "Hostname darf nicht leer sein", "invalid-port-config-error": "Port muss gültig sein", @@ -381,7 +381,7 @@ de = { "edit-rooms-tooltip": "Edit room list.", # TO DO: Translate - "executable-path-tooltip": "Pfad zum ausgewählten, unterstützten Mediaplayer (mpv, mpv.net, VLC, MPC-HC/BE or mplayer2).", + "executable-path-tooltip": "Pfad zum ausgewählten, unterstützten Mediaplayer (mpv, mpv.net, VLC, MPC-HC/BE, mplayer2, oder IINA).", "media-path-tooltip": "Pfad zum wiederzugebenden Video oder Stream. Notwendig für mplayer2.", "player-arguments-tooltip": "Zusätzliche Kommandozeilenparameter/-schalter für diesen Mediaplayer.", "mediasearcdirectories-arguments-tooltip": "Verzeichnisse, in denen Syncplay nach Mediendateien suchen soll, z.B. wenn du die Click-to-switch-Funktion verwendest. Syncplay wird Unterverzeichnisse rekursiv durchsuchen.", # TODO: Translate Click-to-switch? (or use as name for feature) diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 3cad327..60a26e0 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -113,7 +113,7 @@ en = { "mpv-version-error": "Syncplay is not compatible with this version of mpv. Please use a different version of mpv (e.g. Git HEAD).", "mpv-failed-advice": "The reason mpv cannot start may be due to the use of unsupported command line arguments or an unsupported version of mpv.", "player-file-open-error": "Player failed opening file", - "player-path-error": "Player path is not set properly. Supported players are: mpv, mpv.net, VLC, MPC-HC, MPC-BE and mplayer2", + "player-path-error": "Player path is not set properly. Supported players are: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2, and IINA", "hostname-empty-error": "Hostname can't be empty", "empty-error": "{} can't be empty", # Configuration "media-player-error": "Media player error: \"{}\"", # Error line @@ -124,7 +124,7 @@ en = { "unable-to-start-client-error": "Unable to start client", - "player-path-config-error": "Player path is not set properly. Supported players are: mpv, mpv.net, VLC, MPC-HC, MPC-BE and mplayer2.", + "player-path-config-error": "Player path is not set properly. Supported players are: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2, and IINA.", "no-file-path-config-error": "File must be selected before starting your player", "no-hostname-config-error": "Hostname can't be empty", "invalid-port-config-error": "Port must be valid", @@ -384,7 +384,7 @@ en = { "edit-rooms-tooltip": "Edit room list.", - "executable-path-tooltip": "Location of your chosen supported media player (mpv, mpv.net, VLC, MPC-HC/BE or mplayer2).", + "executable-path-tooltip": "Location of your chosen supported media player (mpv, mpv.net, VLC, MPC-HC/BE, mplayer2 or IINA).", "media-path-tooltip": "Location of video or stream to be opened. Necessary for mplayer2.", "player-arguments-tooltip": "Additional command line arguments / switches to pass on to this media player.", "mediasearcdirectories-arguments-tooltip": "Directories where Syncplay will search for media files, e.g. when you are using the click to switch feature. Syncplay will look recursively through sub-folders.", diff --git a/syncplay/messages_es.py b/syncplay/messages_es.py index ba0d04a..3867789 100644 --- a/syncplay/messages_es.py +++ b/syncplay/messages_es.py @@ -113,7 +113,7 @@ es = { "mpv-version-error": "Syncplay no es compatible con esta versión de mpv. Por favor utiliza una versión diferente de mpv (p.ej. Git HEAD).", "mpv-failed-advice": "The reason mpv cannot start may be due to the use of unsupported command line arguments or an unsupported version of mpv.", # TODO: Translate "player-file-open-error": "El reproductor falló al abrir el archivo", - "player-path-error": "La ruta del reproductor no está definida correctamente. Los reproductores soportados son: mpv, mpv.net, VLC, MPC-HC, MPC-BE y mplayer2", + "player-path-error": "La ruta del reproductor no está definida correctamente. Los reproductores soportados son: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2, y IINA", "hostname-empty-error": "El nombre del host no puede ser vacío", "empty-error": "{} no puede ser vacío", # Configuration "media-player-error": "Error del reproductor multimedia: \"{}\"", # Error line @@ -124,7 +124,7 @@ es = { "unable-to-start-client-error": "No se logró iniciar el cliente", - "player-path-config-error": "La ruta del reproductor no está definida correctamente. Los reproductores soportados son: mpv, mpv.net, VLC, MPC-HC, MPC-BE y mplayer2.", + "player-path-config-error": "La ruta del reproductor no está definida correctamente. Los reproductores soportados son: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2 y IINA.", "no-file-path-config-error": "El archivo debe ser seleccionado antes de iniciar el reproductor", "no-hostname-config-error": "El nombre del host no puede ser vacío", "invalid-port-config-error": "El puerto debe ser válido", @@ -383,7 +383,7 @@ es = { "edit-rooms-tooltip": "Edit room list.", # TO DO: Translate - "executable-path-tooltip": "Ubicación de tu reproductor multimedia compatible elegido (mpv, mpv.net, VLC, MPC-HC/BE o mplayer2).", + "executable-path-tooltip": "Ubicación de tu reproductor multimedia compatible elegido (mpv, mpv.net, VLC, MPC-HC/BE, mplayer2 o IINA).", "media-path-tooltip": "Ubicación del video o flujo que se abrirá. Necesario para mplayer2.", "player-arguments-tooltip": "Arguementos de línea de comandos adicionales / parámetros para pasar a este reproductor multimedia.", "mediasearcdirectories-arguments-tooltip": "Directorios donde Syncplay buscará archivos de medios, p.ej. cuando estás usando la función \"clic para cambiar\". Syncplay buscará recursivamente a través de las subcarpetas.", diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index acfe3c4..6a4e9cf 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -113,7 +113,7 @@ it = { "mpv-version-error": "Syncplay non è compatibile con questa versione di mpv. Per favore usa un'altra versione di mpv (es. Git HEAD).", "mpv-failed-advice": "The reason mpv cannot start may be due to the use of unsupported command line arguments or an unsupported version of mpv.", # TODO: Translate "player-file-open-error": "Il player non è riuscito ad aprire il file", - "player-path-error": "Il path del player non è configurato correttamente. I player supportati sono: mpv, mpv.net, VLC, MPC-HC, MPC-BE e mplayer2", + "player-path-error": "Il path del player non è configurato correttamente. I player supportati sono: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2 e IINA", "hostname-empty-error": "Il campo hostname non può essere vuoto", "empty-error": "Il campo {} non può esssere vuoto", # Configuration "media-player-error": "Errore media player: \"{}\"", # Error line @@ -124,7 +124,7 @@ it = { "unable-to-start-client-error": "Impossibile avviare il client", - "player-path-config-error": "Il percorso del player non è configurato correttamente. I player supportati sono: mpv, mpv.net, VLC, MPC-HC, MPC-BE e mplayer2.", + "player-path-config-error": "Il percorso del player non è configurato correttamente. I player supportati sono: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2 e IINA.", "no-file-path-config-error": "Deve essere selezionato un file prima di avviare il player", "no-hostname-config-error": "Il campo hostname non può essere vuoto", "invalid-port-config-error": "La porta deve essere valida", @@ -383,7 +383,7 @@ it = { "edit-rooms-tooltip": "Edit room list.", # TO DO: Translate - "executable-path-tooltip": "Percorso del media player desiderato (scegliere tra mpv, mpv.net, VLC, MPC-HC/BE or mplayer2).", + "executable-path-tooltip": "Percorso del media player desiderato (scegliere tra mpv, mpv.net, VLC, MPC-HC/BE, mplayer2 o IINA).", "media-path-tooltip": "Percorso del video o stream da aprire. Necessario per mplayer2.", "player-arguments-tooltip": "Argomenti da linea di comando aggiuntivi da passare al media player scelto.", "mediasearcdirectories-arguments-tooltip": "Cartelle dove Syncplay cercherà i file multimediali, es. quando usi la funzione click to switch. Syncplay cercherà anche nelle sottocartelle.", diff --git a/syncplay/messages_pt_BR.py b/syncplay/messages_pt_BR.py index 7155011..45c2d25 100644 --- a/syncplay/messages_pt_BR.py +++ b/syncplay/messages_pt_BR.py @@ -113,7 +113,7 @@ pt_BR = { "mpv-version-error": "O motivo pelo qual o mpv não pode ser iniciado pode ser devido ao uso de argumentos da linha de comando não suportados ou a uma versão não suportada do mpv.", "mpv-failed-advice": "The reason mpv cannot start may be due to the use of unsupported command line arguments or an unsupported version of mpv.", # TODO: Translate "player-file-open-error": "O reprodutor falhou ao abrir o arquivo", - "player-path-error": "O caminho até o arquivo executável do reprodutor não está configurado corretamente. Os reprodutores suportados são: mpv, mpv.net, VLC, MPC-HC, MPC-BE e mplayer2", + "player-path-error": "O caminho até o arquivo executável do reprodutor não está configurado corretamente. Os reprodutores suportados são: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2 e IINA", "hostname-empty-error": "O endereço do servidor não pode ser vazio", "empty-error": "{} não pode ser vazio", # Configuration "media-player-error": "Erro do reprodutor de mídia: \"{}\"", # Error line @@ -124,7 +124,7 @@ pt_BR = { "unable-to-start-client-error": "Não foi possível iniciar o client", - "player-path-config-error": "O caminho até o arquivo executável do reprodutor não está configurado corretamente. Os reprodutores suportados são: mpv, mpv.net, VLC, MPC-HC, MPC-BE e mplayer2.", + "player-path-config-error": "O caminho até o arquivo executável do reprodutor não está configurado corretamente. Os reprodutores suportados são: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2 e IINA.", "no-file-path-config-error": "O arquivo deve ser selecionado antes de iniciar seu reprodutor", "no-hostname-config-error": "O endereço do servidor não pode ser vazio", "invalid-port-config-error": "A porta deve ser válida", @@ -383,7 +383,7 @@ pt_BR = { "edit-rooms-tooltip": "Edit room list.", # TO DO: Translate - "executable-path-tooltip": "Localização do seu reprodutor de mídia preferido (mpv, mpv.net, VLC, MPC-HC/BE ou mplayer2).", + "executable-path-tooltip": "Localização do seu reprodutor de mídia preferido (mpv, mpv.net, VLC, MPC-HC/BE, mplayer2 ou IINA).", "media-path-tooltip": "Localização do vídeo ou transmissão a ser aberto. Necessário com o mplayer2.", "player-arguments-tooltip": "Argumentos de comando de linha adicionais para serem repassados ao reprodutor de mídia.", "mediasearcdirectories-arguments-tooltip": "Diretório onde o Syncplay vai procurar por arquivos de mídia, por exemplo quando você estiver usando o recurso de clicar para mudar. O Syncplay irá procurar recursivamente pelas subpastas.", diff --git a/syncplay/messages_pt_PT.py b/syncplay/messages_pt_PT.py index 0bf874d..8f9d5e6 100644 --- a/syncplay/messages_pt_PT.py +++ b/syncplay/messages_pt_PT.py @@ -113,7 +113,7 @@ pt_PT = { "mpv-version-error": "O Syncplay não é compatível com esta versão do mpv. Por favor, use uma versão diferente do mpv (por exemplo, Git HEAD).", "mpv-failed-advice": "O motivo pelo qual o mpv não pode ser iniciado pode ser devido ao uso de argumentos da linha de comando não suportados ou a uma versão não suportada do mpv.", # TODO: Translate "player-file-open-error": "O reprodutor falhou ao abrir o ficheiro", - "player-path-error": "O caminho até o ficheiro executável do reprodutor não está configurado corretamente. Os reprodutores suportados são: mpv, mpv.net, VLC, MPC-HC, MPC-BE e mplayer2", + "player-path-error": "O caminho até o ficheiro executável do reprodutor não está configurado corretamente. Os reprodutores suportados são: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2 e IINA", "hostname-empty-error": "O endereço do servidor não pode ser vazio", "empty-error": "{} não pode ser vazio", # Configuration "media-player-error": "Erro do reprodutor de mídia: \"{}\"", # Error line @@ -124,7 +124,7 @@ pt_PT = { "unable-to-start-client-error": "Não foi possível iniciar o client", - "player-path-config-error": "O caminho até o ficheiro executável do reprodutor não está configurado corretamente. Os reprodutores suportados são: mpv, mpv.net, VLC, MPC-HC, MPC-BE e mplayer2.", + "player-path-config-error": "O caminho até o ficheiro executável do reprodutor não está configurado corretamente. Os reprodutores suportados são: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2 e IINA.", "no-file-path-config-error": "O ficheiro deve ser selecionado antes de iniciar seu reprodutor", "no-hostname-config-error": "O endereço do servidor não pode ser vazio", "invalid-port-config-error": "A porta deve ser válida", @@ -382,7 +382,7 @@ pt_PT = { "edit-rooms-tooltip": "Edit room list.", # TO DO: Translate - "executable-path-tooltip": "Localização do seu reprodutor de mídia preferido (mpv, mpv.net, VLC, MPC-HC/BE ou mplayer2).", + "executable-path-tooltip": "Localização do seu reprodutor de mídia preferido (mpv, mpv.net, VLC, MPC-HC/BE, mplayer2 ou IINA).", "media-path-tooltip": "Localização do vídeo ou transmissão a ser aberto. Necessário com o mplayer2.", "player-arguments-tooltip": "Argumentos de comando de linha adicionais para serem repassados ao reprodutor de mídia.", "mediasearcdirectories-arguments-tooltip": "Pasta onde o Syncplay vai procurar por ficheiros de mídia, por exemplo quando você estiver usando o recurso de clicar para mudar. O Syncplay irá procurar recursivamente pelas subpastas.", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 6c48650..3341b24 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -113,7 +113,7 @@ ru = { "mpv-version-error": "Syncplay не совместим с данной версией mpv. Пожалуйста, используйте другую версию mpv (лучше свежайшую).", "mpv-failed-advice": "The reason mpv cannot start may be due to the use of unsupported command line arguments or an unsupported version of mpv.", # TODO: Translate "player-file-open-error": "Проигрыватель не может открыть файл.", - "player-path-error": "Путь к проигрывателю задан неверно. Supported players are: mpv, mpv.net, VLC, MPC-HC, MPC-BE and mplayer2.", # TODO: Translate last sentence + "player-path-error": "Путь к проигрывателю задан неверно. Supported players are: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2 and IINA.", # TODO: Translate last sentence "hostname-empty-error": "Имя пользователя не может быть пустым.", "empty-error": "{} не может быть пустым.", # Configuration "media-player-error": "Ошибка проигрывателя: \"{}\"", # Error line @@ -124,7 +124,7 @@ ru = { "unable-to-start-client-error": "Невозможно запустить клиент", - "player-path-config-error": "Путь к проигрывателю установлен неверно. Supported players are: mpv, mpv.net, VLC, MPC-HC, MPC-BE and mplayer2", # To do: Translate end + "player-path-config-error": "Путь к проигрывателю установлен неверно. Supported players are: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2 and IINA", # To do: Translate end "no-file-path-config-error": "Файл должен быть указан до включения проигрывателя", "no-hostname-config-error": "Имя сервера не может быть пустым", "invalid-port-config-error": "Неверный номер порта", @@ -386,7 +386,7 @@ ru = { "edit-rooms-tooltip": "Edit room list.", # TO DO: Translate - "executable-path-tooltip": "Расположение Вашего видеопроигрывателя (mpv, mpv.net, VLC, MPC-HC/BE или mplayer2).", + "executable-path-tooltip": "Расположение Вашего видеопроигрывателя (mpv, mpv.net, VLC, MPC-HC/BE, mplayer2, или IINA).", "media-path-tooltip": "Расположение видеофайла или потока для просмотра. Обязательно для mplayer2.", # TODO: Confirm translation "player-arguments-tooltip": "Передавать дополнительные аргументы командной строки этому проигрывателю.", "mediasearcdirectories-arguments-tooltip": "Папки, где Syncplay будет искать медиа файлы, включая подпапки.", diff --git a/syncplay/players/__init__.py b/syncplay/players/__init__.py index 8c812db..90fa73b 100755 --- a/syncplay/players/__init__.py +++ b/syncplay/players/__init__.py @@ -12,7 +12,12 @@ try: except ImportError: from syncplay.players.basePlayer import DummyPlayer MpcBePlayer = DummyPlayer +try: + from syncplay.players.iina import IinaPlayer +except ImportError: + from syncplay.players.basePlayer import DummyPlayer + IinaPlayer = DummyPlayer def getAvailablePlayers(): - return [MPCHCAPIPlayer, MpvPlayer, MpvnetPlayer, VlcPlayer, MpcBePlayer, MplayerPlayer] + return [MPCHCAPIPlayer, MpvPlayer, MpvnetPlayer, VlcPlayer, MpcBePlayer, MplayerPlayer, IinaPlayer] diff --git a/syncplay/players/iina.py b/syncplay/players/iina.py new file mode 100644 index 0000000..12ee2c3 --- /dev/null +++ b/syncplay/players/iina.py @@ -0,0 +1,88 @@ +import os +from syncplay import constants +from syncplay.utils import findResourcePath +from syncplay.players.mpv import MpvPlayer +from syncplay.players.ipc_iina import IINA + +class IinaPlayer(MpvPlayer): + + @staticmethod + def run(client, playerPath, filePath, args): + constants.MPV_NEW_VERSION = True + constants.MPV_OSC_VISIBILITY_CHANGE_VERSION = True + return IinaPlayer(client, IinaPlayer.getExpandedPath(playerPath), filePath, args) + + @staticmethod + def getStartupArgs(userArgs): + args = {} + if userArgs: + for argToAdd in userArgs: + if argToAdd.startswith('--'): + argToAdd = argToAdd[2:] + elif argToAdd.startswith('-'): + argToAdd = argToAdd[1:] + if argToAdd.strip() == "": + continue + if "=" in argToAdd: + (argName, argValue) = argToAdd.split("=", 1) + else: + argName = argToAdd + argValue = "yes" + args[argName] = argValue + return args + + @staticmethod + def getDefaultPlayerPathsList(): + l = [] + for path in constants.IINA_PATHS: + p = IinaPlayer.getExpandedPath(path) + if p: + l.append(p) + return l + + @staticmethod + def isValidPlayerPath(path): + if "iina-cli" in path or "iina-cli" in IinaPlayer.getExpandedPath(path): + return True + return False + + @staticmethod + def getExpandedPath(playerPath): + if "iina-cli" in playerPath: + pass + elif "IINA.app/Contents/MacOS/IINA" in playerPath: + playerPath = os.path.join(os.path.dirname(playerPath), "iina-cli") + + if os.access(playerPath, os.X_OK): + return playerPath + for path in os.environ['PATH'].split(':'): + path = os.path.join(os.path.realpath(path), playerPath) + if os.access(path, os.X_OK): + return path + return playerPath + + @staticmethod + def getIconPath(path): + return constants.IINA_ICONPATH + + def __init__(self, client, playerPath, filePath, args): + from twisted.internet import reactor + self.reactor = reactor + self._client = client + self._set_defaults() + + self._playerIPCHandler = IINA + self._create_listener(playerPath, filePath, args) + + def _preparePlayer(self): + for key, value in constants.IINA_PROPERTIES.items(): + self._setProperty(key, value) + self._listener.sendLine(["load-script", findResourcePath("syncplayintf.lua")]) + super()._preparePlayer() + + def _onFileUpdate(self): + # do not show file info for our placeholder image in Syncplay UI + if self._filename == "iina-bkg.png": + return + else: + super()._onFileUpdate() diff --git a/syncplay/players/ipc_iina.py b/syncplay/players/ipc_iina.py new file mode 100755 index 0000000..a91e8ff --- /dev/null +++ b/syncplay/players/ipc_iina.py @@ -0,0 +1,56 @@ +import os.path +import subprocess +import time + +from syncplay.players.python_mpv_jsonipc.python_mpv_jsonipc import log, MPV, MPVError, MPVProcess +from syncplay.utils import resourcespath + +class IINA(MPV): + """The main IINA interface class. Use this to control the MPV player instantiated by IINA.""" + + def _start_mpv(self, ipc_socket, mpv_location, **kwargs): + # Attempt to start IINA 3 times. + for i in range(3): + try: + self.mpv_process = IINAProcess(ipc_socket, mpv_location, **kwargs) + break + except MPVError: + log.warning("IINA start failed.", exc_info=1) + continue + else: + raise MPVError("IINA process retry limit reached.") + +class IINAProcess(MPVProcess): + """ + Manages an IINA process, ensuring the socket or pipe is available. (Internal) + """ + + def _start_process(self, ipc_socket, args, env): + self.process = subprocess.Popen(args, env=env) + ipc_exists = False + for _ in range(100): # Give IINA 10 seconds to start. + time.sleep(0.1) + self.process.poll() + if os.path.exists(ipc_socket): + ipc_exists = True + log.debug("Found IINA socket.") + break + if self.process.returncode != 0: # iina-cli returns immediately after its start + log.error("IINA failed with returncode {0}.".format(self.process.returncode)) + break + else: + self.process.terminate() + raise MPVError("IINA start timed out.") + + if not ipc_exists or self.process.returncode != 0: + self.process.terminate() + raise MPVError("IINA not started.") + + def _get_arglist(self, exec_location, **kwargs): + args = [exec_location] + args.append('--no-stdin') + args.append(resourcespath + 'iina-bkg.png') + self._set_default(kwargs, "mpv-input-ipc-server", self.ipc_socket) + args.extend("--{0}={1}".format(v[0].replace("_", "-"), self._mpv_fmt(v[1])) + for v in kwargs.items()) + return args diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 3971f83..cebc63d 100755 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -504,6 +504,12 @@ class MpvPlayer(BasePlayer): from twisted.internet import reactor self.reactor = reactor self._client = client + self._set_defaults() + + self._playerIPCHandler = MPV + self._create_listener(playerPath, filePath, args) + + def _set_defaults(self): self._paused = None self._position = 0.0 self._duration = None @@ -513,8 +519,10 @@ class MpvPlayer(BasePlayer): self.lastLoadedTime = None self.fileLoaded = False self.delayedFilePath = None + + def _create_listener(self, playerPath, filePath, args): try: - self._listener = self.__Listener(self, playerPath, filePath, args) + self._listener = self.__Listener(self, self._playerIPCHandler, playerPath, filePath, args) except ValueError: self._client.ui.showMessage(getMessage("mplayer-file-required-notification")) self._client.ui.showMessage(getMessage("mplayer-file-required-notification/example")) @@ -549,7 +557,8 @@ class MpvPlayer(BasePlayer): self.lineReceived(text) class __Listener(threading.Thread): - def __init__(self, playerController, playerPath, filePath, args): + def __init__(self, playerController, playerIPCHandler, playerPath, filePath, args): + self.playerIPCHandler = playerIPCHandler self.playerPath = playerPath self.mpv_arguments = playerController.getStartupArgs(args) self.mpv_running = True @@ -594,7 +603,7 @@ class MpvPlayer(BasePlayer): env['PYTHONPATH'] = pythonPath try: socket = self.mpv_arguments.get('input-ipc-server') - self.mpvpipe = MPV(mpv_location=self.playerPath, ipc_socket=socket, loglevel="info", log_handler=self.__playerController.mpv_log_handler, quit_callback=self.stop_client, **self.mpv_arguments) + self.mpvpipe = self.playerIPCHandler(mpv_location=self.playerPath, ipc_socket=socket, loglevel="info", log_handler=self.__playerController.mpv_log_handler, quit_callback=self.stop_client, env=env, **self.mpv_arguments) except Exception as e: self.quitReason = getMessage("media-player-error").format(str(e)) + " " + getMessage("mpv-failed-advice") self.__playerController.reactor.callFromThread(self.__playerController._client.ui.showErrorMessage, self.quitReason, True) diff --git a/syncplay/players/python_mpv_jsonipc/python_mpv_jsonipc.py b/syncplay/players/python_mpv_jsonipc/python_mpv_jsonipc.py index 993606b..64b3dc2 100644 --- a/syncplay/players/python_mpv_jsonipc/python_mpv_jsonipc.py +++ b/syncplay/players/python_mpv_jsonipc/python_mpv_jsonipc.py @@ -182,7 +182,7 @@ class MPVProcess: """ Manages an MPV process, ensuring the socket or pipe is available. (Internal) """ - def __init__(self, ipc_socket, mpv_location=None, **kwargs): + def __init__(self, ipc_socket, mpv_location=None, env=None, **kwargs): """ Create and start the MPV process. Will block until socket/pipe is available. @@ -198,7 +198,6 @@ class MPVProcess: mpv_location = "mpv" log.debug("Staring MPV from {0}.".format(mpv_location)) - ipc_socket_name = ipc_socket if os.name == 'nt': ipc_socket = "\\\\.\\pipe\\" + ipc_socket @@ -207,13 +206,11 @@ class MPVProcess: log.debug("Using IPC socket {0} for MPV.".format(ipc_socket)) self.ipc_socket = ipc_socket - args = [mpv_location] - self._set_default(kwargs, "idle", True) - self._set_default(kwargs, "input_ipc_server", ipc_socket_name) - self._set_default(kwargs, "input_terminal", False) - self._set_default(kwargs, "terminal", False) - args.extend("--{0}={1}".format(v[0].replace("_", "-"), self._mpv_fmt(v[1])) - for v in kwargs.items()) + args = self._get_arglist(mpv_location, **kwargs) + + self._start_process(ipc_socket, args, env=env) + + def _start_process(self, ipc_socket, args, env): self.process = subprocess.Popen(args) ipc_exists = False for _ in range(100): # Give MPV 10 seconds to start. @@ -238,6 +235,16 @@ class MPVProcess: if key not in prop_dict: prop_dict[key] = value + def _get_arglist(self, exec_location, **kwargs): + args = [exec_location] + self._set_default(kwargs, "idle", True) + self._set_default(kwargs, "input_ipc_server", self.ipc_socket) + self._set_default(kwargs, "input_terminal", False) + self._set_default(kwargs, "terminal", False) + args.extend("--{0}={1}".format(v[0].replace("_", "-"), self._mpv_fmt(v[1])) + for v in kwargs.items()) + return args + def _mpv_fmt(self, data): if data == True: return "yes" @@ -405,16 +412,7 @@ class MPV: ipc_socket = "/tmp/{0}".format(rand_file) if start_mpv: - # Attempt to start MPV 3 times. - for i in range(3): - try: - self.mpv_process = MPVProcess(ipc_socket, mpv_location, **kwargs) - break - except MPVError: - log.warning("MPV start failed.", exc_info=1) - continue - else: - raise MPVError("MPV process retry limit reached.") + self._start_mpv(ipc_socket, mpv_location, **kwargs) self.mpv_inter = MPVInter(ipc_socket, self._callback, self._quit_callback) self.properties = set(x.replace("-", "_") for x in self.command("get_property", "property-list")) @@ -451,6 +449,18 @@ class MPV: if len(args) == 2 and args[0] == "custom-bind": self.event_handler.put_task(self.key_bindings[args[1]]) + def _start_mpv(self, ipc_socket, mpv_location, **kwargs): + # Attempt to start MPV 3 times. + for i in range(3): + try: + self.mpv_process = MPVProcess(ipc_socket, mpv_location, **kwargs) + break + except MPVError: + log.warning("MPV start failed.", exc_info=1) + continue + else: + raise MPVError("MPV process retry limit reached.") + def _quit_callback(self): """ Internal handler for quit events. diff --git a/syncplay/resources/IINA.png b/syncplay/resources/IINA.png new file mode 100644 index 0000000000000000000000000000000000000000..525fb55397d9bb4982ab263f6b84c284537bbceb GIT binary patch literal 1513 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 z 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 O9nVA0m7lqO|Nj6m#)@J9 literal 0 HcmV?d00001