Revert "Revert "Code Syntax Refactoring""
This reverts commit 51fba3722f653156eca30d45cb07846fb725a777.
This commit is contained in:
parent
2e987e8b0d
commit
ac4da59479
15
appdmg.py
15
appdmg.py
@ -7,6 +7,7 @@ import os.path
|
||||
application = defines.get('app', 'dist/Syncplay.app')
|
||||
appname = os.path.basename(application)
|
||||
|
||||
|
||||
def icon_from_app(app_path):
|
||||
plist_path = os.path.join(app_path, 'Contents', 'Info.plist')
|
||||
plist = biplist.readPlist(plist_path)
|
||||
@ -17,6 +18,7 @@ def icon_from_app(app_path):
|
||||
icon_name = icon_root + icon_ext
|
||||
return os.path.join(app_path, 'Contents', 'Resources', icon_name)
|
||||
|
||||
|
||||
# Volume format (see hdiutil create -help)
|
||||
format = defines.get('format', 'UDZO')
|
||||
|
||||
@ -27,10 +29,19 @@ compression_level = 9
|
||||
size = defines.get('size', None)
|
||||
|
||||
# Files to include
|
||||
files = [ application, 'resources/lua/intf/.syncplay.lua', 'resources/.macos_vlc_install.command', 'resources/.macOS_readme.pdf' ]
|
||||
files = [
|
||||
application,
|
||||
'resources/lua/intf/.syncplay.lua',
|
||||
'resources/.macos_vlc_install.command',
|
||||
'resources/.macOS_readme.pdf'
|
||||
]
|
||||
|
||||
# Symlinks to create
|
||||
symlinks = { 'Applications': '/Applications', 'Install for VLC': '.macos_vlc_install.command', 'Read Me': '.macOS_readme.pdf' }
|
||||
symlinks = {
|
||||
'Applications': '/Applications',
|
||||
'Install for VLC': '.macos_vlc_install.command',
|
||||
'Read Me': '.macOS_readme.pdf'
|
||||
}
|
||||
|
||||
# Volume icon
|
||||
#
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
# *** TROUBLESHOOTING ***
|
||||
# 1) If you get the error "ImportError: No module named zope.interface" then add an empty __init__.py file to the PYTHONDIR/Lib/site-packages/zope directory
|
||||
# 2) It is expected that you will have NSIS 3 NSIS from http://nsis.sourceforge.net installed.
|
||||
|
||||
import sys, codecs
|
||||
import codecs
|
||||
import sys
|
||||
# try:
|
||||
# if (sys.version_info.major != 2) or (sys.version_info.minor < 7):
|
||||
# raise Exception("You must build Syncplay with Python 2.7!")
|
||||
@ -14,18 +14,20 @@ import sys, codecs
|
||||
# import warnings
|
||||
# warnings.warn("You must build Syncplay with Python 2.7!")
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from string import Template
|
||||
|
||||
import syncplay
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from syncplay.messages import getMissingStrings
|
||||
|
||||
|
||||
missingStrings = getMissingStrings()
|
||||
if missingStrings is not None and missingStrings is not "":
|
||||
import warnings
|
||||
warnings.warn("MISSING/UNUSED STRINGS DETECTED:\n{}".format(missingStrings))
|
||||
|
||||
|
||||
def get_nsis_path():
|
||||
bin_name = "makensis.exe"
|
||||
from winreg import HKEY_LOCAL_MACHINE as HKLM
|
||||
@ -39,9 +41,11 @@ def get_nsis_path():
|
||||
raise Exception("You must install NSIS 3 or later.")
|
||||
except WindowsError:
|
||||
return bin_name
|
||||
|
||||
|
||||
NSIS_COMPILE = get_nsis_path()
|
||||
|
||||
OUT_DIR = "dist\Syncplay"
|
||||
OUT_DIR = r"dist\Syncplay"
|
||||
SETUP_SCRIPT_PATH = "syncplay_setup.nsi"
|
||||
NSIS_SCRIPT_TEMPLATE = r"""
|
||||
!include LogicLib.nsh
|
||||
@ -608,6 +612,7 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
SectionEnd
|
||||
"""
|
||||
|
||||
|
||||
class NSISScript(object):
|
||||
def create(self):
|
||||
fileList, totalSize = self.getBuildDirContents(OUT_DIR)
|
||||
@ -664,7 +669,9 @@ class NSISScript(object):
|
||||
delete.append('RMdir "$INSTDIR\\{}"'.format(file_))
|
||||
return "\n".join(delete)
|
||||
|
||||
guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock_go.png',
|
||||
|
||||
guiIcons = [
|
||||
'resources/accept.png', 'resources/arrow_undo.png', 'resources/clock_go.png',
|
||||
'resources/control_pause_blue.png', 'resources/cross.png', 'resources/door_in.png',
|
||||
'resources/folder_explore.png', 'resources/help.png', 'resources/table_refresh.png',
|
||||
'resources/timeline_marker.png', 'resources/control_play_blue.png',
|
||||
@ -686,7 +693,13 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock
|
||||
'resources/email_go.png',
|
||||
'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png', 'resources/spinner.mng'
|
||||
]
|
||||
resources = ["resources/icon.ico", "resources/syncplay.png", "resources/syncplayintf.lua", "resources/license.rtf", "resources/third-party-notices.rtf"]
|
||||
resources = [
|
||||
"resources/icon.ico",
|
||||
"resources/syncplay.png",
|
||||
"resources/syncplayintf.lua",
|
||||
"resources/license.rtf",
|
||||
"resources/third-party-notices.rtf"
|
||||
]
|
||||
resources.extend(guiIcons)
|
||||
intf_resources = ["resources/lua/intf/syncplay.lua"]
|
||||
|
||||
|
||||
@ -18,7 +18,12 @@ OPTIONS = {
|
||||
'extra_scripts': 'syncplayServer.py',
|
||||
'includes': {'PySide2.QtCore', 'PySide2.QtUiTools', 'PySide2.QtGui', 'PySide2.QtWidgets', 'certifi'},
|
||||
'excludes': {'PySide', 'PySide.QtCore', 'PySide.QtUiTools', 'PySide.QtGui'},
|
||||
'qt_plugins': ['platforms/libqcocoa.dylib', 'platforms/libqminimal.dylib','platforms/libqoffscreen.dylib', 'styles/libqmacstyle.dylib'],
|
||||
'qt_plugins': [
|
||||
'platforms/libqcocoa.dylib',
|
||||
'platforms/libqminimal.dylib',
|
||||
'platforms/libqoffscreen.dylib',
|
||||
'styles/libqmacstyle.dylib'
|
||||
],
|
||||
'plist': {
|
||||
'CFBundleName': 'Syncplay',
|
||||
'CFBundleShortVersionString': syncplay.version,
|
||||
|
||||
@ -6,7 +6,8 @@
|
||||
# 1) If you get the error "ImportError: No module named zope.interface" then add an empty __init__.py file to the PYTHONDIR/Lib/site-packages/zope directory
|
||||
# 2) It is expected that you will have NSIS 3 NSIS from http://nsis.sourceforge.net installed.
|
||||
|
||||
import sys, codecs
|
||||
import codecs
|
||||
import sys
|
||||
# try:
|
||||
# if (sys.version_info.major != 2) or (sys.version_info.minor < 7):
|
||||
# raise Exception("You must build Syncplay with Python 2.7!")
|
||||
@ -14,23 +15,26 @@ import sys, codecs
|
||||
# import warnings
|
||||
# warnings.warn("You must build Syncplay with Python 2.7!")
|
||||
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from string import Template
|
||||
|
||||
from distutils.core import setup
|
||||
try:
|
||||
from py2exe.build_exe import py2exe
|
||||
except ImportError:
|
||||
from py2exe.distutils_buildexe import py2exe
|
||||
from string import Template
|
||||
|
||||
import syncplay
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from syncplay.messages import getMissingStrings
|
||||
|
||||
missingStrings = getMissingStrings()
|
||||
if missingStrings is not None and missingStrings is not "":
|
||||
import warnings
|
||||
warnings.warn("MISSING/UNUSED STRINGS DETECTED:\n{}".format(missingStrings))
|
||||
|
||||
|
||||
def get_nsis_path():
|
||||
bin_name = "makensis.exe"
|
||||
from winreg import HKEY_LOCAL_MACHINE as HKLM
|
||||
@ -44,6 +48,8 @@ def get_nsis_path():
|
||||
raise Exception("You must install NSIS 3 or later.")
|
||||
except WindowsError:
|
||||
return bin_name
|
||||
|
||||
|
||||
NSIS_COMPILE = get_nsis_path()
|
||||
|
||||
OUT_DIR = "syncplay_v{}".format(syncplay.version)
|
||||
@ -613,6 +619,7 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
SectionEnd
|
||||
"""
|
||||
|
||||
|
||||
class NSISScript(object):
|
||||
def create(self):
|
||||
fileList, totalSize = self.getBuildDirContents(OUT_DIR)
|
||||
@ -669,6 +676,7 @@ class NSISScript(object):
|
||||
delete.append('RMdir "$INSTDIR\\{}"'.format(file_))
|
||||
return "\n".join(delete)
|
||||
|
||||
|
||||
class build_installer(py2exe):
|
||||
def run(self):
|
||||
py2exe.run(self)
|
||||
@ -678,7 +686,9 @@ class build_installer(py2exe):
|
||||
script.compile()
|
||||
print("*** DONE ***")
|
||||
|
||||
guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock_go.png',
|
||||
|
||||
guiIcons = [
|
||||
'resources/accept.png', 'resources/arrow_undo.png', 'resources/clock_go.png',
|
||||
'resources/control_pause_blue.png', 'resources/cross.png', 'resources/door_in.png',
|
||||
'resources/folder_explore.png', 'resources/help.png', 'resources/table_refresh.png',
|
||||
'resources/timeline_marker.png', 'resources/control_play_blue.png',
|
||||
@ -700,7 +710,13 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock
|
||||
'resources/email_go.png',
|
||||
'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png', 'resources/spinner.mng'
|
||||
]
|
||||
resources = ["resources/icon.ico", "resources/syncplay.png", "resources/syncplayintf.lua", "resources/license.rtf", "resources/third-party-notices.rtf"]
|
||||
resources = [
|
||||
"resources/icon.ico",
|
||||
"resources/syncplay.png",
|
||||
"resources/syncplayintf.lua",
|
||||
"resources/license.rtf",
|
||||
"resources/third-party-notices.rtf"
|
||||
]
|
||||
resources.extend(guiIcons)
|
||||
intf_resources = ["resources/lua/intf/syncplay.lua"]
|
||||
|
||||
@ -714,11 +730,16 @@ common_info = dict(
|
||||
|
||||
info = dict(
|
||||
common_info,
|
||||
windows=[{"script":"syncplayClient.py", "icon_resources":[(1, "resources\\icon.ico")], 'dest_base': "Syncplay"},],
|
||||
windows=[{
|
||||
"script": "syncplayClient.py",
|
||||
"icon_resources": [(1, "resources\\icon.ico")],
|
||||
'dest_base': "Syncplay"},
|
||||
],
|
||||
console=['syncplayServer.py'],
|
||||
# *** If you wish to make the Syncplay client use console mode (for --no-gui to work) then comment out the above two lines and uncomment the following line:
|
||||
# console=['syncplayServer.py', {"script":"syncplayClient.py", "icon_resources":[(1, "resources\\icon.ico")], 'dest_base': "Syncplay"}],
|
||||
options={'py2exe': {
|
||||
options={
|
||||
'py2exe': {
|
||||
'dist_dir': OUT_DIR,
|
||||
'packages': 'PySide.QtUiTools',
|
||||
'includes': 'twisted, sys, encodings, datetime, os, time, math, PySide, liburl, ast, unicodedata, _ssl',
|
||||
|
||||
@ -1,22 +1,27 @@
|
||||
|
||||
import ast
|
||||
import collections
|
||||
import hashlib
|
||||
import os.path
|
||||
import time
|
||||
import random
|
||||
import re
|
||||
import sys
|
||||
import ast
|
||||
import random
|
||||
import threading
|
||||
import time
|
||||
from copy import deepcopy
|
||||
from functools import wraps
|
||||
|
||||
from twisted.internet.protocol import ClientFactory
|
||||
from twisted.internet import reactor, task, defer, threads
|
||||
from functools import wraps
|
||||
from copy import deepcopy
|
||||
from syncplay.protocols import SyncClientProtocol
|
||||
|
||||
from syncplay import utils, constants, version
|
||||
from syncplay.utils import isMacOS
|
||||
from syncplay.messages import getMissingStrings, getMessage
|
||||
from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \
|
||||
PRIVACY_HIDDENFILENAME
|
||||
import collections
|
||||
from syncplay.messages import getMissingStrings, getMessage
|
||||
from syncplay.protocols import SyncClientProtocol
|
||||
from syncplay.utils import isMacOS
|
||||
|
||||
|
||||
class SyncClientFactory(ClientFactory):
|
||||
def __init__(self, client, retry=constants.RECONNECT_RETRIES):
|
||||
self._client = client
|
||||
@ -58,6 +63,7 @@ class SyncClientFactory(ClientFactory):
|
||||
def stopRetrying(self):
|
||||
self._timesTried = self.retry
|
||||
|
||||
|
||||
class SyncplayClient(object):
|
||||
def __init__(self, playerClass, ui, config):
|
||||
constants.SHOW_OSD = config['showOSD']
|
||||
@ -203,12 +209,19 @@ class SyncplayClient(object):
|
||||
self._playerPosition = position
|
||||
self._playerPaused = paused
|
||||
currentLength = self.userlist.currentUser.file["duration"] if self.userlist.currentUser.file else 0
|
||||
if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\
|
||||
and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD:
|
||||
if (
|
||||
pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH
|
||||
and abs(position - currentLength) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD
|
||||
):
|
||||
self.playlist.advancePlaylistCheck()
|
||||
elif pauseChange and "readiness" in self.serverFeatures and self.serverFeatures["readiness"]:
|
||||
if currentLength == 0 or currentLength == -1 or\
|
||||
not (not self.playlist.notJustChangedPlaylist() and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD):
|
||||
if (
|
||||
currentLength == 0 or currentLength == -1 or
|
||||
not (
|
||||
not self.playlist.notJustChangedPlaylist() and
|
||||
abs(position - currentLength) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD
|
||||
)
|
||||
):
|
||||
pauseChange = self._toggleReady(pauseChange, paused)
|
||||
|
||||
if self._lastGlobalUpdate:
|
||||
@ -304,7 +317,7 @@ class SyncplayClient(object):
|
||||
self.setPosition(self.getGlobalPosition())
|
||||
self._player.setPaused(True)
|
||||
madeChangeOnPlayer = True
|
||||
if (self.lastLeftTime < time.time() - constants.OSD_DURATION) or (hideFromOSD == True):
|
||||
if (self.lastLeftTime < time.time() - constants.OSD_DURATION) or hideFromOSD:
|
||||
self.ui.showMessage(getMessage("pause-notification").format(setBy), hideFromOSD)
|
||||
else:
|
||||
self.ui.showMessage(getMessage("left-paused-notification").format(self.lastLeftUser, setBy), hideFromOSD)
|
||||
@ -351,9 +364,9 @@ class SyncplayClient(object):
|
||||
self._lastGlobalUpdate = time.time()
|
||||
if doSeek:
|
||||
madeChangeOnPlayer = self._serverSeeked(position, setBy)
|
||||
if diff > self._config['rewindThreshold'] and not doSeek and not self._config['rewindOnDesync'] == False:
|
||||
if diff > self._config['rewindThreshold'] and not doSeek and self._config['rewindOnDesync']:
|
||||
madeChangeOnPlayer = self._rewindPlayerDueToTimeDifference(position, setBy)
|
||||
if self._config['fastforwardOnDesync'] and (self.userlist.currentUser.canControl() == False or self._config['dontSlowDownWithMe'] == True):
|
||||
if self._config['fastforwardOnDesync'] and (not self.userlist.currentUser.canControl() or self._config['dontSlowDownWithMe']):
|
||||
if diff < (constants.FASTFORWARD_BEHIND_THRESHOLD * -1) and not doSeek:
|
||||
if self.behindFirstDetected is None:
|
||||
self.behindFirstDetected = time.time()
|
||||
@ -365,11 +378,11 @@ class SyncplayClient(object):
|
||||
self.behindFirstDetected = time.time() + constants.FASTFORWARD_RESET_THRESHOLD
|
||||
else:
|
||||
self.behindFirstDetected = None
|
||||
if self._player.speedSupported and not doSeek and not paused and not self._config['slowOnDesync'] == False:
|
||||
if self._player.speedSupported and not doSeek and not paused and self._config['slowOnDesync']:
|
||||
madeChangeOnPlayer = self._slowDownToCoverTimeDifference(diff, setBy)
|
||||
if paused == False and pauseChanged:
|
||||
if not paused and pauseChanged:
|
||||
madeChangeOnPlayer = self._serverUnpaused(setBy)
|
||||
elif paused == True and pauseChanged:
|
||||
elif paused and pauseChanged:
|
||||
madeChangeOnPlayer = self._serverPaused(setBy)
|
||||
return madeChangeOnPlayer
|
||||
|
||||
@ -566,11 +579,11 @@ class SyncplayClient(object):
|
||||
constants.MAX_ROOM_NAME_LENGTH = self.serverFeatures["maxRoomNameLength"]
|
||||
if self.serverFeatures["maxFilenameLength"] is not None:
|
||||
constants.MAX_FILENAME_LENGTH = self.serverFeatures["maxFilenameLength"]
|
||||
constants.MPV_SYNCPLAYINTF_CONSTANTS_TO_SEND = ["MaxChatMessageLength={}".format(constants.MAX_CHAT_MESSAGE_LENGTH),
|
||||
constants.MPV_SYNCPLAYINTF_CONSTANTS_TO_SEND = [
|
||||
"MaxChatMessageLength={}".format(constants.MAX_CHAT_MESSAGE_LENGTH),
|
||||
"inputPromptStartCharacter={}".format(constants.MPV_INPUT_PROMPT_START_CHARACTER),
|
||||
"inputPromptEndCharacter={}".format(constants.MPV_INPUT_PROMPT_END_CHARACTER),
|
||||
"backslashSubstituteCharacter={}".format(
|
||||
constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER)]
|
||||
"backslashSubstituteCharacter={}".format(constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER)]
|
||||
self.ui.setFeatures(self.serverFeatures)
|
||||
if self._player:
|
||||
self._player.setFeatures(self.serverFeatures)
|
||||
@ -586,7 +599,6 @@ class SyncplayClient(object):
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def sendFile(self):
|
||||
file_ = self.getSanitizedCurrentUserFile()
|
||||
if self._protocol and self._protocol.logged and file_:
|
||||
@ -762,7 +774,7 @@ class SyncplayClient(object):
|
||||
from syncplay.ui.ConfigurationGetter import ConfigurationGetter
|
||||
ConfigurationGetter().setConfigOption("sharedPlaylistEnabled", newState)
|
||||
self._config["sharedPlaylistEnabled"] = newState
|
||||
if oldState == False and newState == True:
|
||||
if not oldState and newState:
|
||||
self.playlist.loadCurrentPlaylistIndex()
|
||||
|
||||
def changeAutoplayState(self, newState):
|
||||
@ -773,7 +785,7 @@ class SyncplayClient(object):
|
||||
oldAutoplayConditionsMet = self.autoplayConditionsMet()
|
||||
self.autoPlayThreshold = newThreshold
|
||||
newAutoplayConditionsMet = self.autoplayConditionsMet()
|
||||
if oldAutoplayConditionsMet == False and newAutoplayConditionsMet == True:
|
||||
if not oldAutoplayConditionsMet and newAutoplayConditionsMet:
|
||||
self.autoplayCheck()
|
||||
|
||||
def autoplayCheck(self):
|
||||
@ -799,9 +811,12 @@ class SyncplayClient(object):
|
||||
|
||||
def autoplayConditionsMet(self):
|
||||
recentlyReset = (self.lastRewindTime is not None and abs(time.time() - self.lastRewindTime) < 10) and self._playerPosition < 3
|
||||
return self._playerPaused and (self.autoPlay or recentlyReset) and self.userlist.currentUser.canControl() and self.userlist.isReadinessSupported()\
|
||||
and self.userlist.areAllUsersInRoomReady(requireSameFilenames=self._config["autoplayRequireSameFilenames"])\
|
||||
return (
|
||||
self._playerPaused and (self.autoPlay or recentlyReset) and
|
||||
self.userlist.currentUser.canControl() and self.userlist.isReadinessSupported()
|
||||
and self.userlist.areAllUsersInRoomReady(requireSameFilenames=self._config["autoplayRequireSameFilenames"])
|
||||
and ((self.autoPlayThreshold and self.userlist.usersInRoomCount() >= self.autoPlayThreshold) or recentlyReset)
|
||||
)
|
||||
|
||||
def autoplayTimerIsRunning(self):
|
||||
return self.autoplayTimer.running
|
||||
@ -1049,6 +1064,7 @@ class SyncplayClient(object):
|
||||
elif not self._userlist.currentUser.isReady(): # CurrentUser should always be reminded they are set to not ready
|
||||
self.checkReadyStates()
|
||||
|
||||
|
||||
class SyncplayUser(object):
|
||||
def __init__(self, username=None, room=None, file_=None):
|
||||
self.ready = None
|
||||
@ -1113,6 +1129,7 @@ class SyncplayUser(object):
|
||||
def setFeatures(self, features):
|
||||
self._features = features
|
||||
|
||||
|
||||
class SyncplayUserlist(object):
|
||||
def __init__(self, ui, client):
|
||||
self.currentUser = SyncplayUser()
|
||||
@ -1141,7 +1158,7 @@ class SyncplayUserlist(object):
|
||||
showOnOSD = constants.SHOW_OSD_WARNINGS
|
||||
else:
|
||||
showOnOSD = constants.SHOW_DIFFERENT_ROOM_OSD
|
||||
if constants.SHOW_NONCONTROLLER_OSD == False and self.canControl(username) == False:
|
||||
if constants.SHOW_NONCONTROLLER_OSD == False and not self.canControl(username):
|
||||
showOnOSD = False
|
||||
hideFromOSD = not showOnOSD
|
||||
if not file_:
|
||||
@ -1258,12 +1275,16 @@ class SyncplayUserlist(object):
|
||||
return False
|
||||
for user in self._users.values():
|
||||
if user.room == self.currentUser.room:
|
||||
if user.isReadyWithFile() == False:
|
||||
if not user.isReadyWithFile() == False:
|
||||
return False
|
||||
elif requireSameFilenames and\
|
||||
(self.currentUser.file is None
|
||||
elif (
|
||||
requireSameFilenames and
|
||||
(
|
||||
self.currentUser.file is None
|
||||
or user.file is None
|
||||
or not utils.sameFilename(self.currentUser.file['name'], user.file['name'])):
|
||||
or not utils.sameFilename(self.currentUser.file['name'], user.file['name'])
|
||||
)
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -1394,6 +1415,7 @@ class SyncplayUserlist(object):
|
||||
rooms = collections.OrderedDict(sorted(list(rooms.items()), key=lambda s: s[0].lower()))
|
||||
return rooms
|
||||
|
||||
|
||||
class UiManager(object):
|
||||
def __init__(self, client, ui):
|
||||
self._client = client
|
||||
@ -1429,7 +1451,8 @@ class UiManager(object):
|
||||
self.__ui.showMessage(messageString)
|
||||
|
||||
def showMessage(self, message, noPlayer=False, noTimestamp=False, OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL):
|
||||
if not noPlayer: self.showOSDMessage(message, duration=constants.OSD_DURATION, OSDType=OSDType, mood=mood)
|
||||
if not noPlayer:
|
||||
self.showOSDMessage(message, duration=constants.OSD_DURATION, OSDType=OSDType, mood=mood)
|
||||
self.__ui.showMessage(message, noTimestamp)
|
||||
|
||||
def updateAutoPlayState(self, newState):
|
||||
@ -1487,6 +1510,7 @@ class UiManager(object):
|
||||
def drop(self):
|
||||
self.__ui.drop()
|
||||
|
||||
|
||||
class SyncplayPlaylist():
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
@ -1686,9 +1710,11 @@ class SyncplayPlaylist():
|
||||
def advancePlaylistCheck(self):
|
||||
position = self._client.getStoredPlayerPosition()
|
||||
currentLength = self._client.userlist.currentUser.file["duration"] if self._client.userlist.currentUser.file else 0
|
||||
if currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\
|
||||
and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD\
|
||||
and self.notJustChangedPlaylist():
|
||||
if (
|
||||
currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH and
|
||||
abs(position - currentLength) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD and
|
||||
self.notJustChangedPlaylist()
|
||||
):
|
||||
self.loadNextFileInPlaylist()
|
||||
|
||||
def notJustChangedPlaylist(self):
|
||||
@ -1759,6 +1785,7 @@ class SyncplayPlaylist():
|
||||
def _playlistBufferNeedsUpdating(self, newPlaylist):
|
||||
return self._previousPlaylist != self._playlist and self._playlist != newPlaylist
|
||||
|
||||
|
||||
class FileSwitchManager(object):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
from syncplay.ui.ConfigurationGetter import ConfigurationGetter
|
||||
|
||||
from syncplay import ui
|
||||
from syncplay.messages import getMessage
|
||||
from syncplay.ui.ConfigurationGetter import ConfigurationGetter
|
||||
|
||||
|
||||
class SyncplayClientManager(object):
|
||||
def run(self):
|
||||
@ -13,4 +15,3 @@ class SyncplayClientManager(object):
|
||||
syncplayClient.start(config['host'], config['port'])
|
||||
else:
|
||||
interface.showErrorMessage(getMessage("unable-to-start-client-error"), True)
|
||||
|
||||
|
||||
@ -16,7 +16,12 @@ SHOW_CONTACT_INFO = True # Displays dev contact details below list in GUI
|
||||
SHOW_TOOLTIPS = True
|
||||
WARN_ABOUT_MISSING_STRINGS = False # (If debug mode is enabled)
|
||||
FALLBACK_INITIAL_LANGUAGE = "en"
|
||||
FALLBACK_PUBLIC_SYNCPLAY_SERVERS = [['syncplay.pl:8995 (France)', 'syncplay.pl:8995'],['syncplay.pl:8996 (France)', 'syncplay.pl:8996'],['syncplay.pl:8997 (France)', 'syncplay.pl:8997'],['syncplay.pl:8998 (France)', 'syncplay.pl:8998'],['syncplay.pl:8999 (France)', 'syncplay.pl:8999']]
|
||||
FALLBACK_PUBLIC_SYNCPLAY_SERVERS = [
|
||||
['syncplay.pl:8995 (France)', 'syncplay.pl:8995'],
|
||||
['syncplay.pl:8996 (France)', 'syncplay.pl:8996'],
|
||||
['syncplay.pl:8997 (France)', 'syncplay.pl:8997'],
|
||||
['syncplay.pl:8998 (France)', 'syncplay.pl:8998'],
|
||||
['syncplay.pl:8999 (France)', 'syncplay.pl:8999']]
|
||||
PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH = 10 # Seconds
|
||||
PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD = 5 # Seconds (only triggered if file is paused, e.g. due to EOF)
|
||||
|
||||
@ -200,7 +205,11 @@ MPV_SYNCPLAYINTF_OPTIONS_TO_SEND = ["chatInputEnabled","chatInputFontFamily", "c
|
||||
"chatTopMargin", "chatLeftMargin", "chatBottomMargin", "chatDirectInput",
|
||||
"notificationTimeout", "alertTimeout", "chatTimeout", "chatOutputEnabled"]
|
||||
|
||||
MPV_SYNCPLAYINTF_CONSTANTS_TO_SEND = ["MaxChatMessageLength={}".format(MAX_CHAT_MESSAGE_LENGTH),"inputPromptStartCharacter={}".format(MPV_INPUT_PROMPT_START_CHARACTER),"inputPromptEndCharacter={}".format(MPV_INPUT_PROMPT_END_CHARACTER),"backslashSubstituteCharacter={}".format(MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER)]
|
||||
MPV_SYNCPLAYINTF_CONSTANTS_TO_SEND = [
|
||||
"MaxChatMessageLength={}".format(MAX_CHAT_MESSAGE_LENGTH),
|
||||
"inputPromptStartCharacter={}".format(MPV_INPUT_PROMPT_START_CHARACTER),
|
||||
"inputPromptEndCharacter={}".format(MPV_INPUT_PROMPT_END_CHARACTER),
|
||||
"backslashSubstituteCharacter={}".format(MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER)]
|
||||
# Note: Constants updated in client.py->checkForFeatureSupport
|
||||
MPV_SYNCPLAYINTF_LANGUAGE_TO_SEND = ["mpv-key-tab-hint", "mpv-key-hint", "alphakey-mode-warning-first-line", "alphakey-mode-warning-second-line"]
|
||||
VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek',
|
||||
|
||||
@ -14,6 +14,7 @@ messages = {
|
||||
"CURRENT": None
|
||||
}
|
||||
|
||||
|
||||
def getLanguages():
|
||||
langList = {}
|
||||
for lang in messages:
|
||||
@ -21,9 +22,11 @@ def getLanguages():
|
||||
langList[lang] = getMessage("LANGUAGE", lang)
|
||||
return langList
|
||||
|
||||
|
||||
def setLanguage(lang):
|
||||
messages["CURRENT"] = lang
|
||||
|
||||
|
||||
def getMissingStrings():
|
||||
missingStrings = ""
|
||||
for lang in messages:
|
||||
@ -37,6 +40,7 @@ def getMissingStrings():
|
||||
|
||||
return missingStrings
|
||||
|
||||
|
||||
def getInitialLanguage():
|
||||
import locale
|
||||
try:
|
||||
@ -47,9 +51,11 @@ def getInitialLanguage():
|
||||
initialLanguage = constants.FALLBACK_INITIAL_LANGUAGE
|
||||
return initialLanguage
|
||||
|
||||
|
||||
def isValidLanguage(language):
|
||||
return language in messages
|
||||
|
||||
|
||||
def getMessage(type_, locale=None):
|
||||
if constants.SHOW_TOOLTIPS == False:
|
||||
if "-tooltip" in type_:
|
||||
|
||||
@ -12,5 +12,6 @@ except ImportError:
|
||||
from syncplay.players.basePlayer import DummyPlayer
|
||||
MpcBePlayer = DummyPlayer
|
||||
|
||||
|
||||
def getAvailablePlayers():
|
||||
return [MPCHCAPIPlayer, MplayerPlayer, MpvPlayer, VlcPlayer, MpcBePlayer]
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
from syncplay import constants
|
||||
|
||||
|
||||
class BasePlayer(object):
|
||||
|
||||
'''
|
||||
@ -12,7 +14,9 @@ class BasePlayer(object):
|
||||
'''
|
||||
Display given message on player's OSD or similar means
|
||||
'''
|
||||
def displayMessage(self, message, duration = (constants.OSD_DURATION*1000), secondaryOSD=False, mood=constants.MESSAGE_NEUTRAL):
|
||||
def displayMessage(
|
||||
self, message, duration=(constants.OSD_DURATION*1000), secondaryOSD=False, mood=constants.MESSAGE_NEUTRAL
|
||||
):
|
||||
raise NotImplementedError()
|
||||
|
||||
'''
|
||||
@ -58,7 +62,6 @@ class BasePlayer(object):
|
||||
def openFile(self, filePath, resetPosition=False):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
'''
|
||||
@return: list of strings
|
||||
'''
|
||||
@ -107,6 +110,7 @@ class BasePlayer(object):
|
||||
def getPlayerPathErrors(playerPath, filePath):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class DummyPlayer(BasePlayer):
|
||||
|
||||
@staticmethod
|
||||
|
||||
@ -1,15 +1,18 @@
|
||||
#coding:utf8
|
||||
|
||||
import os.path
|
||||
import re
|
||||
import time
|
||||
import threading
|
||||
import _thread
|
||||
import win32con, win32api, win32gui, ctypes, ctypes.wintypes #@UnresolvedImport @UnusedImport
|
||||
from functools import wraps
|
||||
from syncplay.players.basePlayer import BasePlayer
|
||||
import re
|
||||
from syncplay.utils import retry
|
||||
|
||||
import win32con, win32api, win32gui, ctypes, ctypes.wintypes #@UnresolvedImport @UnusedImport
|
||||
|
||||
from syncplay import constants
|
||||
from syncplay.messages import getMessage
|
||||
import os.path
|
||||
from syncplay.players.basePlayer import BasePlayer
|
||||
from syncplay.utils import retry
|
||||
|
||||
|
||||
class MpcHcApi:
|
||||
def __init__(self):
|
||||
@ -49,7 +52,7 @@ class MpcHcApi:
|
||||
self.__listener.SendCommand(self.CMD_OPENFILE, filePath)
|
||||
|
||||
def isPaused(self):
|
||||
return self.playState != self.__MPC_PLAYSTATE.PS_PLAY and self.playState != None
|
||||
return self.playState != self.__MPC_PLAYSTATE.PS_PLAY and self.playState is not None
|
||||
|
||||
def askForVersion(self):
|
||||
self.__listener.SendCommand(self.CMD_GETVERSION)
|
||||
@ -103,7 +106,11 @@ class MpcHcApi:
|
||||
|
||||
elif cmd == self.CMD_STATE:
|
||||
self.loadState = int(value)
|
||||
fileNotReady = self.loadState == self.__MPC_LOADSTATE.MLS_CLOSING or self.loadState == self.__MPC_LOADSTATE.MLS_LOADING or self.loadState == self.__MPC_LOADSTATE.MLS_CLOSED
|
||||
fileNotReady = (
|
||||
self.loadState == self.__MPC_LOADSTATE.MLS_CLOSING or
|
||||
self.loadState == self.__MPC_LOADSTATE.MLS_LOADING or
|
||||
self.loadState == self.__MPC_LOADSTATE.MLS_CLOSED
|
||||
)
|
||||
if fileNotReady:
|
||||
self.playState = None
|
||||
self.__locks.fileReady.clear()
|
||||
@ -272,7 +279,6 @@ class MpcHcApi:
|
||||
self.locks.listenerStart.set()
|
||||
win32gui.PumpMessages()
|
||||
|
||||
|
||||
def OnCopyData(self, hwnd, msg, wparam, lparam):
|
||||
pCDS = ctypes.cast(lparam, self.__PCOPYDATASTRUCT)
|
||||
# print "API:\tin>\t 0x%X\t" % int(pCDS.contents.dwData), ctypes.wstring_at(pCDS.contents.lpData)
|
||||
@ -304,6 +310,7 @@ class MpcHcApi:
|
||||
('lpData', ctypes.c_void_p)
|
||||
]
|
||||
|
||||
|
||||
class MPCHCAPIPlayer(BasePlayer):
|
||||
speedSupported = False
|
||||
alertOSDSupported = False
|
||||
@ -400,7 +407,10 @@ class MPCHCAPIPlayer(BasePlayer):
|
||||
def openFile(self, filePath, resetPosition=False):
|
||||
self._mpcApi.openFile(filePath)
|
||||
|
||||
def displayMessage(self, message, duration = (constants.OSD_DURATION*1000), OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL):
|
||||
def displayMessage(
|
||||
self, message,
|
||||
duration=(constants.OSD_DURATION*1000), OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL
|
||||
):
|
||||
self._mpcApi.sendOsd(message, constants.MPC_OSD_POSITION, duration)
|
||||
|
||||
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
||||
@ -412,6 +422,7 @@ class MPCHCAPIPlayer(BasePlayer):
|
||||
self._mpcApi.pause()
|
||||
else:
|
||||
self._mpcApi.unpause()
|
||||
|
||||
def setFeatures(self, featureList):
|
||||
pass
|
||||
|
||||
@ -482,7 +493,10 @@ class MPCHCAPIPlayer(BasePlayer):
|
||||
|
||||
@staticmethod
|
||||
def getIconPath(path):
|
||||
if MPCHCAPIPlayer.getExpandedPath(path).lower().endswith('mpc-hc64.exe'.lower()) or MPCHCAPIPlayer.getExpandedPath(path).lower().endswith('mpc-hc64_nvo.exe'.lower()):
|
||||
if (
|
||||
MPCHCAPIPlayer.getExpandedPath(path).lower().endswith('mpc-hc64.exe'.lower()) or
|
||||
MPCHCAPIPlayer.getExpandedPath(path).lower().endswith('mpc-hc64_nvo.exe'.lower())
|
||||
):
|
||||
return constants.MPC64_ICONPATH
|
||||
else:
|
||||
return constants.MPC_ICONPATH
|
||||
@ -496,7 +510,11 @@ class MPCHCAPIPlayer(BasePlayer):
|
||||
@staticmethod
|
||||
def getExpandedPath(path):
|
||||
if os.path.isfile(path):
|
||||
if path.lower().endswith('mpc-hc.exe'.lower()) or path.lower().endswith('mpc-hcportable.exe'.lower()) or path.lower().endswith('mpc-hc64.exe'.lower()) or path.lower().endswith('mpc-hc64_nvo.exe'.lower()) or path.lower().endswith('mpc-hc_nvo.exe'.lower()):
|
||||
if (
|
||||
path.lower().endswith('mpc-hc.exe'.lower()) or path.lower().endswith('mpc-hcportable.exe'.lower()) or
|
||||
path.lower().endswith('mpc-hc64.exe'.lower()) or path.lower().endswith('mpc-hc64_nvo.exe'.lower()) or
|
||||
path.lower().endswith('mpc-hc_nvo.exe'.lower())
|
||||
):
|
||||
return path
|
||||
if os.path.isfile(path + "mpc-hc.exe"):
|
||||
path += "mpc-hc.exe"
|
||||
@ -528,4 +546,3 @@ class MPCHCAPIPlayer(BasePlayer):
|
||||
if os.path.isfile(path + "\\mpc-hc64_nvo.exe"):
|
||||
path += "\\mpc-hc64_nvo.exe"
|
||||
return path
|
||||
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
from syncplay import constants
|
||||
|
||||
import os.path
|
||||
|
||||
from syncplay import constants
|
||||
from syncplay.messages import getMessage
|
||||
from syncplay.players.mpc import MPCHCAPIPlayer
|
||||
|
||||
|
||||
class MpcBePlayer(MPCHCAPIPlayer):
|
||||
@staticmethod
|
||||
def run(client, playerPath, filePath, args):
|
||||
@ -30,7 +33,11 @@ class MpcBePlayer(MPCHCAPIPlayer):
|
||||
@staticmethod
|
||||
def getExpandedPath(path):
|
||||
if os.path.isfile(path):
|
||||
if path.lower().endswith('mpc-be.exe'.lower()) or path.lower().endswith('mpc-be64.exe'.lower() or path.lower().endswith('mpc-beportable.exe'.lower())):
|
||||
if (
|
||||
path.lower().endswith('mpc-be.exe'.lower()) or
|
||||
path.lower().endswith('mpc-be64.exe'.lower()) or
|
||||
path.lower().endswith('mpc-beportable.exe'.lower())
|
||||
):
|
||||
return path
|
||||
if os.path.isfile(path + "mpc-be.exe"):
|
||||
path += "mpc-be.exe"
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
# coding:utf8
|
||||
import subprocess
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from syncplay.players.basePlayer import BasePlayer
|
||||
|
||||
|
||||
from syncplay import constants, utils
|
||||
from syncplay.players.basePlayer import BasePlayer
|
||||
from syncplay.messages import getMessage
|
||||
import os, sys
|
||||
from syncplay.utils import isWindows
|
||||
|
||||
|
||||
class MplayerPlayer(BasePlayer):
|
||||
speedSupported = True
|
||||
customOpenDialog = False
|
||||
@ -90,15 +94,20 @@ class MplayerPlayer(BasePlayer):
|
||||
def _getProperty(self, property_):
|
||||
self._listener.sendLine("get_property {}".format(property_))
|
||||
|
||||
def displayMessage(self, message, duration=(constants.OSD_DURATION * 1000), OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL):
|
||||
def displayMessage(
|
||||
self, message,
|
||||
duration=(constants.OSD_DURATION * 1000), OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL
|
||||
):
|
||||
messageString = self._sanitizeText(message.replace("\\n", "<NEWLINE>")).replace("<NEWLINE>", "\\n")
|
||||
self._listener.sendLine('{} "{!s}" {} {}'.format(self.OSD_QUERY, messageString, duration, constants.MPLAYER_OSD_LEVEL))
|
||||
self._listener.sendLine('{} "{!s}" {} {}'.format(
|
||||
self.OSD_QUERY, messageString, duration, constants.MPLAYER_OSD_LEVEL))
|
||||
|
||||
def displayChatMessage(self, username, message):
|
||||
messageString = "<{}> {}".format(username, message)
|
||||
messageString = self._sanitizeText(messageString.replace("\\n", "<NEWLINE>")).replace("<NEWLINE>", "\\n")
|
||||
duration = int(constants.OSD_DURATION * 1000)
|
||||
self._listener.sendLine('{} "{!s}" {} {}'.format(self.OSD_QUERY, messageString, duration, constants.MPLAYER_OSD_LEVEL))
|
||||
self._listener.sendLine('{} "{!s}" {} {}'.format(
|
||||
self.OSD_QUERY, messageString, duration, constants.MPLAYER_OSD_LEVEL))
|
||||
|
||||
def setSpeed(self, value):
|
||||
self._setProperty('speed', "{:.2f}".format(value))
|
||||
@ -181,7 +190,13 @@ class MplayerPlayer(BasePlayer):
|
||||
line = line.replace("[term-msg] ", "") # -v workaround
|
||||
line = line.replace(" cplayer: ", "") # --msg-module workaround
|
||||
line = line.replace(" term-msg: ", "")
|
||||
if "Failed to get value of property" in line or "=(unavailable)" in line or line == "ANS_filename=" or line == "ANS_length=" or line == "ANS_path=":
|
||||
if (
|
||||
"Failed to get value of property" in line or
|
||||
"=(unavailable)" in line or
|
||||
line == "ANS_filename=" or
|
||||
line == "ANS_length=" or
|
||||
line == "ANS_path="
|
||||
):
|
||||
if "filename" in line:
|
||||
self._getFilename()
|
||||
elif "length" in line:
|
||||
@ -250,7 +265,7 @@ class MplayerPlayer(BasePlayer):
|
||||
|
||||
@staticmethod
|
||||
def isValidPlayerPath(path):
|
||||
if "mplayer" in path and MplayerPlayer.getExpandedPath(path) and not "mplayerc.exe" in path: # "mplayerc.exe" is Media Player Classic (not Home Cinema):
|
||||
if "mplayer" in path and MplayerPlayer.getExpandedPath(path) and "mplayerc.exe" not in path: # "mplayerc.exe" is Media Player Classic (not Home Cinema):
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -324,12 +339,15 @@ class MplayerPlayer(BasePlayer):
|
||||
if 'TERM' in env:
|
||||
del env['TERM']
|
||||
if filePath:
|
||||
self.__process = subprocess.Popen(call, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=self.__getCwd(filePath, env), env=env, bufsize=0)
|
||||
self.__process = subprocess.Popen(
|
||||
call, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
cwd=self.__getCwd(filePath, env), env=env, bufsize=0)
|
||||
else:
|
||||
self.__process = subprocess.Popen(call, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, env=env, bufsize=0)
|
||||
self.__process = subprocess.Popen(
|
||||
call, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
env=env, bufsize=0)
|
||||
threading.Thread.__init__(self, name="MPlayer Listener")
|
||||
|
||||
|
||||
def __getCwd(self, filePath, env):
|
||||
if not filePath:
|
||||
return None
|
||||
@ -365,8 +383,8 @@ class MplayerPlayer(BasePlayer):
|
||||
if command and command[:1] == "/":
|
||||
message = message[1:]
|
||||
else:
|
||||
self.__playerController.reactor.callFromThread(self.__playerController._client.ui.executeCommand,
|
||||
command)
|
||||
self.__playerController.reactor.callFromThread(
|
||||
self.__playerController._client.ui.executeCommand, command)
|
||||
return
|
||||
self.__playerController.reactor.callFromThread(self.__playerController._client.sendChat, message)
|
||||
|
||||
@ -378,11 +396,11 @@ class MplayerPlayer(BasePlayer):
|
||||
oldState = self.readyToSend
|
||||
self.readyToSend = newReadyState
|
||||
self.lastNotReadyTime = time.time() if newReadyState == False else None
|
||||
if self.readyToSend == True:
|
||||
if self.readyToSend:
|
||||
self.__playerController._client.ui.showDebugMessage("<mpv> Ready to send: True")
|
||||
else:
|
||||
self.__playerController._client.ui.showDebugMessage("<mpv> Ready to send: False")
|
||||
if self.readyToSend == True and oldState == False:
|
||||
if self.readyToSend and oldState == False:
|
||||
self.processSendQueue()
|
||||
|
||||
def checkForReadinessOverride(self):
|
||||
@ -391,7 +409,7 @@ class MplayerPlayer(BasePlayer):
|
||||
|
||||
def sendLine(self, line, notReadyAfterThis=None):
|
||||
self.checkForReadinessOverride()
|
||||
if self.readyToSend == False and "print_text ANS_pause" in line:
|
||||
if not self.readyToSend and "print_text ANS_pause" in line:
|
||||
self.__playerController._client.ui.showDebugMessage("<mpv> Not ready to get status update, so skipping")
|
||||
return
|
||||
try:
|
||||
@ -401,11 +419,13 @@ class MplayerPlayer(BasePlayer):
|
||||
if line.startswith(command):
|
||||
for itemID, deletionCandidate in enumerate(self.sendQueue):
|
||||
if deletionCandidate.startswith(command):
|
||||
self.__playerController._client.ui.showDebugMessage("<mpv> Remove duplicate (supersede): {}".format(self.sendQueue[itemID]))
|
||||
self.__playerController._client.ui.showDebugMessage(
|
||||
"<mpv> Remove duplicate (supersede): {}".format(self.sendQueue[itemID]))
|
||||
try:
|
||||
self.sendQueue.remove(self.sendQueue[itemID])
|
||||
except UnicodeWarning:
|
||||
self.__playerController._client.ui.showDebugMessage("<mpv> Unicode mismatch occured when trying to remove duplicate")
|
||||
self.__playerController._client.ui.showDebugMessage(
|
||||
"<mpv> Unicode mismatch occured when trying to remove duplicate")
|
||||
# TODO: Prevent this from being triggered
|
||||
pass
|
||||
break
|
||||
@ -415,7 +435,8 @@ class MplayerPlayer(BasePlayer):
|
||||
if line == command:
|
||||
for itemID, deletionCandidate in enumerate(self.sendQueue):
|
||||
if deletionCandidate == command:
|
||||
self.__playerController._client.ui.showDebugMessage("<mpv> Remove duplicate (delete both): {}".format(self.sendQueue[itemID]))
|
||||
self.__playerController._client.ui.showDebugMessage(
|
||||
"<mpv> Remove duplicate (delete both): {}".format(self.sendQueue[itemID]))
|
||||
self.__playerController._client.ui.showDebugMessage(self.sendQueue[itemID])
|
||||
return
|
||||
except:
|
||||
@ -428,7 +449,9 @@ class MplayerPlayer(BasePlayer):
|
||||
def processSendQueue(self):
|
||||
while self.sendQueue and self.readyToSend:
|
||||
if self.lastSendTime and time.time() - self.lastSendTime < constants.MPV_SENDMESSAGE_COOLDOWN_TIME:
|
||||
self.__playerController._client.ui.showDebugMessage("<mpv> Throttling message send, so sleeping for {}".format(constants.MPV_SENDMESSAGE_COOLDOWN_TIME))
|
||||
self.__playerController._client.ui.showDebugMessage(
|
||||
"<mpv> Throttling message send, so sleeping for {}".format(
|
||||
constants.MPV_SENDMESSAGE_COOLDOWN_TIME))
|
||||
time.sleep(constants.MPV_SENDMESSAGE_COOLDOWN_TIME)
|
||||
try:
|
||||
lineToSend = self.sendQueue.pop()
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
# coding:utf8
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
from syncplay import constants
|
||||
from syncplay.players.mplayer import MplayerPlayer
|
||||
from syncplay.messages import getMessage
|
||||
from syncplay import constants
|
||||
from syncplay.utils import isURL, findResourcePath
|
||||
import os, sys, time
|
||||
|
||||
|
||||
class MpvPlayer(MplayerPlayer):
|
||||
RE_VERSION = re.compile('.*mpv (\d+)\.(\d+)\.\d+.*')
|
||||
RE_VERSION = re.compile(r'.*mpv (\d+)\.(\d+)\.\d+.*')
|
||||
osdMessageSeparator = "\\n"
|
||||
osdMessageSeparator = "; " # TODO: Make conditional
|
||||
|
||||
@ -21,7 +25,9 @@ class MpvPlayer(MplayerPlayer):
|
||||
constants.MPV_NEW_VERSION = ver is None or int(ver.group(1)) > 0 or int(ver.group(2)) >= 6
|
||||
constants.MPV_OSC_VISIBILITY_CHANGE_VERSION = False if ver is None else int(ver.group(1)) > 0 or int(ver.group(2)) >= 28
|
||||
if not constants.MPV_OSC_VISIBILITY_CHANGE_VERSION:
|
||||
client.ui.showDebugMessage("This version of mpv is not known to be compatible with changing the OSC visibility. Please use mpv >=0.28.0.")
|
||||
client.ui.showDebugMessage(
|
||||
"This version of mpv is not known to be compatible with changing the OSC visibility. "
|
||||
"Please use mpv >=0.28.0.")
|
||||
if constants.MPV_NEW_VERSION:
|
||||
return NewMpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args)
|
||||
else:
|
||||
@ -77,6 +83,7 @@ class MpvPlayer(MplayerPlayer):
|
||||
def getPlayerPathErrors(playerPath, filePath):
|
||||
return None
|
||||
|
||||
|
||||
class OldMpvPlayer(MpvPlayer):
|
||||
POSITION_QUERY = 'time-pos'
|
||||
OSD_QUERY = 'show_text'
|
||||
@ -111,6 +118,7 @@ class OldMpvPlayer(MpvPlayer):
|
||||
self.setPaused(self._client.getGlobalPaused())
|
||||
self.setPosition(self._client.getGlobalPosition())
|
||||
|
||||
|
||||
class NewMpvPlayer(OldMpvPlayer):
|
||||
lastResetTime = None
|
||||
lastMPVPositionUpdate = None
|
||||
@ -122,14 +130,15 @@ class NewMpvPlayer(OldMpvPlayer):
|
||||
if not self._client._config["chatOutputEnabled"]:
|
||||
super(self.__class__, self).displayMessage(message=message, duration=duration, OSDType=OSDType, mood=mood)
|
||||
return
|
||||
messageString = self._sanitizeText(message.replace("\\n", "<NEWLINE>")).replace("\\\\",constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER).replace("<NEWLINE>", "\\n")
|
||||
messageString = self._sanitizeText(message.replace("\\n", "<NEWLINE>")).replace(
|
||||
"\\\\", constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER).replace("<NEWLINE>", "\\n")
|
||||
self._listener.sendLine('script-message-to syncplayintf {}-osd-{} "{}"'.format(OSDType, mood, messageString))
|
||||
|
||||
def displayChatMessage(self, username, message):
|
||||
if not self._client._config["chatOutputEnabled"]:
|
||||
super(self.__class__, self).displayChatMessage(username, message)
|
||||
return
|
||||
username = self._sanitizeText(username.replace("\\",constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER))
|
||||
username = self._sanitizeText(username.replace("\\", sconstants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER))
|
||||
message = self._sanitizeText(message.replace("\\", constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER))
|
||||
messageString = "<{}> {}".format(username, message)
|
||||
self._listener.sendLine('script-message-to syncplayintf chat "{}"'.format(messageString))
|
||||
@ -155,25 +164,34 @@ class NewMpvPlayer(OldMpvPlayer):
|
||||
self._listener.sendLine("print_text ""ANS_{}=${{{}}}""".format(property_, propertyID))
|
||||
|
||||
def getCalculatedPosition(self):
|
||||
if self.fileLoaded == False:
|
||||
self._client.ui.showDebugMessage("File not loaded so using GlobalPosition for getCalculatedPosition({})".format(self._client.getGlobalPosition()))
|
||||
if not self.fileLoaded:
|
||||
self._client.ui.showDebugMessage(
|
||||
"File not loaded so using GlobalPosition for getCalculatedPosition({})".format(
|
||||
self._client.getGlobalPosition()))
|
||||
return self._client.getGlobalPosition()
|
||||
|
||||
if self.lastMPVPositionUpdate is None:
|
||||
self._client.ui.showDebugMessage("MPV not updated position so using GlobalPosition for getCalculatedPosition ({})".format(self._client.getGlobalPosition()))
|
||||
self._client.ui.showDebugMessage(
|
||||
"MPV not updated position so using GlobalPosition for getCalculatedPosition ({})".format(
|
||||
self._client.getGlobalPosition()))
|
||||
return self._client.getGlobalPosition()
|
||||
|
||||
if self._recentlyReset():
|
||||
self._client.ui.showDebugMessage("Recently reset so using self.position for getCalculatedPosition ({})".format(self._position))
|
||||
self._client.ui.showDebugMessage(
|
||||
"Recently reset so using self.position for getCalculatedPosition ({})".format(
|
||||
self._position))
|
||||
return self._position
|
||||
|
||||
diff = time.time() - self.lastMPVPositionUpdate
|
||||
|
||||
if diff > constants.MPV_UNRESPONSIVE_THRESHOLD:
|
||||
self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("mpv-unresponsive-error").format(int(diff)), True)
|
||||
self.reactor.callFromThread(
|
||||
self._client.ui.showErrorMessage, getMessage("mpv-unresponsive-error").format(int(diff)), True)
|
||||
self.drop()
|
||||
if diff > constants.PLAYER_ASK_DELAY and not self._paused:
|
||||
self._client.ui.showDebugMessage("mpv did not response in time, so assuming position is {} ({}+{})".format(self._position + diff, self._position, diff))
|
||||
self._client.ui.showDebugMessage(
|
||||
"mpv did not response in time, so assuming position is {} ({}+{})".format(
|
||||
self._position + diff, self._position, diff))
|
||||
return self._position + diff
|
||||
else:
|
||||
return self._position
|
||||
@ -186,7 +204,8 @@ class NewMpvPlayer(OldMpvPlayer):
|
||||
elif self._fileIsLoaded() or (value < constants.MPV_NEWFILE_IGNORE_TIME and self._fileIsLoaded(ignoreDelay=True)):
|
||||
self._position = max(value, 0)
|
||||
else:
|
||||
self._client.ui.showDebugMessage("No file loaded so storing position as GlobalPosition ({})".format(self._client.getGlobalPosition()))
|
||||
self._client.ui.showDebugMessage(
|
||||
"No file loaded so storing position as GlobalPosition ({})".format(self._client.getGlobalPosition()))
|
||||
self._position = self._client.getGlobalPosition()
|
||||
|
||||
def _storePauseState(self, value):
|
||||
@ -205,7 +224,8 @@ class NewMpvPlayer(OldMpvPlayer):
|
||||
self._getPausedAndPosition()
|
||||
self._positionAsk.wait(constants.MPV_LOCK_WAIT_TIME)
|
||||
self._pausedAsk.wait(constants.MPV_LOCK_WAIT_TIME)
|
||||
self._client.updatePlayerStatus(self._paused if self.fileLoaded else self._client.getGlobalPaused(), self.getCalculatedPosition())
|
||||
self._client.updatePlayerStatus(
|
||||
self._paused if self.fileLoaded else self._client.getGlobalPaused(), self.getCalculatedPosition())
|
||||
|
||||
def _getPausedAndPosition(self):
|
||||
self._listener.sendLine("print_text ANS_pause=${pause}\r\nprint_text ANS_time-pos=${=time-pos}")
|
||||
@ -229,7 +249,8 @@ class NewMpvPlayer(OldMpvPlayer):
|
||||
|
||||
def setPosition(self, value):
|
||||
if value < constants.DO_NOT_RESET_POSITION_THRESHOLD and self._recentlyReset():
|
||||
self._client.ui.showDebugMessage("Did not seek as recently reset and {} below 'do not reset position' threshold".format(value))
|
||||
self._client.ui.showDebugMessage(
|
||||
"Did not seek as recently reset and {} below 'do not reset position' threshold".format(value))
|
||||
return
|
||||
super(self.__class__, self).setPosition(value)
|
||||
self.lastMPVPositionUpdate = time.time()
|
||||
@ -245,7 +266,7 @@ class NewMpvPlayer(OldMpvPlayer):
|
||||
self._client.ui.showDebugMessage("Want to set paused to {}".format(self._client.getGlobalPaused()))
|
||||
else:
|
||||
self._client.ui.showDebugMessage("Don't want to set paused to {}".format(self._client.getGlobalPaused()))
|
||||
if resetPosition == False:
|
||||
if not resetPosition:
|
||||
self.setPosition(self._client.getGlobalPosition())
|
||||
else:
|
||||
self._storePosition(0)
|
||||
@ -285,7 +306,14 @@ class NewMpvPlayer(OldMpvPlayer):
|
||||
self._listener.setReadyToSend(True)
|
||||
|
||||
def _setOSDPosition(self):
|
||||
if self._client._config['chatMoveOSD'] and (self._client._config['chatOutputEnabled'] or (self._client._config['chatInputEnabled'] and self._client._config['chatInputPosition'] == constants.INPUT_POSITION_TOP)):
|
||||
if (
|
||||
self._client._config['chatMoveOSD'] and (
|
||||
self._client._config['chatOutputEnabled'] or (
|
||||
self._client._config['chatInputEnabled'] and
|
||||
self._client._config['chatInputPosition'] == constants.INPUT_POSITION_TOP
|
||||
)
|
||||
)
|
||||
):
|
||||
self._setProperty("osd-align-y", "bottom")
|
||||
self._setProperty("osd-margin-y", int(self._client._config['chatOSDMargin']))
|
||||
|
||||
@ -309,9 +337,9 @@ class NewMpvPlayer(OldMpvPlayer):
|
||||
def _fileIsLoaded(self, ignoreDelay=False):
|
||||
if ignoreDelay:
|
||||
self._client.ui.showDebugMessage("Ignoring _fileIsLoaded MPV_NEWFILE delay")
|
||||
return True if self.fileLoaded else False
|
||||
return bool(self.fileLoaded)
|
||||
|
||||
if self.fileLoaded == True and self.lastLoadedTime != None and time.time() > (self.lastLoadedTime + constants.MPV_NEWFILE_IGNORE_TIME):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return (
|
||||
self.fileLoaded and self.lastLoadedTime is not None and
|
||||
time.time() > (self.lastLoadedTime + constants.MPV_NEWFILE_IGNORE_TIME)
|
||||
)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import syncplay.players
|
||||
|
||||
|
||||
class PlayerFactory(object):
|
||||
def __init__(self):
|
||||
self._players = syncplay.players.getAvailablePlayers()
|
||||
|
||||
@ -1,18 +1,24 @@
|
||||
import subprocess
|
||||
import re
|
||||
import threading
|
||||
from syncplay.players.basePlayer import BasePlayer
|
||||
from syncplay import constants, utils
|
||||
|
||||
import asynchat
|
||||
import asyncore
|
||||
import os
|
||||
import sys
|
||||
import random
|
||||
import re
|
||||
import socket
|
||||
import asynchat, asyncore
|
||||
import urllib.request, urllib.parse, urllib.error
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
from syncplay import constants, utils
|
||||
from syncplay.messages import getMessage
|
||||
from syncplay.players.basePlayer import BasePlayer
|
||||
from syncplay.utils import isBSD, isLinux, isWindows, isMacOS
|
||||
|
||||
|
||||
class VlcPlayer(BasePlayer):
|
||||
speedSupported = True
|
||||
customOpenDialog = False
|
||||
@ -47,7 +53,8 @@ class VlcPlayer(BasePlayer):
|
||||
if self.radixChar == "" or self.radixChar == "1" or self.radixChar == "5":
|
||||
raise ValueError
|
||||
except:
|
||||
self._client.ui.showErrorMessage("Failed to determine locale. As a fallback Syncplay is using the following radix character: \".\".")
|
||||
self._client.ui.showErrorMessage(
|
||||
"Failed to determine locale. As a fallback Syncplay is using the following radix character: \".\".")
|
||||
self.radixChar = "."
|
||||
|
||||
self._durationAsk = threading.Event()
|
||||
@ -110,7 +117,8 @@ class VlcPlayer(BasePlayer):
|
||||
return self._client.getGlobalPosition()
|
||||
diff = time.time() - self._lastVLCPositionUpdate
|
||||
if diff > constants.PLAYER_ASK_DELAY and not self._paused:
|
||||
self._client.ui.showDebugMessage("VLC did not response in time, so assuming position is {} ({}+{})".format(self._position + diff, self._position, diff))
|
||||
self._client.ui.showDebugMessage("VLC did not response in time, so assuming position is {} ({}+{})".format(
|
||||
self._position + diff, self._position, diff))
|
||||
if diff > constants.VLC_LATENCY_ERROR_THRESHOLD:
|
||||
if not self.shownVLCLatencyError or constants.DEBUG_MODE:
|
||||
self._client.ui.showErrorMessage(getMessage("media-player-latency-warning").format(int(diff)))
|
||||
@ -119,7 +127,10 @@ class VlcPlayer(BasePlayer):
|
||||
else:
|
||||
return self._position
|
||||
|
||||
def displayMessage(self, message, duration=constants.OSD_DURATION * 1000, OSDType=constants.OSD_DURATION, mood=constants.MESSAGE_NEUTRAL):
|
||||
def displayMessage(
|
||||
self, message,
|
||||
duration=constants.OSD_DURATION * 1000, OSDType=constants.OSD_DURATION, mood=constants.MESSAGE_NEUTRAL
|
||||
):
|
||||
duration /= 1000
|
||||
if OSDType != constants.OSD_ALERT:
|
||||
self._listener.sendLine('display-osd: {}, {}, {}'.format('top-right', duration, message))
|
||||
@ -212,21 +223,25 @@ class VlcPlayer(BasePlayer):
|
||||
self._duration = float(value.replace(",", "."))
|
||||
self._durationAsk.set()
|
||||
elif name == "playstate":
|
||||
self._paused = bool(value != 'playing') if(value != "no-input" and self._filechanged == False) else self._client.getGlobalPaused()
|
||||
self._paused = bool(value != 'playing') if (value != "no-input" and not self._filechanged) else self._client.getGlobalPaused()
|
||||
diff = time.time() - self._lastVLCPositionUpdate if self._lastVLCPositionUpdate else 0
|
||||
if self._paused == False \
|
||||
and self._position == self._previousPreviousPosition \
|
||||
and self._previousPosition == self._position \
|
||||
and self._duration > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH \
|
||||
and (self._duration - self._position) < constants.VLC_EOF_DURATION_THRESHOLD \
|
||||
and diff > constants.VLC_LATENCY_ERROR_THRESHOLD:
|
||||
if (
|
||||
self._paused == False and
|
||||
self._position == self._previousPreviousPosition and
|
||||
self._previousPosition == self._position and
|
||||
self._duration > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH and
|
||||
(self._duration - self._position) < constants.VLC_EOF_DURATION_THRESHOLD and
|
||||
diff > constants.VLC_LATENCY_ERROR_THRESHOLD
|
||||
):
|
||||
self._client.ui.showDebugMessage("Treating 'playing' response as 'paused' due to VLC EOF bug")
|
||||
self.setPaused(True)
|
||||
self._pausedAsk.set()
|
||||
elif name == "position":
|
||||
newPosition = float(value.replace(",", ".")) if (value != "no-input" and self._filechanged == False) else self._client.getGlobalPosition()
|
||||
if newPosition == self._previousPosition and newPosition != self._duration and not self._paused:
|
||||
self._client.ui.showDebugMessage("Not considering position {} duplicate as new time because of VLC time precision bug".format(newPosition))
|
||||
newPosition = float(value.replace(",", ".")) if (value != "no-input" and not self._filechanged) else self._client.getGlobalPosition()
|
||||
if newPosition == self._previousPosition and newPosition != self._duration and self._paused is False:
|
||||
self._client.ui.showDebugMessage(
|
||||
"Not considering position {} duplicate as new time because of VLC time precision bug".format(
|
||||
newPosition))
|
||||
self._previousPreviousPosition = self._previousPosition
|
||||
self._previousPosition = self._position
|
||||
self._positionAsk.set()
|
||||
@ -326,6 +341,7 @@ class VlcPlayer(BasePlayer):
|
||||
call.append(filePath)
|
||||
else:
|
||||
call.append(self.__playerController.getMRL(filePath))
|
||||
|
||||
def _usevlcintf(vlcIntfPath, vlcIntfUserPath):
|
||||
vlcSyncplayInterfacePath = vlcIntfPath + "syncplay.lua"
|
||||
if not os.path.isfile(vlcSyncplayInterfacePath):
|
||||
@ -347,7 +363,8 @@ class VlcPlayer(BasePlayer):
|
||||
playerController.vlcIntfUserPath = os.path.join(os.getenv('HOME', '.'), ".local/share/vlc/lua/intf/")
|
||||
elif isMacOS():
|
||||
playerController.vlcIntfPath = "/Applications/VLC.app/Contents/MacOS/share/lua/intf/"
|
||||
playerController.vlcIntfUserPath = os.path.join(os.getenv('HOME', '.'), "Library/Application Support/org.videolan.vlc/lua/intf/")
|
||||
playerController.vlcIntfUserPath = os.path.join(
|
||||
os.getenv('HOME', '.'), "Library/Application Support/org.videolan.vlc/lua/intf/")
|
||||
elif isBSD():
|
||||
# *BSD ports/pkgs install to /usr/local by default.
|
||||
# This should also work for all the other BSDs, such as OpenBSD or DragonFly.
|
||||
@ -358,14 +375,17 @@ class VlcPlayer(BasePlayer):
|
||||
playerController.vlcIntfUserPath = os.path.join(os.getenv('APPDATA', '.'), "VLC\\lua\\intf\\")
|
||||
playerController.vlcModulePath = playerController.vlcIntfPath + "modules/?.luac"
|
||||
if _usevlcintf(playerController.vlcIntfPath, playerController.vlcIntfUserPath):
|
||||
playerController.SLAVE_ARGS.append('--lua-config=syncplay={{port=\"{}\"}}'.format(str(playerController.vlcport)))
|
||||
playerController.SLAVE_ARGS.append(
|
||||
'--lua-config=syncplay={{port=\"{}\"}}'.format(str(playerController.vlcport)))
|
||||
else:
|
||||
if isLinux():
|
||||
playerController.vlcDataPath = "/usr/lib/syncplay/resources"
|
||||
else:
|
||||
playerController.vlcDataPath = utils.findWorkingDir() + "\\resources"
|
||||
playerController.SLAVE_ARGS.append('--data-path={}'.format(playerController.vlcDataPath))
|
||||
playerController.SLAVE_ARGS.append('--lua-config=syncplay={{modulepath=\"{}\",port=\"{}\"}}'.format(playerController.vlcModulePath, str(playerController.vlcport)))
|
||||
playerController.SLAVE_ARGS.append(
|
||||
'--lua-config=syncplay={{modulepath=\"{}\",port=\"{}\"}}'.format(
|
||||
playerController.vlcModulePath, str(playerController.vlcport)))
|
||||
|
||||
call.extend(playerController.SLAVE_ARGS)
|
||||
if args:
|
||||
@ -376,11 +396,15 @@ class VlcPlayer(BasePlayer):
|
||||
self._vlcVersion = None
|
||||
|
||||
if self.oldIntfVersion:
|
||||
self.__playerController.drop(getMessage("vlc-interface-version-mismatch").format(self.oldIntfVersion,constants.VLC_INTERFACE_MIN_VERSION))
|
||||
self.__playerController.drop(
|
||||
getMessage("vlc-interface-version-mismatch").format(
|
||||
self.oldIntfVersion, constants.VLC_INTERFACE_MIN_VERSION))
|
||||
|
||||
else:
|
||||
if isWindows() and getattr(sys, 'frozen', '') and getattr(sys, '_MEIPASS', '') is not None: # Needed for pyinstaller --onefile bundle
|
||||
self.__process = subprocess.Popen(call, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=False, creationflags=0x08000000)
|
||||
self.__process = subprocess.Popen(
|
||||
call, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE,
|
||||
shell=False, creationflags=0x08000000)
|
||||
else:
|
||||
self.__process = subprocess.Popen(call, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
self.timeVLCLaunched = time.time()
|
||||
@ -416,10 +440,7 @@ class VlcPlayer(BasePlayer):
|
||||
self._sendingData = threading.Lock()
|
||||
|
||||
def _shouldListenForSTDOUT(self):
|
||||
if isWindows():
|
||||
return False # Due to VLC3 not using STDOUT/STDERR
|
||||
else:
|
||||
return True
|
||||
return not isWindows()
|
||||
|
||||
def initiate_send(self):
|
||||
with self._sendingData:
|
||||
@ -462,7 +483,6 @@ class VlcPlayer(BasePlayer):
|
||||
break
|
||||
out.close()
|
||||
|
||||
|
||||
def found_terminator(self):
|
||||
self.vlcHasResponded = True
|
||||
self.__playerController.lineReceived(b"".join(self._ibuffer))
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
# coding:utf8
|
||||
from twisted.protocols.basic import LineReceiver
|
||||
import json
|
||||
import syncplay
|
||||
from functools import wraps
|
||||
import time
|
||||
from syncplay.messages import getMessage
|
||||
from functools import wraps
|
||||
|
||||
from twisted.protocols.basic import LineReceiver
|
||||
|
||||
import syncplay
|
||||
from syncplay.constants import PING_MOVING_AVERAGE_WEIGHT, CONTROLLED_ROOMS_MIN_VERSION, USER_READY_MIN_VERSION, SHARED_PLAYLIST_MIN_VERSION, CHAT_MIN_VERSION
|
||||
from syncplay.messages import getMessage
|
||||
from syncplay.utils import meetsMinVersion
|
||||
|
||||
|
||||
class JSONCommandProtocol(LineReceiver):
|
||||
def handleMessages(self, messages):
|
||||
for message in messages.items():
|
||||
@ -102,9 +105,11 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
hello = {}
|
||||
hello["username"] = self._client.getUsername()
|
||||
password = self._client.getPassword()
|
||||
if password: hello["password"] = password
|
||||
if password:
|
||||
hello["password"] = password
|
||||
room = self._client.getRoom()
|
||||
if room: hello["room"] = {"name" :room}
|
||||
if room:
|
||||
hello["room"] = {"name": room}
|
||||
hello["version"] = "1.2.255" # Used so newer clients work on 1.2.X server
|
||||
hello["realversion"] = syncplay.version
|
||||
hello["features"] = self._client.getFeatures()
|
||||
@ -160,7 +165,8 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
def sendRoomSetting(self, roomName, password=None):
|
||||
setting = {}
|
||||
setting["name"] = roomName
|
||||
if password: setting["password"] = password
|
||||
if password:
|
||||
setting["password"] = password
|
||||
self.sendSet({"room": setting})
|
||||
|
||||
def sendFileSetting(self, file_):
|
||||
@ -231,7 +237,8 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
state["playstate"] = {}
|
||||
state["playstate"]["position"] = position
|
||||
state["playstate"]["paused"] = paused
|
||||
if doSeek: state["playstate"]["doSeek"] = doSeek
|
||||
if doSeek:
|
||||
state["playstate"]["doSeek"] = doSeek
|
||||
state["ping"] = {}
|
||||
if latencyCalculation:
|
||||
state["ping"]["latencyCalculation"] = latencyCalculation
|
||||
@ -255,6 +262,7 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
"password": password
|
||||
}
|
||||
})
|
||||
|
||||
def handleChat(self, message):
|
||||
username = message['username']
|
||||
userMessage = message['message']
|
||||
@ -282,13 +290,13 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
def handleError(self, error):
|
||||
self.dropWithError(error["message"])
|
||||
|
||||
def sendError(self, message):
|
||||
self.sendMessage({"Error": {"message": message}})
|
||||
|
||||
|
||||
class SyncServerProtocol(JSONCommandProtocol):
|
||||
def __init__(self, factory):
|
||||
self._factory = factory
|
||||
@ -410,7 +418,8 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
hello["username"] = username
|
||||
userIp = self.transport.getPeer().host
|
||||
room = self._watcher.getRoom()
|
||||
if room: hello["room"] = {"name": room.getName()}
|
||||
if room:
|
||||
hello["room"] = {"name": room.getName()}
|
||||
hello["version"] = clientVersion # Used so 1.2.X client works on newer server
|
||||
hello["realversion"] = syncplay.version
|
||||
hello["motd"] = self._factory.getMotd(userIp, username, room, clientVersion)
|
||||
@ -461,7 +470,6 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
def sendSetReady(self, username, isReady, manuallyInitiated=True):
|
||||
self.sendSet({
|
||||
"ready": {
|
||||
@ -556,7 +564,6 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
if self.serverIgnoringOnTheFly == 0 or forced:
|
||||
self.sendMessage({"State": state})
|
||||
|
||||
|
||||
def _extractStatePlaystateArguments(self, state):
|
||||
position = state["playstate"]["position"] if "position" in state["playstate"] else 0
|
||||
paused = state["playstate"]["paused"] if "paused" in state["playstate"] else None
|
||||
@ -590,6 +597,7 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
def sendError(self, message):
|
||||
self.sendMessage({"Error": {"message": message}})
|
||||
|
||||
|
||||
class PingService(object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@ -1,20 +1,26 @@
|
||||
|
||||
import argparse
|
||||
import codecs
|
||||
import hashlib
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
from string import Template
|
||||
|
||||
from twisted.internet import task, reactor
|
||||
from twisted.internet.protocol import Factory
|
||||
|
||||
import syncplay
|
||||
from syncplay.protocols import SyncServerProtocol
|
||||
import time
|
||||
from syncplay import constants
|
||||
from syncplay.messages import getMessage
|
||||
import codecs
|
||||
import os
|
||||
from string import Template
|
||||
import argparse
|
||||
from syncplay.protocols import SyncServerProtocol
|
||||
from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion, playlistIsValid, truncateText
|
||||
|
||||
|
||||
class SyncFactory(Factory):
|
||||
def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False,disableChat=False, maxChatMessageLength=constants.MAX_CHAT_MESSAGE_LENGTH, maxUsernameLength=constants.MAX_USERNAME_LENGTH):
|
||||
def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None,
|
||||
disableReady=False, disableChat=False, maxChatMessageLength=constants.MAX_CHAT_MESSAGE_LENGTH,
|
||||
maxUsernameLength=constants.MAX_USERNAME_LENGTH):
|
||||
self.isolateRooms = isolateRooms
|
||||
print(getMessage("welcome-server-notification").format(syncplay.version))
|
||||
if password:
|
||||
@ -179,6 +185,7 @@ class SyncFactory(Factory):
|
||||
else:
|
||||
watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex())
|
||||
|
||||
|
||||
class RoomManager(object):
|
||||
def __init__(self):
|
||||
self._rooms = {}
|
||||
@ -341,6 +348,7 @@ class Room(object):
|
||||
def getPlaylistIndex(self):
|
||||
return self._playlistIndex
|
||||
|
||||
|
||||
class ControlledRoom(Room):
|
||||
def __init__(self, name):
|
||||
Room.__init__(self, name)
|
||||
@ -389,6 +397,7 @@ class ControlledRoom(Room):
|
||||
def getControllers(self):
|
||||
return self._controllers
|
||||
|
||||
|
||||
class Watcher(object):
|
||||
def __init__(self, server, connector, name):
|
||||
self._ready = None
|
||||
@ -531,6 +540,7 @@ class Watcher(object):
|
||||
return RoomPasswordProvider.isControlledRoom(self._room.getName()) \
|
||||
and self._room.canControl(self)
|
||||
|
||||
|
||||
class ConfigurationGetter(object):
|
||||
def getConfiguration(self):
|
||||
self._prepareArgParser()
|
||||
@ -540,7 +550,8 @@ class ConfigurationGetter(object):
|
||||
return args
|
||||
|
||||
def _prepareArgParser(self):
|
||||
self._argparser = argparse.ArgumentParser(description=getMessage("server-argument-description"),
|
||||
self._argparser = argparse.ArgumentParser(
|
||||
description=getMessage("server-argument-description"),
|
||||
epilog=getMessage("server-argument-epilog"))
|
||||
self._argparser.add_argument('--port', metavar='port', type=str, nargs='?', help=getMessage("server-port-argument"))
|
||||
self._argparser.add_argument('--password', metavar='password', type=str, nargs='?', help=getMessage("server-password-argument"))
|
||||
|
||||
@ -1,19 +1,23 @@
|
||||
from configparser import SafeConfigParser, DEFAULTSECT
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import codecs
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import ast
|
||||
from configparser import SafeConfigParser, DEFAULTSECT
|
||||
|
||||
from syncplay import constants, utils, version, milestone
|
||||
from syncplay.messages import getMessage, setLanguage, isValidLanguage
|
||||
from syncplay.players.playerFactory import PlayerFactory
|
||||
from syncplay.utils import isMacOS
|
||||
import codecs
|
||||
import re
|
||||
|
||||
|
||||
class InvalidConfigValue(Exception):
|
||||
def __init__(self, message):
|
||||
Exception.__init__(self, message)
|
||||
|
||||
|
||||
class ConfigurationGetter(object):
|
||||
def __init__(self):
|
||||
self._config = {
|
||||
@ -175,7 +179,8 @@ class ConfigurationGetter(object):
|
||||
|
||||
self._iniStructure = {
|
||||
"server_data": ["host", "port", "password"],
|
||||
"client_settings": ["name", "room", "playerPath",
|
||||
"client_settings": [
|
||||
"name", "room", "playerPath",
|
||||
"perPlayerArguments", "slowdownThreshold",
|
||||
"rewindThreshold", "fastforwardThreshold",
|
||||
"slowOnDesync", "rewindOnDesync",
|
||||
@ -187,7 +192,8 @@ class ConfigurationGetter(object):
|
||||
"sharedPlaylistEnabled", "loopAtEndOfPlaylist",
|
||||
"loopSingleFiles",
|
||||
"onlySwitchToTrustedDomains", "trustedDomains", "publicServers"],
|
||||
"gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD",
|
||||
"gui": [
|
||||
"showOSD", "showOSDWarnings", "showSlowdownOSD",
|
||||
"showDifferentRoomOSD", "showSameRoomOSD",
|
||||
"showNonControllerOSD", "showDurationNotification",
|
||||
"chatInputEnabled", "chatInputFontUnderline",
|
||||
@ -202,7 +208,8 @@ class ConfigurationGetter(object):
|
||||
"chatMoveOSD", "chatOSDMargin",
|
||||
"notificationTimeout", "alertTimeout",
|
||||
"chatTimeout", "chatOutputEnabled"],
|
||||
"general": ["language", "checkForUpdatesAutomatically",
|
||||
"general": [
|
||||
"language", "checkForUpdatesAutomatically",
|
||||
"lastCheckedForUpdates"]
|
||||
}
|
||||
|
||||
@ -224,7 +231,7 @@ class ConfigurationGetter(object):
|
||||
try:
|
||||
if varToTest == "" or varToTest is None:
|
||||
return False
|
||||
if str(varToTest).isdigit() == False:
|
||||
if not str(varToTest).isdigit():
|
||||
return False
|
||||
varToTest = int(varToTest)
|
||||
if varToTest > 65535 or varToTest < 1:
|
||||
@ -232,6 +239,7 @@ class ConfigurationGetter(object):
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
for key in self._boolean:
|
||||
if self._config[key] == "True":
|
||||
self._config[key] = True
|
||||
@ -269,13 +277,14 @@ class ConfigurationGetter(object):
|
||||
self._config["playerClass"] = player
|
||||
else:
|
||||
raise InvalidConfigValue(getMessage("player-path-config-error"))
|
||||
playerPathErrors = player.getPlayerPathErrors(self._config["playerPath"], self._config['file'] if self._config['file'] else None)
|
||||
playerPathErrors = player.getPlayerPathErrors(
|
||||
self._config["playerPath"], self._config['file'] if self._config['file'] else None)
|
||||
if playerPathErrors:
|
||||
raise InvalidConfigValue(playerPathErrors)
|
||||
elif key == "host":
|
||||
self._config["host"], self._config["port"] = self._splitPortAndHost(self._config["host"])
|
||||
hostNotValid = (self._config["host"] == "" or self._config["host"] is None)
|
||||
portNotValid = (_isPortValid(self._config["port"]) == False)
|
||||
portNotValid = (not _isPortValid(self._config["port"]))
|
||||
if hostNotValid:
|
||||
raise InvalidConfigValue(getMessage("no-hostname-config-error"))
|
||||
elif portNotValid:
|
||||
@ -408,7 +417,6 @@ class ConfigurationGetter(object):
|
||||
if changed:
|
||||
parser.write(codecs.open(iniPath, "wb", "utf_8_sig"))
|
||||
|
||||
|
||||
def _forceGuiPrompt(self):
|
||||
from syncplay.ui.GuiConfiguration import GuiConfiguration
|
||||
try:
|
||||
@ -452,7 +460,8 @@ class ConfigurationGetter(object):
|
||||
#
|
||||
if self._config['language']:
|
||||
setLanguage(self._config['language'])
|
||||
self._argparser = argparse.ArgumentParser(description=getMessage("argument-description"),
|
||||
self._argparser = argparse.ArgumentParser(
|
||||
description=getMessage("argument-description"),
|
||||
epilog=getMessage("argument-epilog"))
|
||||
self._argparser.add_argument('--no-gui', action='store_true', help=getMessage("nogui-argument"))
|
||||
self._argparser.add_argument('-a', '--host', metavar='hostname', type=str, help=getMessage("host-argument"))
|
||||
@ -514,6 +523,7 @@ class ConfigurationGetter(object):
|
||||
self._saveConfig(path)
|
||||
self._config = backup
|
||||
|
||||
|
||||
class SafeConfigParserUnicode(SafeConfigParser):
|
||||
def write(self, fp):
|
||||
"""Write an .ini-format representation of the configuration state."""
|
||||
|
||||
@ -1,19 +1,24 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
from syncplay import constants
|
||||
from syncplay import utils
|
||||
from syncplay.messages import getMessage, getLanguages, setLanguage, getInitialLanguage
|
||||
from syncplay.players.playerFactory import PlayerFactory
|
||||
from syncplay.utils import isBSD, isLinux, isMacOS, isWindows
|
||||
from syncplay.utils import resourcespath, posixresourcespath
|
||||
|
||||
from syncplay.vendor.Qt import QtCore, QtWidgets, QtGui, __binding__, IsPySide, IsPySide2
|
||||
from syncplay.vendor.Qt.QtCore import Qt, QSettings, QCoreApplication, QSize, QPoint, QUrl, QLine, QEventLoop, Signal
|
||||
from syncplay.vendor.Qt.QtWidgets import QApplication, QLineEdit, QLabel, QCheckBox, QButtonGroup, QRadioButton, QDoubleSpinBox, QPlainTextEdit
|
||||
from syncplay.vendor.Qt.QtGui import QCursor, QIcon, QImage, QDesktopServices
|
||||
if IsPySide2:
|
||||
from PySide2.QtCore import QStandardPaths
|
||||
from syncplay.players.playerFactory import PlayerFactory
|
||||
from datetime import datetime
|
||||
from syncplay import utils
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
from syncplay.messages import getMessage, getLanguages, setLanguage, getInitialLanguage
|
||||
from syncplay import constants
|
||||
from syncplay.utils import isBSD, isLinux, isMacOS, isWindows
|
||||
from syncplay.utils import resourcespath, posixresourcespath
|
||||
|
||||
|
||||
class GuiConfiguration:
|
||||
def __init__(self, config, error=None, defaultConfig=None):
|
||||
self.defaultConfig = defaultConfig
|
||||
@ -82,8 +87,10 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
|
||||
def automaticUpdatePromptCheck(self):
|
||||
if self.automaticupdatesCheckbox.checkState() == Qt.PartiallyChecked:
|
||||
reply = QtWidgets.QMessageBox.question(self, "Syncplay",
|
||||
getMessage("promptforupdate-label"), QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No)
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self, "Syncplay",
|
||||
getMessage("promptforupdate-label"),
|
||||
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No)
|
||||
if reply == QtWidgets.QMessageBox.Yes:
|
||||
self.automaticupdatesCheckbox.setChecked(True)
|
||||
else:
|
||||
@ -91,7 +98,7 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
|
||||
def moreToggled(self):
|
||||
self.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
|
||||
if self.moreToggling == False:
|
||||
if self.moreToggling is False:
|
||||
self.moreToggling = True
|
||||
|
||||
if self.showmoreCheckbox.isChecked():
|
||||
@ -154,7 +161,7 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
settings.endGroup()
|
||||
foundpath = ""
|
||||
|
||||
if playerpath != None and playerpath != "":
|
||||
if playerpath is not None and playerpath != "":
|
||||
if utils.isURL(playerpath):
|
||||
foundpath = playerpath
|
||||
self.executablepathCombobox.addItem(foundpath)
|
||||
@ -162,7 +169,7 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
else:
|
||||
if not os.path.isfile(playerpath):
|
||||
expandedpath = PlayerFactory().getExpandedPlayerPathByPath(playerpath)
|
||||
if expandedpath != None and os.path.isfile(expandedpath):
|
||||
if expandedpath is not None and os.path.isfile(expandedpath):
|
||||
playerpath = expandedpath
|
||||
|
||||
if os.path.isfile(playerpath):
|
||||
@ -252,7 +259,8 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
elif isBSD():
|
||||
defaultdirectory = "/usr/local/bin"
|
||||
|
||||
fileName, filtr = QtWidgets.QFileDialog.getOpenFileName(self,
|
||||
fileName, filtr = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Browse for media player executable",
|
||||
defaultdirectory,
|
||||
browserfilter, "", options)
|
||||
@ -387,7 +395,8 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
else:
|
||||
defaultdirectory = ""
|
||||
browserfilter = "All files (*)"
|
||||
fileName, filtr = QtWidgets.QFileDialog.getOpenFileName(self, "Browse for media files", defaultdirectory,
|
||||
fileName, filtr = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self, "Browse for media files", defaultdirectory,
|
||||
browserfilter, "", options)
|
||||
if fileName:
|
||||
self.mediapathTextbox.setText(os.path.normpath(fileName))
|
||||
@ -542,10 +551,10 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
config = self.config
|
||||
playerpaths = self.playerpaths
|
||||
error = self.error
|
||||
if self.datacleared == True:
|
||||
if self.datacleared:
|
||||
error = constants.ERROR_MESSAGE_MARKER + "{}".format(getMessage("gui-data-cleared-notification"))
|
||||
self.error = error
|
||||
if config['host'] == None:
|
||||
if config['host'] is None:
|
||||
host = ""
|
||||
elif ":" in config['host']:
|
||||
host = config['host']
|
||||
@ -566,7 +575,7 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
serverAddressPort = publicServer[1]
|
||||
self.hostCombobox.addItem(serverAddressPort)
|
||||
self.hostCombobox.setItemData(i, serverTitle, Qt.ToolTipRole)
|
||||
if not serverAddressPort in self.publicServerAddresses:
|
||||
if serverAddressPort not in self.publicServerAddresses:
|
||||
self.publicServerAddresses.append(serverAddressPort)
|
||||
i += 1
|
||||
self.hostCombobox.setEditable(True)
|
||||
@ -1224,7 +1233,7 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
|
||||
def populateEmptyServerList(self):
|
||||
if self.publicServers is None:
|
||||
if self.config["checkForUpdatesAutomatically"] == True:
|
||||
if self.config["checkForUpdatesAutomatically"]:
|
||||
self.updateServerList()
|
||||
else:
|
||||
currentServer = self.hostCombobox.currentText()
|
||||
@ -1264,7 +1273,7 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
self._playerProbeThread.done.connect(self._updateExecutableIcon)
|
||||
self._playerProbeThread.start()
|
||||
|
||||
if self.config['clearGUIData'] == True:
|
||||
if self.config['clearGUIData']:
|
||||
self.config['clearGUIData'] = False
|
||||
self.clearGUIData()
|
||||
|
||||
@ -1305,7 +1314,7 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
self.addBottomLayout()
|
||||
self.updatePasswordVisibilty()
|
||||
|
||||
if self.getMoreState() == False:
|
||||
if self.getMoreState() is False:
|
||||
self.tabListFrame.hide()
|
||||
self.resetButton.hide()
|
||||
self.playerargsTextbox.hide()
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import os
|
||||
|
||||
if "QT_PREFERRED_BINDING" not in os.environ:
|
||||
os.environ["QT_PREFERRED_BINDING"] = os.pathsep.join(
|
||||
["PySide2", "PySide", "PyQt5", "PyQt4"]
|
||||
@ -10,8 +11,9 @@ except ImportError:
|
||||
pass
|
||||
from syncplay.ui.consoleUI import ConsoleUI
|
||||
|
||||
|
||||
def getUi(graphical=True):
|
||||
if graphical: #TODO: Add graphical ui
|
||||
if graphical:
|
||||
ui = GraphicalUI()
|
||||
else:
|
||||
ui = ConsoleUI()
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
import syncplay
|
||||
import re
|
||||
from syncplay import utils
|
||||
from syncplay import constants
|
||||
from syncplay import utils
|
||||
from syncplay.messages import getMessage
|
||||
import sys
|
||||
from syncplay.utils import formatTime
|
||||
|
||||
|
||||
class ConsoleUI(threading.Thread):
|
||||
def __init__(self):
|
||||
self.promptMode = threading.Event()
|
||||
@ -157,7 +159,7 @@ class ConsoleUI(threading.Thread):
|
||||
self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused())
|
||||
elif command.group('command') in constants.COMMANDS_ROOM:
|
||||
room = command.group('parameter')
|
||||
if room == None:
|
||||
if room is None:
|
||||
if self._syncplayClient.userlist.currentUser.file:
|
||||
room = self._syncplayClient.userlist.currentUser.file["name"]
|
||||
else:
|
||||
@ -167,7 +169,7 @@ class ConsoleUI(threading.Thread):
|
||||
self._syncplayClient.sendRoom()
|
||||
elif command.group('command') in constants.COMMANDS_CREATE:
|
||||
roombasename = command.group('parameter')
|
||||
if roombasename == None:
|
||||
if roombasename is None:
|
||||
roombasename = self._syncplayClient.getRoom()
|
||||
roombasename = utils.stripRoomName(roombasename)
|
||||
self._syncplayClient.createControlledRoom(roombasename)
|
||||
|
||||
@ -1,28 +1,34 @@
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from datetime import datetime
|
||||
from functools import wraps
|
||||
from platform import python_version
|
||||
|
||||
from twisted.internet import task
|
||||
|
||||
from syncplay import utils, constants, version, revision, release_number
|
||||
from syncplay.messages import getMessage
|
||||
from syncplay.ui.consoleUI import ConsoleUI
|
||||
from syncplay.utils import resourcespath
|
||||
from syncplay.utils import isLinux, isWindows, isMacOS
|
||||
from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize, isURL
|
||||
from syncplay.vendor import Qt
|
||||
from syncplay.vendor.Qt import QtWidgets, QtGui, __binding__, __binding_version__, __qt_version__, IsPySide, IsPySide2
|
||||
from syncplay.vendor.Qt.QtCore import Qt, QSettings, QSize, QPoint, QUrl, QLine, QDateTime
|
||||
from platform import python_version
|
||||
if IsPySide2:
|
||||
from PySide2.QtCore import QStandardPaths
|
||||
from syncplay import utils, constants, version, revision, release_number
|
||||
from syncplay.messages import getMessage
|
||||
from syncplay.utils import resourcespath
|
||||
import sys
|
||||
import time
|
||||
import urllib.request, urllib.parse, urllib.error
|
||||
from datetime import datetime
|
||||
from syncplay.utils import isLinux, isWindows, isMacOS
|
||||
import re
|
||||
import os
|
||||
from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize, isURL
|
||||
from functools import wraps
|
||||
from twisted.internet import task
|
||||
from syncplay.ui.consoleUI import ConsoleUI
|
||||
if isMacOS() and IsPySide:
|
||||
from Foundation import NSURL
|
||||
from Cocoa import NSString, NSUTF8StringEncoding
|
||||
lastCheckedForUpdates = None
|
||||
|
||||
|
||||
class ConsoleInGUI(ConsoleUI):
|
||||
def showMessage(self, message, noTimestamp=False):
|
||||
self._syncplayClient.ui.showMessage(message, True)
|
||||
@ -39,6 +45,7 @@ class ConsoleInGUI(ConsoleUI):
|
||||
def getUserlist(self):
|
||||
self._syncplayClient.showUserList(self)
|
||||
|
||||
|
||||
class UserlistItemDelegate(QtWidgets.QStyledItemDelegate):
|
||||
def __init__(self):
|
||||
QtWidgets.QStyledItemDelegate.__init__(self)
|
||||
@ -102,6 +109,7 @@ class UserlistItemDelegate(QtWidgets.QStyledItemDelegate):
|
||||
optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+16)
|
||||
QtWidgets.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex)
|
||||
|
||||
|
||||
class AboutDialog(QtWidgets.QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super(AboutDialog, self).__init__(parent)
|
||||
@ -117,9 +125,13 @@ class AboutDialog(QtWidgets.QDialog):
|
||||
linkLabel = QtWidgets.QLabel("<center><a href=\"https://syncplay.pl\">syncplay.pl</a></center>")
|
||||
linkLabel.setOpenExternalLinks(True)
|
||||
versionExtString = version + revision
|
||||
versionLabel = QtWidgets.QLabel("<p><center>" + getMessage("about-dialog-release").format(versionExtString, release_number) + "<br />Python " + python_version() + " - " + __binding__ + " " + __binding_version__ + " - Qt " + __qt_version__ + "</center></p>")
|
||||
#versionLabel = QtWidgets.QLabel("<p><center>Version 1.5.4 release 62<br />Python 3.4.5 - PySide 1.2.4 - Qt 4.8.7</center></p>")
|
||||
licenseLabel = QtWidgets.QLabel("<center><p>Copyright © 2012–2018 Syncplay</p><p>" + getMessage("about-dialog-license-text") + "</p></center>")
|
||||
versionLabel = QtWidgets.QLabel(
|
||||
"<p><center>" + getMessage("about-dialog-release").format(versionExtString, release_number) +
|
||||
"<br />Python " + python_version() + " - " + __binding__ + " " + __binding_version__ +
|
||||
" - Qt " + __qt_version__ + "</center></p>")
|
||||
licenseLabel = QtWidgets.QLabel(
|
||||
"<center><p>Copyright © 2012–2018 Syncplay</p><p>" +
|
||||
getMessage("about-dialog-license-text") + "</p></center>")
|
||||
aboutIconPixmap = QtGui.QPixmap(resourcespath + "syncplay.png")
|
||||
aboutIconLabel = QtWidgets.QLabel()
|
||||
aboutIconLabel.setPixmap(aboutIconPixmap.scaled(65, 65, Qt.KeepAspectRatio))
|
||||
@ -154,6 +166,7 @@ class AboutDialog(QtWidgets.QDialog):
|
||||
else:
|
||||
QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + "third-party-notices.rtf"))
|
||||
|
||||
|
||||
class MainWindow(QtWidgets.QMainWindow):
|
||||
insertPosition = None
|
||||
playlistState = []
|
||||
@ -347,8 +360,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
else:
|
||||
super(MainWindow.PlaylistWidget, self).dropEvent(event)
|
||||
|
||||
|
||||
|
||||
class topSplitter(QtWidgets.QSplitter):
|
||||
def createHandle(self):
|
||||
return self.topSplitterHandle(self.orientation(), self)
|
||||
@ -470,9 +481,16 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
def showUserList(self, currentUser, rooms):
|
||||
self._usertreebuffer = QtGui.QStandardItemModel()
|
||||
self._usertreebuffer.setHorizontalHeaderLabels(
|
||||
(getMessage("roomuser-heading-label"), getMessage("size-heading-label"), getMessage("duration-heading-label"), getMessage("filename-heading-label") ))
|
||||
(
|
||||
getMessage("roomuser-heading-label"), getMessage("size-heading-label"),
|
||||
getMessage("duration-heading-label"), getMessage("filename-heading-label")
|
||||
))
|
||||
usertreeRoot = self._usertreebuffer.invisibleRootItem()
|
||||
if self._syncplayClient.userlist.currentUser.file and self._syncplayClient.userlist.currentUser.file and os.path.isfile(self._syncplayClient.userlist.currentUser.file["path"]):
|
||||
if (
|
||||
self._syncplayClient.userlist.currentUser.file and
|
||||
self._syncplayClient.userlist.currentUser.file and
|
||||
os.path.isfile(self._syncplayClient.userlist.currentUser.file["path"])
|
||||
):
|
||||
self._syncplayClient.fileSwitch.setCurrentDirectory(os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"]))
|
||||
|
||||
for room in rooms:
|
||||
@ -613,7 +631,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
menu.addAction(QtGui.QPixmap(resourcespath + "shield_edit.png"), getMessage("settrusteddomains-menu-label"), lambda: self.openSetTrustedDomainsDialog())
|
||||
menu.exec_(self.playlist.viewport().mapToGlobal(position))
|
||||
|
||||
|
||||
def openRoomMenu(self, position):
|
||||
# TODO: Deselect items after right click
|
||||
indexes = self.listTreeView.selectedIndexes()
|
||||
@ -694,7 +711,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
def updateReadyState(self, newState):
|
||||
oldState = self.readyPushButton.isChecked()
|
||||
if newState != oldState and newState != None:
|
||||
if newState != oldState and newState is not None:
|
||||
self.readyPushButton.blockSignals(True)
|
||||
self.readyPushButton.setChecked(newState)
|
||||
self.readyPushButton.blockSignals(False)
|
||||
@ -768,7 +785,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
@needsClient
|
||||
def joinRoom(self, room=None):
|
||||
if room == None:
|
||||
if room is None:
|
||||
room = self.roomInput.text()
|
||||
if room == "":
|
||||
if self._syncplayClient.userlist.currentUser.file:
|
||||
@ -781,14 +798,13 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self._syncplayClient.sendRoom()
|
||||
|
||||
def seekPositionDialog(self):
|
||||
seekTime, ok = QtWidgets.QInputDialog.getText(self, getMessage("seektime-menu-label"),
|
||||
seekTime, ok = QtWidgets.QInputDialog.getText(
|
||||
self, getMessage("seektime-menu-label"),
|
||||
getMessage("seektime-msgbox-label"), QtWidgets.QLineEdit.Normal,
|
||||
"0:00")
|
||||
if ok and seekTime != '':
|
||||
self.seekPosition(seekTime)
|
||||
|
||||
|
||||
|
||||
def seekFromButton(self):
|
||||
self.seekPosition(self.seekInput.text())
|
||||
|
||||
@ -871,7 +887,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
@needsClient
|
||||
def browseMediapath(self):
|
||||
if self._syncplayClient._player.customOpenDialog == True:
|
||||
if self._syncplayClient._player.customOpenDialog:
|
||||
self._syncplayClient._player.openCustomOpenDialog()
|
||||
return
|
||||
|
||||
@ -887,7 +903,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
else:
|
||||
defaultdirectory = self.getInitialMediaDirectory()
|
||||
browserfilter = "All files (*)"
|
||||
fileName, filtr = QtWidgets.QFileDialog.getOpenFileName(self, getMessage("browseformedia-label"), defaultdirectory,
|
||||
fileName, filtr = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self, getMessage("browseformedia-label"), defaultdirectory,
|
||||
browserfilter, "", options)
|
||||
if fileName:
|
||||
if isWindows():
|
||||
@ -899,7 +916,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
@needsClient
|
||||
def OpenAddFilesToPlaylistDialog(self):
|
||||
if self._syncplayClient._player.customOpenDialog == True:
|
||||
if self._syncplayClient._player.customOpenDialog:
|
||||
self._syncplayClient._player.openCustomOpenDialog()
|
||||
return
|
||||
|
||||
@ -915,7 +932,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
else:
|
||||
defaultdirectory = self.getInitialMediaDirectory()
|
||||
browserfilter = "All files (*)"
|
||||
fileNames, filtr = QtWidgets.QFileDialog.getOpenFileNames(self, getMessage("browseformedia-label"), defaultdirectory,
|
||||
fileNames, filtr = QtWidgets.QFileDialog.getOpenFileNames(
|
||||
self, getMessage("browseformedia-label"), defaultdirectory,
|
||||
browserfilter, "", options)
|
||||
self.updatingPlaylist = True
|
||||
if fileNames:
|
||||
@ -1046,6 +1064,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
if result == QtWidgets.QDialog.Accepted:
|
||||
newTrustedDomains = utils.convertMultilineStringToList(TrustedDomainsTextbox.toPlainText())
|
||||
self._syncplayClient.setTrustedDomains(newTrustedDomains)
|
||||
|
||||
@needsClient
|
||||
def addTrustedDomain(self, newDomain):
|
||||
trustedDomains = self.config["trustedDomains"][:]
|
||||
@ -1059,7 +1078,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
options = QtWidgets.QFileDialog.Options(QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
|
||||
else:
|
||||
options = QtWidgets.QFileDialog.Options(QtWidgets.QFileDialog.ShowDirsOnly)
|
||||
folderName = str(QtWidgets.QFileDialog.getExistingDirectory(self,None,self.getInitialMediaDirectory(includeUserSpecifiedDirectories=False),options))
|
||||
folderName = str(QtWidgets.QFileDialog.getExistingDirectory(
|
||||
self, None, self.getInitialMediaDirectory(includeUserSpecifiedDirectories=False), options))
|
||||
|
||||
if folderName:
|
||||
existingMediaDirs = MediaDirectoriesTextbox.toPlainText()
|
||||
@ -1073,15 +1093,16 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
@needsClient
|
||||
def promptForStreamURL(self):
|
||||
streamURL, ok = QtWidgets.QInputDialog.getText(self, getMessage("promptforstreamurl-msgbox-label"),
|
||||
getMessage("promptforstreamurlinfo-msgbox-label"), QtWidgets.QLineEdit.Normal,
|
||||
"")
|
||||
streamURL, ok = QtWidgets.QInputDialog.getText(
|
||||
self, getMessage("promptforstreamurl-msgbox-label"),
|
||||
getMessage("promptforstreamurlinfo-msgbox-label"), QtWidgets.QLineEdit.Normal, "")
|
||||
if ok and streamURL != '':
|
||||
self._syncplayClient._player.openFile(streamURL)
|
||||
|
||||
@needsClient
|
||||
def createControlledRoom(self):
|
||||
controlroom, ok = QtWidgets.QInputDialog.getText(self, getMessage("createcontrolledroom-msgbox-label"),
|
||||
controlroom, ok = QtWidgets.QInputDialog.getText(
|
||||
self, getMessage("createcontrolledroom-msgbox-label"),
|
||||
getMessage("controlledroominfo-msgbox-label"), QtWidgets.QLineEdit.Normal,
|
||||
utils.stripRoomName(self._syncplayClient.getRoom()))
|
||||
if ok and controlroom != '':
|
||||
@ -1106,9 +1127,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
@needsClient
|
||||
def setOffset(self):
|
||||
newoffset, ok = QtWidgets.QInputDialog.getText(self, getMessage("setoffset-msgbox-label"),
|
||||
getMessage("offsetinfo-msgbox-label"), QtWidgets.QLineEdit.Normal,
|
||||
"")
|
||||
newoffset, ok = QtWidgets.QInputDialog.getText(
|
||||
self, getMessage("setoffset-msgbox-label"),
|
||||
getMessage("offsetinfo-msgbox-label"), QtWidgets.QLineEdit.Normal, "")
|
||||
if ok and newoffset != '':
|
||||
o = re.match(constants.UI_OFFSET_REGEX, "o " + newoffset)
|
||||
if o:
|
||||
@ -1187,7 +1208,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
window.chatInput = QtWidgets.QLineEdit()
|
||||
window.chatInput.setMaxLength(constants.MAX_CHAT_MESSAGE_LENGTH)
|
||||
window.chatInput.returnPressed.connect(self.sendChatMessage)
|
||||
window.chatButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'email_go.png'),
|
||||
window.chatButton = QtWidgets.QPushButton(
|
||||
QtGui.QPixmap(resourcespath + 'email_go.png'),
|
||||
getMessage("sendmessage-label"))
|
||||
window.chatButton.pressed.connect(self.sendChatMessage)
|
||||
window.chatLayout = QtWidgets.QHBoxLayout()
|
||||
@ -1242,7 +1264,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
window.roomInput = QtWidgets.QLineEdit()
|
||||
window.roomInput.setMaxLength(constants.MAX_ROOM_NAME_LENGTH)
|
||||
window.roomInput.returnPressed.connect(self.joinRoom)
|
||||
window.roomButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'door_in.png'),
|
||||
window.roomButton = QtWidgets.QPushButton(
|
||||
QtGui.QPixmap(resourcespath + 'door_in.png'),
|
||||
getMessage("joinroom-label"))
|
||||
window.roomButton.pressed.connect(self.joinRoom)
|
||||
window.roomLayout = QtWidgets.QHBoxLayout()
|
||||
@ -1405,7 +1428,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
getMessage("setmediadirectories-menu-label"))
|
||||
window.openAction.triggered.connect(self.openSetMediaDirectoriesDialog)
|
||||
|
||||
|
||||
window.exitAction = window.fileMenu.addAction(QtGui.QPixmap(resourcespath + 'cross.png'),
|
||||
getMessage("exit-menu-label"))
|
||||
window.exitAction.triggered.connect(self.exitSyncplay)
|
||||
@ -1414,13 +1436,21 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
# Playback menu
|
||||
|
||||
window.playbackMenu = QtWidgets.QMenu(getMessage("playback-menu-label"), self)
|
||||
window.playAction = window.playbackMenu.addAction(QtGui.QPixmap(resourcespath + 'control_play_blue.png'), getMessage("play-menu-label"))
|
||||
window.playAction = window.playbackMenu.addAction(
|
||||
QtGui.QPixmap(resourcespath + 'control_play_blue.png'),
|
||||
getMessage("play-menu-label"))
|
||||
window.playAction.triggered.connect(self.play)
|
||||
window.pauseAction = window.playbackMenu.addAction(QtGui.QPixmap(resourcespath + 'control_pause_blue.png'), getMessage("pause-menu-label"))
|
||||
window.pauseAction = window.playbackMenu.addAction(
|
||||
QtGui.QPixmap(resourcespath + 'control_pause_blue.png'),
|
||||
getMessage("pause-menu-label"))
|
||||
window.pauseAction.triggered.connect(self.pause)
|
||||
window.seekAction = window.playbackMenu.addAction(QtGui.QPixmap(resourcespath + 'clock_go.png'), getMessage("seektime-menu-label"))
|
||||
window.seekAction = window.playbackMenu.addAction(
|
||||
QtGui.QPixmap(resourcespath + 'clock_go.png'),
|
||||
getMessage("seektime-menu-label"))
|
||||
window.seekAction.triggered.connect(self.seekPositionDialog)
|
||||
window.unseekAction = window.playbackMenu.addAction(QtGui.QPixmap(resourcespath + 'arrow_undo.png'), getMessage("undoseek-menu-label"))
|
||||
window.unseekAction = window.playbackMenu.addAction(
|
||||
QtGui.QPixmap(resourcespath + 'arrow_undo.png'),
|
||||
getMessage("undoseek-menu-label"))
|
||||
window.unseekAction.triggered.connect(self.undoSeek)
|
||||
|
||||
window.menuBar.addMenu(window.playbackMenu)
|
||||
@ -1428,10 +1458,12 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
# Advanced menu
|
||||
|
||||
window.advancedMenu = QtWidgets.QMenu(getMessage("advanced-menu-label"), self)
|
||||
window.setoffsetAction = window.advancedMenu.addAction(QtGui.QPixmap(resourcespath + 'timeline_marker.png'),
|
||||
window.setoffsetAction = window.advancedMenu.addAction(
|
||||
QtGui.QPixmap(resourcespath + 'timeline_marker.png'),
|
||||
getMessage("setoffset-menu-label"))
|
||||
window.setoffsetAction.triggered.connect(self.setOffset)
|
||||
window.setTrustedDomainsAction = window.advancedMenu.addAction(QtGui.QPixmap(resourcespath + 'shield_edit.png'),
|
||||
window.setTrustedDomainsAction = window.advancedMenu.addAction(
|
||||
QtGui.QPixmap(resourcespath + 'shield_edit.png'),
|
||||
getMessage("settrusteddomains-menu-label"))
|
||||
window.setTrustedDomainsAction.triggered.connect(self.openSetTrustedDomainsDialog)
|
||||
window.createcontrolledroomAction = window.advancedMenu.addAction(
|
||||
@ -1456,21 +1488,23 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
window.autoplayAction.triggered.connect(self.updateAutoplayVisibility)
|
||||
window.menuBar.addMenu(window.windowMenu)
|
||||
|
||||
|
||||
# Help menu
|
||||
|
||||
window.helpMenu = QtWidgets.QMenu(getMessage("help-menu-label"), self)
|
||||
|
||||
window.userguideAction = window.helpMenu.addAction(QtGui.QPixmap(resourcespath + 'help.png'),
|
||||
window.userguideAction = window.helpMenu.addAction(
|
||||
QtGui.QPixmap(resourcespath + 'help.png'),
|
||||
getMessage("userguide-menu-label"))
|
||||
window.userguideAction.triggered.connect(self.openUserGuide)
|
||||
window.updateAction = window.helpMenu.addAction(QtGui.QPixmap(resourcespath + 'application_get.png'),
|
||||
window.updateAction = window.helpMenu.addAction(
|
||||
QtGui.QPixmap(resourcespath + 'application_get.png'),
|
||||
getMessage("update-menu-label"))
|
||||
window.updateAction.triggered.connect(self.userCheckForUpdates)
|
||||
|
||||
if not isMacOS():
|
||||
window.helpMenu.addSeparator()
|
||||
window.about = window.helpMenu.addAction(QtGui.QPixmap(resourcespath + 'syncplay.png'),
|
||||
window.about = window.helpMenu.addAction(
|
||||
QtGui.QPixmap(resourcespath + 'syncplay.png'),
|
||||
getMessage("about-menu-label"))
|
||||
else:
|
||||
window.about = window.helpMenu.addAction("&About")
|
||||
@ -1528,7 +1562,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
def updateAutoPlayState(self, newState):
|
||||
oldState = self.autoplayPushButton.isChecked()
|
||||
if newState != oldState and newState != None:
|
||||
if newState != oldState and newState is not None:
|
||||
self.autoplayPushButton.blockSignals(True)
|
||||
self.autoplayPushButton.setChecked(newState)
|
||||
self.autoplayPushButton.blockSignals(False)
|
||||
@ -1591,10 +1625,11 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
else:
|
||||
import syncplay
|
||||
updateMessage = getMessage("update-check-failed-notification").format(syncplay.version)
|
||||
if userInitiated == True:
|
||||
if userInitiated:
|
||||
updateURL = constants.SYNCPLAY_DOWNLOAD_URL
|
||||
if updateURL is not None:
|
||||
reply = QtWidgets.QMessageBox.question(self, "Syncplay",
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self, "Syncplay",
|
||||
updateMessage, QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No)
|
||||
if reply == QtWidgets.QMessageBox.Yes:
|
||||
self.QtGui.QDesktopServices.openUrl(QUrl(updateURL))
|
||||
@ -1624,7 +1659,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
dropfilepath = os.path.abspath(NSURL.URLWithString_(pathString).filePathURL().path())
|
||||
else:
|
||||
dropfilepath = os.path.abspath(str(url.toLocalFile()))
|
||||
if rewindFile == False:
|
||||
if not rewindFile:
|
||||
self._syncplayClient._player.openFile(dropfilepath)
|
||||
else:
|
||||
self._syncplayClient.setPosition(0)
|
||||
|
||||
@ -1,35 +1,44 @@
|
||||
import time
|
||||
import re
|
||||
|
||||
import ast
|
||||
import datetime
|
||||
import hashlib
|
||||
import itertools
|
||||
import random
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import unicodedata
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
from syncplay import constants
|
||||
from syncplay.messages import getMessage
|
||||
import sys
|
||||
import os
|
||||
import itertools
|
||||
import hashlib
|
||||
import random
|
||||
import string
|
||||
import urllib.request, urllib.parse, urllib.error
|
||||
import ast
|
||||
import unicodedata
|
||||
import platform
|
||||
import subprocess
|
||||
import traceback
|
||||
|
||||
folderSearchEnabled = True
|
||||
|
||||
|
||||
def isWindows():
|
||||
return sys.platform.startswith(constants.OS_WINDOWS)
|
||||
|
||||
|
||||
def isLinux():
|
||||
return sys.platform.startswith(constants.OS_LINUX)
|
||||
|
||||
|
||||
def isMacOS():
|
||||
return sys.platform.startswith(constants.OS_MACOS)
|
||||
|
||||
|
||||
def isBSD():
|
||||
return constants.OS_BSD in sys.platform or sys.platform.startswith(constants.OS_DRAGONFLY)
|
||||
|
||||
|
||||
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
|
||||
"""Retry calling the decorated function using an exponential backoff.
|
||||
|
||||
@ -71,6 +80,7 @@ def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
|
||||
return f_retry # true decorator
|
||||
return deco_retry
|
||||
|
||||
|
||||
def parseTime(timeStr):
|
||||
regex = re.compile(constants.PARSE_TIME_REGEX)
|
||||
parts = regex.match(timeStr)
|
||||
@ -86,6 +96,7 @@ def parseTime(timeStr):
|
||||
time_params[name] = int(param)
|
||||
return datetime.timedelta(**time_params).total_seconds()
|
||||
|
||||
|
||||
def formatTime(timeInSeconds, weeksAsTitles=True):
|
||||
if timeInSeconds < 0:
|
||||
timeInSeconds = -timeInSeconds
|
||||
@ -115,7 +126,8 @@ def formatTime(timeInSeconds, weeksAsTitles=True):
|
||||
formattedTime = "{0:} (Title {1:.0f})".format(formattedTime, title)
|
||||
return formattedTime
|
||||
|
||||
def formatSize (bytes, precise=False):
|
||||
|
||||
def formatSize(num_of_bytes, precise=False):
|
||||
if bytes == 0: # E.g. when file size privacy is enabled
|
||||
return "???"
|
||||
try:
|
||||
@ -128,9 +140,11 @@ def formatSize (bytes, precise=False):
|
||||
except: # E.g. when filesize is hashed
|
||||
return "???"
|
||||
|
||||
|
||||
def isASCII(s):
|
||||
return all(ord(c) < 128 for c in s)
|
||||
|
||||
|
||||
def findResourcePath(resourceName):
|
||||
if resourceName == "syncplay.lua":
|
||||
resourcePath = os.path.join(findWorkingDir(), "lua", "intf", "resources", resourceName)
|
||||
@ -138,6 +152,7 @@ def findResourcePath(resourceName):
|
||||
resourcePath = os.path.join(findWorkingDir(), "resources", resourceName)
|
||||
return resourcePath
|
||||
|
||||
|
||||
def findWorkingDir():
|
||||
frozen = getattr(sys, 'frozen', '')
|
||||
if not frozen:
|
||||
@ -153,15 +168,18 @@ def findWorkingDir():
|
||||
path = ""
|
||||
return path
|
||||
|
||||
|
||||
def getResourcesPath():
|
||||
if isWindows():
|
||||
return findWorkingDir() + "\\resources\\"
|
||||
else:
|
||||
return findWorkingDir() + "/resources/"
|
||||
|
||||
|
||||
resourcespath = getResourcesPath()
|
||||
posixresourcespath = findWorkingDir().replace("\\", "/") + "/resources/"
|
||||
|
||||
|
||||
def getDefaultMonospaceFont():
|
||||
if platform.system() == "Windows":
|
||||
return constants.DEFAULT_WINDOWS_MONOSPACE_FONT
|
||||
@ -170,15 +188,18 @@ def getDefaultMonospaceFont():
|
||||
else:
|
||||
return constants.FALLBACK_MONOSPACE_FONT
|
||||
|
||||
|
||||
def limitedPowerset(s, minLength):
|
||||
return itertools.chain.from_iterable(itertools.combinations(s, r) for r in range(len(s), minLength, -1))
|
||||
|
||||
|
||||
def blackholeStdoutForFrozenWindow():
|
||||
if getattr(sys, 'frozen', '') == "windows_exe":
|
||||
class Stderr(object):
|
||||
softspace = 0
|
||||
_file = None
|
||||
_error = None
|
||||
|
||||
def write(self, text, fname='.syncplay.log'):
|
||||
if self._file is None and self._error is None:
|
||||
if os.name != 'nt':
|
||||
@ -190,21 +211,27 @@ def blackholeStdoutForFrozenWindow():
|
||||
if self._file is not None:
|
||||
self._file.write(text)
|
||||
self._file.flush()
|
||||
|
||||
def flush(self):
|
||||
if self._file is not None:
|
||||
self._file.flush()
|
||||
|
||||
sys.stderr = Stderr()
|
||||
del Stderr
|
||||
|
||||
class Blackhole(object):
|
||||
softspace = 0
|
||||
|
||||
def write(self, text):
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
sys.stdout = Blackhole()
|
||||
del Blackhole
|
||||
|
||||
|
||||
def truncateText(unicodeText, maxLength):
|
||||
try:
|
||||
unicodeText = unicodeText.decode('utf-8')
|
||||
@ -216,6 +243,7 @@ def truncateText(unicodeText, maxLength):
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
def splitText(unicodeText, maxLength):
|
||||
try:
|
||||
unicodeText = unicodeText.decode('utf-8')
|
||||
@ -231,6 +259,7 @@ def splitText(unicodeText, maxLength):
|
||||
|
||||
# Relate to file hashing / difference checking:
|
||||
|
||||
|
||||
def stripfilename(filename, stripURL):
|
||||
if filename:
|
||||
try:
|
||||
@ -247,6 +276,7 @@ def stripfilename(filename, stripURL):
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def stripRoomName(RoomName):
|
||||
if RoomName:
|
||||
try:
|
||||
@ -256,6 +286,7 @@ def stripRoomName(RoomName):
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def hashFilename(filename, stripURL=False):
|
||||
if isURL(filename):
|
||||
stripURL = True
|
||||
@ -267,9 +298,11 @@ def hashFilename(filename, stripURL = False):
|
||||
filenameHash = hashlib.sha256(strippedFilename).hexdigest()[:12]
|
||||
return filenameHash
|
||||
|
||||
|
||||
def hashFilesize(size):
|
||||
return hashlib.sha256(str(size).encode('utf-8')).hexdigest()[:12]
|
||||
|
||||
|
||||
def sameHashed(string1raw, string1hashed, string2raw, string2hashed):
|
||||
try:
|
||||
if string1raw.lower() == string2raw.lower():
|
||||
@ -285,6 +318,7 @@ def sameHashed(string1raw, string1hashed, string2raw, string2hashed):
|
||||
elif string1hashed == string2hashed:
|
||||
return True
|
||||
|
||||
|
||||
def sameFilename(filename1, filename2):
|
||||
try:
|
||||
filename1 = filename1
|
||||
@ -302,6 +336,7 @@ def sameFilename (filename1, filename2):
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def sameFilesize(filesize1, filesize2):
|
||||
if filesize1 == 0 or filesize2 == 0:
|
||||
return True
|
||||
@ -310,6 +345,7 @@ def sameFilesize (filesize1, filesize2):
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def sameFileduration(duration1, duration2):
|
||||
if not constants.SHOW_DURATION_NOTIFICATION:
|
||||
return True
|
||||
@ -318,11 +354,13 @@ def sameFileduration (duration1, duration2):
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def meetsMinVersion(version, minVersion):
|
||||
def versiontotuple(ver):
|
||||
return tuple(map(int, ver.split(".")))
|
||||
return versiontotuple(version) >= versiontotuple(minVersion)
|
||||
|
||||
|
||||
def isURL(path):
|
||||
if path is None:
|
||||
return False
|
||||
@ -331,22 +369,27 @@ def isURL(path):
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def getPlayerArgumentsByPathAsArray(arguments, path):
|
||||
if arguments and not isinstance(arguments, str) and path in arguments:
|
||||
return arguments[path]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def getPlayerArgumentsByPathAsText(arguments, path):
|
||||
argsToReturn = getPlayerArgumentsByPathAsArray(arguments, path)
|
||||
return " ".join(argsToReturn) if argsToReturn else ""
|
||||
|
||||
|
||||
def getListAsMultilineString(pathArray):
|
||||
return "\n".join(pathArray) if pathArray else ""
|
||||
|
||||
|
||||
def convertMultilineStringToList(multilineString):
|
||||
return str.split(multilineString, "\n") if multilineString else ""
|
||||
|
||||
|
||||
def playlistIsValid(files):
|
||||
if len(files) > constants.PLAYLIST_MAX_ITEMS:
|
||||
return False
|
||||
@ -354,6 +397,7 @@ def playlistIsValid(files):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def getDomainFromURL(URL):
|
||||
try:
|
||||
URL = URL.split("//")[-1].split("/")[0]
|
||||
@ -363,6 +407,7 @@ def getDomainFromURL(URL):
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def open_system_file_browser(path):
|
||||
if isURL(path):
|
||||
return
|
||||
@ -374,6 +419,7 @@ def open_system_file_browser(path):
|
||||
else:
|
||||
subprocess.Popen(["xdg-open", path])
|
||||
|
||||
|
||||
def getListOfPublicServers():
|
||||
try:
|
||||
import urllib.request, urllib.parse, urllib.error, syncplay, sys
|
||||
@ -394,12 +440,13 @@ def getListOfPublicServers():
|
||||
else:
|
||||
raise IOError
|
||||
except:
|
||||
if constants.DEBUG_MODE == True:
|
||||
if constants.DEBUG_MODE:
|
||||
traceback.print_exc()
|
||||
raise
|
||||
else:
|
||||
raise IOError(getMessage("failed-to-load-server-list-error"))
|
||||
|
||||
|
||||
class RoomPasswordProvider(object):
|
||||
CONTROLLED_ROOM_REGEX = re.compile("^\+(.*):(\w{12})$")
|
||||
PASSWORD_REGEX = re.compile("[A-Z]{2}-\d{3}-\d{3}")
|
||||
@ -433,6 +480,7 @@ class RoomPasswordProvider(object):
|
||||
provisionalHash = hashlib.sha256(roomName + salt).hexdigest()
|
||||
return hashlib.sha1(provisionalHash + salt + password).hexdigest()[:12].upper()
|
||||
|
||||
|
||||
class RandomStringGenerator(object):
|
||||
@staticmethod
|
||||
def generate_room_password():
|
||||
@ -458,6 +506,6 @@ class RandomStringGenerator(object):
|
||||
def _get_random_numbers(quantity):
|
||||
return ''.join(random.choice(string.digits) for _ in range(quantity))
|
||||
|
||||
|
||||
class NotControlledRoom(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@ -17,4 +17,3 @@ from syncplay.utils import blackholeStdoutForFrozenWindow
|
||||
if __name__ == '__main__':
|
||||
blackholeStdoutForFrozenWindow()
|
||||
SyncplayClientManager().run()
|
||||
|
||||
|
||||
@ -19,5 +19,14 @@ from syncplay.server import SyncFactory, ConfigurationGetter
|
||||
if __name__ == '__main__':
|
||||
argsGetter = ConfigurationGetter()
|
||||
args = argsGetter.getConfiguration()
|
||||
reactor.listenTCP(int(args.port), SyncFactory(args.password, args.motd_file, args.isolate_rooms, args.salt, args.disable_ready,args.disable_chat, args.max_chat_message_length))
|
||||
reactor.listenTCP(
|
||||
int(args.port),
|
||||
SyncFactory(
|
||||
args.password,
|
||||
args.motd_file,
|
||||
args.isolate_rooms,
|
||||
args.salt,
|
||||
args.disable_ready,
|
||||
args.disable_chat,
|
||||
args.max_chat_message_length))
|
||||
reactor.run()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user