108
buildPy2exe.py
@ -127,13 +127,13 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
Var Dialog
|
||||
Var Icon_Syncplay
|
||||
Var Icon_Syncplay_Handle
|
||||
Var CheckBox_Associate
|
||||
;Var CheckBox_Associate
|
||||
Var CheckBox_VLC
|
||||
Var CheckBox_AutomaticUpdates
|
||||
Var CheckBox_StartMenuShortcut
|
||||
Var CheckBox_DesktopShortcut
|
||||
Var CheckBox_QuickLaunchShortcut
|
||||
Var CheckBox_Associate_State
|
||||
;Var CheckBox_Associate_State
|
||||
Var CheckBox_VLC_State
|
||||
Var CheckBox_AutomaticUpdates_State
|
||||
Var CheckBox_StartMenuShortcut_State
|
||||
@ -165,13 +165,13 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
Var Drive
|
||||
Var VLC_Directory
|
||||
|
||||
!macro APP_ASSOCIATE EXT FileCLASS DESCRIPTION COMMANDTEXT COMMAND
|
||||
WriteRegStr HKCR ".$${EXT}" "" "$${FileCLASS}"
|
||||
WriteRegStr HKCR "$${FileCLASS}" "" `$${DESCRIPTION}`
|
||||
WriteRegStr HKCR "$${FileCLASS}\shell" "" "open"
|
||||
WriteRegStr HKCR "$${FileCLASS}\shell\open" "" `$${COMMANDTEXT}`
|
||||
WriteRegStr HKCR "$${FileCLASS}\shell\open\command" "" `$${COMMAND}`
|
||||
!macroend
|
||||
;!macro APP_ASSOCIATE EXT FileCLASS DESCRIPTION COMMANDTEXT COMMAND
|
||||
; WriteRegStr HKCR ".$${EXT}" "" "$${FileCLASS}"
|
||||
; WriteRegStr HKCR "$${FileCLASS}" "" `$${DESCRIPTION}`
|
||||
; WriteRegStr HKCR "$${FileCLASS}\shell" "" "open"
|
||||
; WriteRegStr HKCR "$${FileCLASS}\shell\open" "" `$${COMMANDTEXT}`
|
||||
; WriteRegStr HKCR "$${FileCLASS}\shell\open\command" "" `$${COMMAND}`
|
||||
;!macroend
|
||||
|
||||
!macro APP_UNASSOCIATE EXT FileCLASS
|
||||
; Backup the previously associated File class
|
||||
@ -180,10 +180,10 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
DeleteRegKey HKCR `$${FileCLASS}`
|
||||
!macroend
|
||||
|
||||
!macro ASSOCIATE EXT
|
||||
!insertmacro APP_ASSOCIATE "$${EXT}" "Syncplay.$${EXT}" "$$INSTDIR\Syncplay.exe,%1%" \
|
||||
"Open with Syncplay" "$$INSTDIR\Syncplay.exe $$\"%1$$\""
|
||||
!macroend
|
||||
;!macro ASSOCIATE EXT
|
||||
; !insertmacro APP_ASSOCIATE "$${EXT}" "Syncplay.$${EXT}" "$$INSTDIR\Syncplay.exe,%1%" \
|
||||
; "Open with Syncplay" "$$INSTDIR\Syncplay.exe $$\"%1$$\""
|
||||
;!macroend
|
||||
|
||||
!macro UNASSOCIATE EXT
|
||||
!insertmacro APP_UNASSOCIATE "$${EXT}" "Syncplay.$${EXT}"
|
||||
@ -197,7 +197,7 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
MessageBox MB_OK|MB_ICONEXCLAMATION "The installer is already running."
|
||||
Abort
|
||||
|
||||
StrCpy $$CheckBox_Associate_State $${BST_CHECKED}
|
||||
;StrCpy $$CheckBox_Associate_State $${BST_CHECKED}
|
||||
StrCpy $$CheckBox_StartMenuShortcut_State $${BST_CHECKED}
|
||||
Call GetVLCDir
|
||||
Call UpdateVLCCheckbox
|
||||
@ -257,8 +257,8 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
$${NSD_CreateLabel} 321u 122u 132 8u "$$(^SpaceAvailable)$$AvailibleSpaceGiB.$$AvailibleSpaceGB"
|
||||
Pop $$Label_Space
|
||||
|
||||
$${NSD_CreateCheckBox} 8u 59u 187u 10u "$$(^Associate)"
|
||||
Pop $$CheckBox_Associate
|
||||
;$${NSD_CreateCheckBox} 8u 59u 187u 10u "$$(^Associate)"
|
||||
;Pop $$CheckBox_Associate
|
||||
|
||||
$${NSD_CreateBrowseButton} 185u 70u 70u 14u "$$(^BrowseVLCBtn)"
|
||||
Pop $$Button_Browse_VLC
|
||||
@ -283,9 +283,9 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
$${NSD_CreateCheckbox} 158u 111u 130u 10u "$$(^QuickLaunchBar)"
|
||||
Pop $$CheckBox_QuickLaunchShortcut
|
||||
|
||||
$${If} $$CheckBox_Associate_State == $${BST_CHECKED}
|
||||
$${NSD_Check} $$CheckBox_Associate
|
||||
$${EndIf}
|
||||
;$${If} $$CheckBox_Associate_State == $${BST_CHECKED}
|
||||
; $${NSD_Check} $$CheckBox_Associate
|
||||
;$${EndIf}
|
||||
|
||||
$${If} $$CheckBox_VLC_State == $${BST_CHECKED}
|
||||
$${NSD_Check} $$CheckBox_VLC
|
||||
@ -317,7 +317,7 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
|
||||
Function DirectoryCustomLeave
|
||||
$${NSD_GetText} $$Text_Directory $$INSTDIR
|
||||
$${NSD_GetState} $$CheckBox_Associate $$CheckBox_Associate_State
|
||||
;$${NSD_GetState} $$CheckBox_Associate $$CheckBox_Associate_State
|
||||
$${NSD_GetState} $$CheckBox_VLC $$CheckBox_VLC_State
|
||||
$${NSD_GetState} $$CheckBox_AutomaticUpdates $$CheckBox_AutomaticUpdates_State
|
||||
$${NSD_GetState} $$CheckBox_StartMenuShortcut $$CheckBox_StartMenuShortcut_State
|
||||
@ -395,10 +395,10 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
FunctionEnd
|
||||
|
||||
Function InstallOptions
|
||||
$${If} $$CheckBox_Associate_State == $${BST_CHECKED}
|
||||
Call Associate
|
||||
DetailPrint "Associated Syncplay with multimedia files"
|
||||
$${EndIf}
|
||||
;$${If} $$CheckBox_Associate_State == $${BST_CHECKED}
|
||||
; Call Associate
|
||||
; DetailPrint "Associated Syncplay with multimedia files"
|
||||
;$${EndIf}
|
||||
|
||||
$${If} $$CheckBox_StartMenuShortcut_State == $${BST_CHECKED}
|
||||
CreateDirectory $$SMPROGRAMS\Syncplay
|
||||
@ -425,32 +425,32 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
FunctionEnd
|
||||
|
||||
;Associates extensions with Syncplay
|
||||
Function Associate
|
||||
!insertmacro ASSOCIATE avi
|
||||
!insertmacro ASSOCIATE mpg
|
||||
!insertmacro ASSOCIATE mpeg
|
||||
!insertmacro ASSOCIATE mpe
|
||||
!insertmacro ASSOCIATE m1v
|
||||
!insertmacro ASSOCIATE m2v
|
||||
!insertmacro ASSOCIATE mpv2
|
||||
!insertmacro ASSOCIATE mp2v
|
||||
!insertmacro ASSOCIATE mkv
|
||||
!insertmacro ASSOCIATE mp4
|
||||
!insertmacro ASSOCIATE m4v
|
||||
!insertmacro ASSOCIATE mp4v
|
||||
!insertmacro ASSOCIATE 3gp
|
||||
!insertmacro ASSOCIATE 3gpp
|
||||
!insertmacro ASSOCIATE 3g2
|
||||
!insertmacro ASSOCIATE 3pg2
|
||||
!insertmacro ASSOCIATE flv
|
||||
!insertmacro ASSOCIATE f4v
|
||||
!insertmacro ASSOCIATE rm
|
||||
!insertmacro ASSOCIATE wmv
|
||||
!insertmacro ASSOCIATE swf
|
||||
!insertmacro ASSOCIATE rmvb
|
||||
!insertmacro ASSOCIATE divx
|
||||
!insertmacro ASSOCIATE amv
|
||||
FunctionEnd
|
||||
;Function Associate
|
||||
; !insertmacro ASSOCIATE avi
|
||||
; !insertmacro ASSOCIATE mpg
|
||||
; !insertmacro ASSOCIATE mpeg
|
||||
; !insertmacro ASSOCIATE mpe
|
||||
; !insertmacro ASSOCIATE m1v
|
||||
; !insertmacro ASSOCIATE m2v
|
||||
; !insertmacro ASSOCIATE mpv2
|
||||
; !insertmacro ASSOCIATE mp2v
|
||||
; !insertmacro ASSOCIATE mkv
|
||||
; !insertmacro ASSOCIATE mp4
|
||||
; !insertmacro ASSOCIATE m4v
|
||||
; !insertmacro ASSOCIATE mp4v
|
||||
; !insertmacro ASSOCIATE 3gp
|
||||
; !insertmacro ASSOCIATE 3gpp
|
||||
; !insertmacro ASSOCIATE 3g2
|
||||
; !insertmacro ASSOCIATE 3pg2
|
||||
; !insertmacro ASSOCIATE flv
|
||||
; !insertmacro ASSOCIATE f4v
|
||||
; !insertmacro ASSOCIATE rm
|
||||
; !insertmacro ASSOCIATE wmv
|
||||
; !insertmacro ASSOCIATE swf
|
||||
; !insertmacro ASSOCIATE rmvb
|
||||
; !insertmacro ASSOCIATE divx
|
||||
; !insertmacro ASSOCIATE amv
|
||||
;FunctionEnd
|
||||
|
||||
Function WriteRegistry
|
||||
Call GetSize
|
||||
@ -653,9 +653,11 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock
|
||||
'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/chevrons_right.png',
|
||||
'resources/user_key.png', 'resources/lock.png', 'resources/key_go.png', 'resources/page_white_key.png',
|
||||
'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png',
|
||||
'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png',
|
||||
'resources/film_go.png', 'resources/world_go.png',
|
||||
'resources/arrow_refresh.png', 'resources/spinner.mng'
|
||||
'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', 'resources/arrow_switch.png',
|
||||
'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', 'resources/bullet_right_grey.png',
|
||||
'resources/film_folder_edit.png',
|
||||
'resources/shield_edit.png',
|
||||
'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png', 'resources/spinner.mng'
|
||||
]
|
||||
resources = ["resources/icon.ico", "resources/syncplay.png"]
|
||||
resources.extend(guiIcons)
|
||||
|
||||
BIN
resources/arrow_switch.png
Normal file
|
After Width: | Height: | Size: 683 B |
BIN
resources/bullet_right_grey.png
Normal file
|
After Width: | Height: | Size: 349 B |
BIN
resources/delete.png
Normal file
|
After Width: | Height: | Size: 715 B |
BIN
resources/film_add.png
Normal file
|
After Width: | Height: | Size: 739 B |
BIN
resources/film_folder_edit.png
Normal file
|
After Width: | Height: | Size: 1021 B |
@ -5,7 +5,7 @@
|
||||
Principal author: Etoh
|
||||
Other contributors: DerGenaue, jb
|
||||
Project: http://syncplay.pl/
|
||||
Version: 0.2.8
|
||||
Version: 0.2.9
|
||||
|
||||
Note:
|
||||
* This interface module is intended to be used in conjunction with Syncplay.
|
||||
@ -84,7 +84,7 @@ You may also need to re-copy the syncplay.lua file when you update VLC.
|
||||
|
||||
--]==========================================================================]
|
||||
|
||||
local connectorversion = "0.2.8"
|
||||
local connectorversion = "0.2.9"
|
||||
local vlcversion = vlc.misc.version()
|
||||
local durationdelay = 500000 -- Pause for get_duration command etc for increased reliability (uses microseconds)
|
||||
local loopsleepduration = 2500 -- Pause for every event loop (uses microseconds)
|
||||
@ -463,7 +463,9 @@ function load_file (filepath)
|
||||
-- [Used by load-file command]
|
||||
|
||||
local uri = vlc.strings.make_uri(filepath)
|
||||
vlc.playlist.add({{path=uri}})
|
||||
vlc.playlist.clear()
|
||||
vlc.playlist.enqueue({{path=uri}})
|
||||
vlc.playlist.next()
|
||||
return "load-file-attempted\n"
|
||||
end
|
||||
|
||||
|
||||
BIN
resources/shield_edit.png
Normal file
|
After Width: | Height: | Size: 773 B |
BIN
resources/world_add.png
Normal file
|
After Width: | Height: | Size: 940 B |
@ -1,4 +1,4 @@
|
||||
version = '1.3.4'
|
||||
milestone = 'Chami'
|
||||
release_number = '21'
|
||||
version = '1.4.0'
|
||||
milestone = 'Yoitsu'
|
||||
release_number = '27'
|
||||
projectURL = 'http://syncplay.pl/'
|
||||
|
||||
@ -4,8 +4,10 @@ import time
|
||||
import re
|
||||
import sys
|
||||
import ast
|
||||
import random
|
||||
import threading
|
||||
from twisted.internet.protocol import ClientFactory
|
||||
from twisted.internet import reactor, task
|
||||
from twisted.internet import reactor, task, defer, threads
|
||||
from functools import wraps
|
||||
from copy import deepcopy
|
||||
from syncplay.protocols import SyncClientProtocol
|
||||
@ -69,6 +71,7 @@ class SyncplayClient(object):
|
||||
self.lastControlPasswordAttempt = None
|
||||
self.serverVersion = "0.0.0"
|
||||
|
||||
self.lastRewindTime = None
|
||||
self.lastLeftTime = 0
|
||||
self.lastPausedOnLeaveTime = None
|
||||
self.lastLeftUser = u""
|
||||
@ -113,7 +116,12 @@ class SyncplayClient(object):
|
||||
self.autoplayTimer = task.LoopingCall(self.autoplayCountdown)
|
||||
self.autoplayTimeLeft = constants.AUTOPLAY_DELAY
|
||||
|
||||
self.__playerReady = defer.Deferred()
|
||||
|
||||
self._warnings = self._WarningManager(self._player, self.userlist, self.ui, self)
|
||||
self.fileSwitch = FileSwitchManager(self)
|
||||
self.playlist = SyncplayPlaylist(self)
|
||||
|
||||
if constants.LIST_RELATIVE_CONFIGS and self._config.has_key('loadedRelativePaths') and self._config['loadedRelativePaths']:
|
||||
paths = "; ".join(self._config['loadedRelativePaths'])
|
||||
self.ui.showMessage(getMessage("relative-config-notification").format(paths), noPlayer=True, noTimestamp=True)
|
||||
@ -121,7 +129,7 @@ class SyncplayClient(object):
|
||||
if constants.DEBUG_MODE and constants.WARN_ABOUT_MISSING_STRINGS:
|
||||
missingStrings = getMissingStrings()
|
||||
if missingStrings is not None and missingStrings is not "":
|
||||
self.ui.showDebugMessage("MISSING/UNUSED STRINGS DETECTED:\n{}".format(missingStrings))
|
||||
self.ui.showDebugMessage(u"MISSING/UNUSED STRINGS DETECTED:\n{}".format(missingStrings))
|
||||
|
||||
def initProtocol(self, protocol):
|
||||
self._protocol = protocol
|
||||
@ -136,6 +144,13 @@ class SyncplayClient(object):
|
||||
if not self._player.secondaryOSDSupported:
|
||||
constants.OSD_WARNING_MESSAGE_DURATION = constants.NO_SECONDARY_OSD_WARNING_DURATION
|
||||
self.scheduleAskPlayer()
|
||||
self.__playerReady.callback(player)
|
||||
|
||||
def addPlayerReadyCallback(self, lambdaToCall):
|
||||
self.__playerReady.addCallback(lambdaToCall)
|
||||
|
||||
def playerIsNotReady(self):
|
||||
return self._player is None
|
||||
|
||||
def scheduleAskPlayer(self, when=constants.PLAYER_ASK_DELAY):
|
||||
self._askPlayerTimer = task.LoopingCall(self.askPlayer)
|
||||
@ -163,12 +178,42 @@ class SyncplayClient(object):
|
||||
seeked = _playerDiff > constants.SEEK_THRESHOLD and _globalDiff > constants.SEEK_THRESHOLD
|
||||
return pauseChange, seeked
|
||||
|
||||
def rewindFile(self):
|
||||
self.setPosition(-1)
|
||||
self.establishRewindDoubleCheck()
|
||||
|
||||
def establishRewindDoubleCheck(self):
|
||||
reactor.callLater(0.5, self.doubleCheckRewindFile,)
|
||||
reactor.callLater(1, self.doubleCheckRewindFile,)
|
||||
reactor.callLater(1.5, self.doubleCheckRewindFile,)
|
||||
|
||||
def doubleCheckRewindFile(self):
|
||||
if self.getStoredPlayerPosition() > 5:
|
||||
self.setPosition(-1)
|
||||
self.ui.showDebugMessage("Rewinded after double-check")
|
||||
|
||||
def updatePlayerStatus(self, paused, position):
|
||||
position -= self.getUserOffset()
|
||||
pauseChange, seeked = self._determinePlayerStateChange(paused, position)
|
||||
self._playerPosition = position
|
||||
self._playerPaused = paused
|
||||
if pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION):
|
||||
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:
|
||||
self.playlist.advancePlaylistCheck()
|
||||
elif pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION):
|
||||
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:
|
||||
self._lastPlayerUpdate = time.time()
|
||||
if (pauseChange or seeked) and self._protocol:
|
||||
if seeked:
|
||||
self.playerPositionBeforeLastSeek = self.getGlobalPosition()
|
||||
self._protocol.sendState(self.getPlayerPosition(), self.getPlayerPaused(), seeked, None, True)
|
||||
|
||||
def _toggleReady(self, pauseChange, paused):
|
||||
if not self.userlist.currentUser.canControl():
|
||||
self._player.setPaused(self._globalPaused)
|
||||
self.toggleReady(manuallyInitiated=True)
|
||||
@ -191,12 +236,7 @@ class SyncplayClient(object):
|
||||
self.lastPausedOnLeaveTime = None
|
||||
else:
|
||||
self.changeReadyState(not self.getPlayerPaused(), manuallyInitiated=False)
|
||||
if self._lastGlobalUpdate:
|
||||
self._lastPlayerUpdate = time.time()
|
||||
if (pauseChange or seeked) and self._protocol:
|
||||
if seeked:
|
||||
self.playerPositionBeforeLastSeek = self.getGlobalPosition()
|
||||
self._protocol.sendState(self.getPlayerPosition(), self.getPlayerPaused(), seeked, None, True)
|
||||
return pauseChange
|
||||
|
||||
def getLocalState(self):
|
||||
paused = self.getPlayerPaused()
|
||||
@ -307,7 +347,8 @@ class SyncplayClient(object):
|
||||
self.behindFirstDetected = time.time()
|
||||
else:
|
||||
durationBehind = time.time() - self.behindFirstDetected
|
||||
if (durationBehind > (self._config['fastforwardThreshold']-constants.FASTFORWARD_BEHIND_THRESHOLD)) and (diff < (self._config['fastforwardThreshold'] * -1)):
|
||||
if (durationBehind > (self._config['fastforwardThreshold']-constants.FASTFORWARD_BEHIND_THRESHOLD))\
|
||||
and (diff < (self._config['fastforwardThreshold'] * -1)):
|
||||
madeChangeOnPlayer = self._fastforwardPlayerDueToTimeDifference(position, setBy)
|
||||
self.behindFirstDetected = time.time() + constants.FASTFORWARD_RESET_THRESHOLD
|
||||
else:
|
||||
@ -368,6 +409,9 @@ class SyncplayClient(object):
|
||||
position += diff
|
||||
return position
|
||||
|
||||
def getStoredPlayerPosition(self):
|
||||
return self._playerPosition if self._playerPosition is not None else None
|
||||
|
||||
def getPlayerPaused(self):
|
||||
if not self._lastPlayerUpdate:
|
||||
if self._lastGlobalUpdate:
|
||||
@ -390,6 +434,7 @@ class SyncplayClient(object):
|
||||
return self._globalPaused
|
||||
|
||||
def updateFile(self, filename, duration, path):
|
||||
newPath = u""
|
||||
if utils.isURL(path):
|
||||
filename = path
|
||||
|
||||
@ -403,9 +448,54 @@ class SyncplayClient(object):
|
||||
size = os.path.getsize(path)
|
||||
except:
|
||||
size = 0
|
||||
if not utils.isURL(path) and os.path.exists(path):
|
||||
self.fileSwitch.notifyUserIfFileNotInMediaDirectory(filename, path)
|
||||
filename, size = self.__executePrivacySettings(filename, size)
|
||||
self.userlist.currentUser.setFile(filename, duration, size, path)
|
||||
self.sendFile()
|
||||
self.playlist.changeToPlaylistIndexFromFilename(filename)
|
||||
|
||||
def setTrustedDomains(self, newTrustedDomains):
|
||||
from syncplay.ui.ConfigurationGetter import ConfigurationGetter
|
||||
ConfigurationGetter().setConfigOption("trustedDomains", newTrustedDomains)
|
||||
self._config['trustedDomains'] = newTrustedDomains
|
||||
self.fileSwitchFoundFiles()
|
||||
|
||||
def isURITrusted(self, URIToTest):
|
||||
for trustedProtocol in constants.TRUSTABLE_WEB_PROTOCOLS:
|
||||
if URIToTest.startswith(trustedProtocol):
|
||||
if self._config['onlySwitchToTrustedDomains']:
|
||||
if self._config['trustedDomains']:
|
||||
for trustedDomain in self._config['trustedDomains']:
|
||||
trustableURI = ''.join([trustedProtocol,trustedDomain,u"/"])
|
||||
if URIToTest.startswith(trustableURI):
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
def openFile(self, filePath, resetPosition=False):
|
||||
self._player.openFile(filePath, resetPosition)
|
||||
if resetPosition:
|
||||
self.establishRewindDoubleCheck()
|
||||
self.lastRewindTime = time.time()
|
||||
|
||||
def fileSwitchFoundFiles(self):
|
||||
self.ui.fileSwitchFoundFiles()
|
||||
self.playlist.loadCurrentPlaylistIndex()
|
||||
|
||||
def setPlaylistIndex(self, index):
|
||||
self._protocol.setPlaylistIndex(index)
|
||||
|
||||
def changeToPlaylistIndex(self, *args, **kwargs):
|
||||
self.playlist.changeToPlaylistIndex(*args, **kwargs)
|
||||
|
||||
def loopSingleFiles(self):
|
||||
return self._config["loopSingleFiles"]
|
||||
|
||||
def isPlaylistLoopingEnabled(self):
|
||||
return self._config["loopAtEndOfPlaylist"]
|
||||
|
||||
def __executePrivacySettings(self, filename, size):
|
||||
if self._config['filenamePrivacyMode'] == PRIVACY_SENDHASHED_MODE:
|
||||
@ -442,8 +532,6 @@ class SyncplayClient(object):
|
||||
if username and username <> "":
|
||||
self.userlist.currentUser.username = username
|
||||
else:
|
||||
import random
|
||||
random.seed()
|
||||
random_number = random.randrange(1000, 9999)
|
||||
self.userlist.currentUser.username = "Anonymous" + str(random_number) # Not localised as this would give away locale
|
||||
|
||||
@ -469,6 +557,12 @@ class SyncplayClient(object):
|
||||
if storedRoomPassword:
|
||||
self.identifyAsController(storedRoomPassword)
|
||||
|
||||
def isConnectedAndInARoom(self):
|
||||
return self._protocol and self._protocol.logged and self.userlist.currentUser.room
|
||||
|
||||
def sharedPlaylistIsEnabled(self):
|
||||
return self._config['sharedPlaylistEnabled']
|
||||
|
||||
def connected(self):
|
||||
readyState = self._config['readyAtStart'] if self.userlist.currentUser.isReady() is None else self.userlist.currentUser.isReady()
|
||||
self._protocol.setReady(readyState, manuallyInitiated=False)
|
||||
@ -493,6 +587,9 @@ class SyncplayClient(object):
|
||||
def setPosition(self, position):
|
||||
if self._lastPlayerUpdate:
|
||||
self._lastPlayerUpdate = time.time()
|
||||
if self.lastRewindTime is not None and abs(time.time() - self.lastRewindTime) < 1.0 and position > 5:
|
||||
self.ui.showDebugMessage("Ignored seek to {} after rewind".format(position))
|
||||
return
|
||||
position += self.getUserOffset()
|
||||
if self._player and self.userlist.currentUser.file:
|
||||
if position < 0:
|
||||
@ -550,6 +647,14 @@ class SyncplayClient(object):
|
||||
return wrapper
|
||||
return requireMinVersionDecorator
|
||||
|
||||
def changePlaylistEnabledState(self, newState):
|
||||
oldState = self.sharedPlaylistIsEnabled()
|
||||
from syncplay.ui.ConfigurationGetter import ConfigurationGetter
|
||||
ConfigurationGetter().setConfigOption("sharedPlaylistEnabled", newState)
|
||||
self._config["sharedPlaylistEnabled"] = newState
|
||||
if oldState == False and newState == True:
|
||||
self.playlist.loadCurrentPlaylistIndex()
|
||||
|
||||
def changeAutoplayState(self, newState):
|
||||
self.autoPlay = newState
|
||||
self.autoplayCheck()
|
||||
@ -576,13 +681,16 @@ class SyncplayClient(object):
|
||||
return True
|
||||
elif unpauseAction == constants.UNPAUSE_IFOTHERSREADY_MODE and self.userlist.areAllOtherUsersInRoomReady():
|
||||
return True
|
||||
elif unpauseAction == constants.UNPAUSE_IFMINUSERSREADY_MODE and self.userlist.areAllOtherUsersInRoomReady() and self.autoPlayThreshold and self.userlist.usersInRoomCount() >= self.autoPlayThreshold:
|
||||
elif unpauseAction == constants.UNPAUSE_IFMINUSERSREADY_MODE and self.userlist.areAllOtherUsersInRoomReady()\
|
||||
and self.autoPlayThreshold and self.userlist.usersInRoomCount() >= self.autoPlayThreshold:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def autoplayConditionsMet(self):
|
||||
return self._playerPaused and self.autoPlay and self.userlist.currentUser.canControl() and self.userlist.isReadinessSupported() and self.userlist.areAllUsersInRoomReady() and self.autoPlayThreshold and self.userlist.usersInRoomCount() >= self.autoPlayThreshold
|
||||
return self._playerPaused and self.autoPlay 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
|
||||
|
||||
def autoplayTimerIsRunning(self):
|
||||
return self.autoplayTimer.running
|
||||
@ -601,7 +709,9 @@ class SyncplayClient(object):
|
||||
if not self.autoplayConditionsMet():
|
||||
self.stopAutoplayCountdown()
|
||||
return
|
||||
countdownMessage = u"{}{}{}".format(getMessage("all-users-ready").format(self.userlist.readyUserCount()),self._player.osdMessageSeparator, getMessage("autoplaying-notification").format(int(self.autoplayTimeLeft)))
|
||||
allReadyMessage = getMessage("all-users-ready").format(self.userlist.readyUserCount())
|
||||
autoplayingMessage = getMessage("autoplaying-notification").format(int(self.autoplayTimeLeft))
|
||||
countdownMessage = u"{}{}{}".format(allReadyMessage,self._player.osdMessageSeparator, autoplayingMessage)
|
||||
self.ui.showOSDMessage(countdownMessage, 1, secondaryOSD=True)
|
||||
if self.autoplayTimeLeft <= 0:
|
||||
self.setPaused(False)
|
||||
@ -692,9 +802,11 @@ class SyncplayClient(object):
|
||||
response = json.loads(response)
|
||||
publicServers = None
|
||||
if response["public-servers"]:
|
||||
publicServers = response["public-servers"].replace("”","'").replace(":’","'").replace("’","'").replace("′","'").replace("\n","").replace("\r","")
|
||||
publicServers = response["public-servers"].\
|
||||
replace("”","'").replace(":’","'").replace("’","'").replace("′","'").replace("\n","").replace("\r","")
|
||||
publicServers = ast.literal_eval(publicServers)
|
||||
return response["version-status"], response["version-message"] if response.has_key("version-message") else None, response["version-url"] if response.has_key("version-url") else None, publicServers
|
||||
return response["version-status"], response["version-message"] if response.has_key("version-message")\
|
||||
else None, response["version-url"] if response.has_key("version-url") else None, publicServers
|
||||
except:
|
||||
return "failed", getMessage("update-check-failed-notification").format(syncplay.version), constants.SYNCPLAY_DOWNLOAD_URL, None
|
||||
|
||||
@ -779,9 +891,11 @@ class SyncplayClient(object):
|
||||
fileDifferencesMessage = getMessage("room-file-differences").format(fileDifferencesForRoom)
|
||||
if self._userlist.currentUser.canControl() and self._userlist.isReadinessSupported():
|
||||
if self._userlist.areAllUsersInRoomReady():
|
||||
osdMessage = u"{}{}{}".format(fileDifferencesMessage, self._client._player.osdMessageSeparator, getMessage("all-users-ready").format(self._userlist.readyUserCount()))
|
||||
allReadyMessage = getMessage("all-users-ready").format(self._userlist.readyUserCount())
|
||||
osdMessage = u"{}{}{}".format(fileDifferencesMessage, self._client._player.osdMessageSeparator, allReadyMessage)
|
||||
else:
|
||||
osdMessage = u"{}{}{}".format(fileDifferencesMessage, self._client._player.osdMessageSeparator, getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady()))
|
||||
notAllReadyMessage = getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady())
|
||||
osdMessage = u"{}{}{}".format(fileDifferencesMessage, self._client._player.osdMessageSeparator, notAllReadyMessage)
|
||||
else:
|
||||
osdMessage = fileDifferencesMessage
|
||||
elif self._userlist.isReadinessSupported():
|
||||
@ -845,9 +959,9 @@ class SyncplayUser(object):
|
||||
|
||||
def __repr__(self, *args, **kwargs):
|
||||
if self.file:
|
||||
return "{}: {} ({}, {})".format(self.username, self.file['name'], self.file['duration'], self.file['size'])
|
||||
return u"{}: {} ({}, {})".format(self.username, self.file['name'], self.file['duration'], self.file['size'])
|
||||
else:
|
||||
return "{}".format(self.username)
|
||||
return u"{}".format(self.username)
|
||||
|
||||
def setControllerStatus(self, isController):
|
||||
self._controller = isController
|
||||
@ -1011,13 +1125,19 @@ class SyncplayUserlist(object):
|
||||
user = self._users[username]
|
||||
user.setControllerStatus(True)
|
||||
|
||||
def areAllUsersInRoomReady(self):
|
||||
def areAllUsersInRoomReady(self, requireSameFilenames=False):
|
||||
if not self.currentUser.canControl():
|
||||
return True
|
||||
if not self.currentUser.isReady():
|
||||
return False
|
||||
for user in self._users.itervalues():
|
||||
if user.room == self.currentUser.room and user.isReadyWithFile() == False:
|
||||
if user.room == self.currentUser.room:
|
||||
if user.isReadyWithFile() == False:
|
||||
return False
|
||||
elif requireSameFilenames and\
|
||||
(self.currentUser.file is None
|
||||
or user.file is None
|
||||
or not utils.sameFilename(self.currentUser.file['name'], user.file['name'])):
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -1155,6 +1275,15 @@ class UiManager(object):
|
||||
self.lastSecondaryOSDEndTime = None
|
||||
self.lastError = ""
|
||||
|
||||
def setPlaylist(self, newPlaylist, newIndexFilename=None):
|
||||
self.__ui.setPlaylist(newPlaylist, newIndexFilename)
|
||||
|
||||
def setPlaylistIndexFilename(self, filename):
|
||||
self.__ui.setPlaylistIndexFilename(filename)
|
||||
|
||||
def fileSwitchFoundFiles(self):
|
||||
self.__ui.fileSwitchFoundFiles()
|
||||
|
||||
def showDebugMessage(self, message):
|
||||
if constants.DEBUG_MODE and message.rstrip():
|
||||
sys.stderr.write("{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()),message.rstrip()))
|
||||
@ -1215,4 +1344,422 @@ class UiManager(object):
|
||||
def drop(self):
|
||||
self.__ui.drop()
|
||||
|
||||
class SyncplayPlaylist():
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
self._ui = self._client.ui
|
||||
self._previousPlaylist = None
|
||||
self._previousPlaylistRoom = None
|
||||
self._playlist = []
|
||||
self._playlistIndex = None
|
||||
self.addedChangeListCallback = False
|
||||
self._lastPlaylistIndexChange = time.time()
|
||||
|
||||
def needsSharedPlaylistsEnabled(f): # @NoSelf
|
||||
@wraps(f)
|
||||
def wrapper(self, *args, **kwds):
|
||||
if not self._client.sharedPlaylistIsEnabled():
|
||||
self._ui.showDebugMessage(u"Tried to use shared playlists when it was disabled!")
|
||||
return
|
||||
return f(self, *args, **kwds)
|
||||
return wrapper
|
||||
|
||||
def changeToPlaylistIndexFromFilename(self, filename):
|
||||
try:
|
||||
index = self._playlist.index(filename)
|
||||
if index <> self._playlistIndex:
|
||||
self.changeToPlaylistIndex(index)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def changeToPlaylistIndex(self, index, username = None):
|
||||
if self._playlist is None or len(self._playlist) == 0:
|
||||
return
|
||||
if index is None:
|
||||
return
|
||||
if username is None and not self._client.sharedPlaylistIsEnabled():
|
||||
return
|
||||
self._lastPlaylistIndexChange = time.time()
|
||||
if self._client.playerIsNotReady():
|
||||
if not self.addedChangeListCallback:
|
||||
self.addedChangeListCallback = True
|
||||
self._client.addPlayerReadyCallback(lambda x: self.changeToPlaylistIndex(index, username))
|
||||
return
|
||||
try:
|
||||
filename = self._playlist[index]
|
||||
self._ui.setPlaylistIndexFilename(filename)
|
||||
if not self._client.sharedPlaylistIsEnabled():
|
||||
self._playlistIndex = index
|
||||
if username is not None and self._client.userlist.currentUser.file and filename == self._client.userlist.currentUser.file['name']:
|
||||
return
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
self._playlistIndex = index
|
||||
if username is None:
|
||||
if self._client.isConnectedAndInARoom() and self._client.sharedPlaylistIsEnabled():
|
||||
self._client.setPlaylistIndex(index)
|
||||
else:
|
||||
self._ui.showMessage(getMessage("playlist-selection-changed-notification").format(username))
|
||||
self.switchToNewPlaylistIndex(index)
|
||||
|
||||
@needsSharedPlaylistsEnabled
|
||||
def switchToNewPlaylistIndex(self, index, resetPosition=False):
|
||||
self._lastPlaylistIndexChange = time.time()
|
||||
if self._client.playerIsNotReady():
|
||||
self._client.addPlayerReadyCallback(lambda x: self.switchToNewPlaylistIndex(index, resetPosition))
|
||||
return
|
||||
|
||||
try:
|
||||
if index is None:
|
||||
self._ui.showDebugMessage(u"Cannot switch to None index in playlist")
|
||||
return
|
||||
filename = self._playlist[index]
|
||||
# TODO: Handle isse with index being None
|
||||
if utils.isURL(filename):
|
||||
if self._client.isURITrusted(filename):
|
||||
self._client.openFile(filename, resetPosition=resetPosition)
|
||||
else:
|
||||
self._ui.showErrorMessage(getMessage("cannot-add-unsafe-path-error").format(filename))
|
||||
return
|
||||
else:
|
||||
path = self._client.fileSwitch.findFilepath(filename, highPriority=True)
|
||||
if path:
|
||||
self._client.openFile(path, resetPosition)
|
||||
else:
|
||||
self._ui.showErrorMessage(getMessage("cannot-find-file-for-playlist-switch-error").format(filename))
|
||||
return
|
||||
except IndexError:
|
||||
self._ui.showDebugMessage("Could not change playlist index due to IndexError")
|
||||
|
||||
def _getValidIndexFromNewPlaylist(self, newPlaylist=None):
|
||||
if self._playlistIndex is None or not newPlaylist or len(newPlaylist) <= 1:
|
||||
return 0
|
||||
|
||||
i = self._playlistIndex
|
||||
while i <= len(self._playlist):
|
||||
try:
|
||||
filename = self._playlist[i]
|
||||
validIndex = newPlaylist.index(filename)
|
||||
return validIndex
|
||||
except:
|
||||
i += 1
|
||||
|
||||
i = self._playlistIndex
|
||||
while i > 0:
|
||||
try:
|
||||
filename = self._playlist[i]
|
||||
validIndex = newPlaylist.index(filename)
|
||||
return validIndex+1 if validIndex < len(newPlaylist)-1 else validIndex
|
||||
except:
|
||||
i -= 1
|
||||
return 0
|
||||
|
||||
def _getFilenameFromIndexInGivenPlaylist(self, _playlist, _index):
|
||||
if not _index or not _playlist:
|
||||
return None
|
||||
filename = _playlist[_index] if len(_playlist) > _index else None
|
||||
return filename
|
||||
|
||||
def changePlaylist(self, files, username = None, resetIndex=False):
|
||||
if self._playlist == files:
|
||||
if self._playlistIndex <> 0 and resetIndex:
|
||||
self.changeToPlaylistIndex(0)
|
||||
return
|
||||
|
||||
if resetIndex:
|
||||
newIndex = 0
|
||||
filename = files[0] if files and len(files) > 0 else None
|
||||
else:
|
||||
newIndex = self._getValidIndexFromNewPlaylist(files)
|
||||
filename = self._getFilenameFromIndexInGivenPlaylist(files, newIndex)
|
||||
|
||||
self._updateUndoPlaylistBuffer(newPlaylist=files, newRoom=self._client.userlist.currentUser.room)
|
||||
self._playlist = files
|
||||
|
||||
if username is None:
|
||||
if self._client.isConnectedAndInARoom() and self._client.sharedPlaylistIsEnabled():
|
||||
self._client._protocol.setPlaylist(files)
|
||||
self.changeToPlaylistIndex(newIndex)
|
||||
self._ui.setPlaylist(self._playlist, filename)
|
||||
self._ui.showMessage(getMessage("playlist-contents-changed-notification").format(self._client.getUsername()))
|
||||
else:
|
||||
self._ui.setPlaylist(self._playlist)
|
||||
self._ui.showMessage(getMessage("playlist-contents-changed-notification").format(username))
|
||||
|
||||
@needsSharedPlaylistsEnabled
|
||||
def undoPlaylistChange(self):
|
||||
if self.canUndoPlaylist(self._playlist):
|
||||
newPlaylist = self._getPreviousPlaylist()
|
||||
self.changePlaylist(newPlaylist, username=None)
|
||||
|
||||
@needsSharedPlaylistsEnabled
|
||||
def shufflePlaylist(self):
|
||||
if self._playlist and len(self._playlist) > 0:
|
||||
shuffledPlaylist = deepcopy(self._playlist)
|
||||
random.shuffle(shuffledPlaylist)
|
||||
self.changePlaylist(shuffledPlaylist, username=None, resetIndex=True)
|
||||
|
||||
def canUndoPlaylist(self, currentPlaylist):
|
||||
return self._previousPlaylist is not None and currentPlaylist <> self._previousPlaylist
|
||||
|
||||
def loadCurrentPlaylistIndex(self):
|
||||
if self._notPlayingCurrentIndex():
|
||||
self.switchToNewPlaylistIndex(self._playlistIndex)
|
||||
|
||||
@needsSharedPlaylistsEnabled
|
||||
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():
|
||||
self.loadNextFileInPlaylist()
|
||||
|
||||
def notJustChangedPlaylist(self):
|
||||
secondsSinceLastChange = time.time() - self._lastPlaylistIndexChange
|
||||
return secondsSinceLastChange > constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD
|
||||
|
||||
@needsSharedPlaylistsEnabled
|
||||
def loadNextFileInPlaylist(self):
|
||||
if self._notPlayingCurrentIndex():
|
||||
return
|
||||
|
||||
if len(self._playlist) == 1 and self._client.loopSingleFiles():
|
||||
self._client.rewindFile()
|
||||
self._client.setPaused(False)
|
||||
reactor.callLater(0.5, self._client.setPaused, False,)
|
||||
|
||||
elif self._thereIsNextPlaylistIndex():
|
||||
self.switchToNewPlaylistIndex(self._nextPlaylistIndex(), resetPosition=True)
|
||||
|
||||
def _updateUndoPlaylistBuffer(self, newPlaylist, newRoom):
|
||||
if self._playlistBufferIsFromOldRoom(newRoom):
|
||||
self._movePlaylistBufferToNewRoom(newRoom)
|
||||
elif self._playlistBufferNeedsUpdating(newPlaylist):
|
||||
self._previousPlaylist = self._playlist
|
||||
|
||||
def _getPreviousPlaylist(self):
|
||||
return self._previousPlaylist
|
||||
|
||||
def _notPlayingCurrentIndex(self):
|
||||
if self._playlistIndex is None or self._playlist is None or len(self._playlist) <= self._playlistIndex:
|
||||
self._ui.showDebugMessage(u"Not playing current index - Index none or length issue")
|
||||
return True
|
||||
currentPlaylistFilename = self._playlist[self._playlistIndex]
|
||||
if self._client.userlist.currentUser.file and currentPlaylistFilename == self._client.userlist.currentUser.file['name']:
|
||||
return False
|
||||
else:
|
||||
self._ui.showDebugMessage(u"Not playing current index - Filename mismatch or no file")
|
||||
return True
|
||||
|
||||
def _thereIsNextPlaylistIndex(self):
|
||||
if self._playlistIndex is None:
|
||||
return False
|
||||
elif len(self._playlist) == 1 and not self._client.loopSingleFiles():
|
||||
return False
|
||||
elif self._playlistIsAtEnd():
|
||||
return self._client.isPlaylistLoopingEnabled()
|
||||
else:
|
||||
return True
|
||||
|
||||
def _nextPlaylistIndex(self):
|
||||
if self._playlistIsAtEnd():
|
||||
return 0
|
||||
else:
|
||||
return self._playlistIndex+1
|
||||
|
||||
def _playlistIsAtEnd(self):
|
||||
return len(self._playlist) <= self._playlistIndex+1
|
||||
|
||||
def _playlistBufferIsFromOldRoom(self, newRoom):
|
||||
return self._previousPlaylistRoom <> newRoom
|
||||
|
||||
def _movePlaylistBufferToNewRoom(self, currentRoom):
|
||||
self._previousPlaylist = None
|
||||
self._previousPlaylistRoom = currentRoom
|
||||
|
||||
def _playlistBufferNeedsUpdating(self, newPlaylist):
|
||||
return self._previousPlaylist <> self._playlist and self._playlist <> newPlaylist
|
||||
|
||||
class FileSwitchManager(object):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
self.mediaFilesCache = {}
|
||||
self.filenameWatchlist = []
|
||||
self.currentDirectory = None
|
||||
self.mediaDirectories = None
|
||||
self.lock = threading.Lock()
|
||||
self.folderSearchEnabled = True
|
||||
self.directorySearchError = None
|
||||
self.newInfo = False
|
||||
self.currentlyUpdating = False
|
||||
self.newWatchlist = []
|
||||
self.fileSwitchTimer = task.LoopingCall(self.updateInfo)
|
||||
self.fileSwitchTimer.start(constants.FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL, True)
|
||||
self.mediaDirectoriesNotFound = []
|
||||
|
||||
def setClient(self, newClient):
|
||||
self.client = newClient
|
||||
|
||||
def setCurrentDirectory(self, curDir):
|
||||
self.currentDirectory = curDir
|
||||
|
||||
def changeMediaDirectories(self, mediaDirs):
|
||||
from syncplay.ui.ConfigurationGetter import ConfigurationGetter
|
||||
ConfigurationGetter().setConfigOption("mediaSearchDirectories", mediaDirs)
|
||||
self._client._config["mediaSearchDirectories"] = mediaDirs
|
||||
self._client.ui.showMessage(getMessage("media-directory-list-updated-notification"))
|
||||
self.mediaDirectoriesNotFound = []
|
||||
self.folderSearchEnabled = True
|
||||
self.setMediaDirectories(mediaDirs)
|
||||
if mediaDirs == "":
|
||||
self._client.ui.showErrorMessage(getMessage("no-media-directories-error"))
|
||||
self.mediaFilesCache = {}
|
||||
self.newInfo = True
|
||||
self.checkForFileSwitchUpdate()
|
||||
|
||||
def setMediaDirectories(self, mediaDirs):
|
||||
self.mediaDirectories = mediaDirs
|
||||
self.updateInfo()
|
||||
|
||||
def checkForFileSwitchUpdate(self):
|
||||
if self.newInfo:
|
||||
self.newInfo = False
|
||||
self.infoUpdated()
|
||||
if self.directorySearchError:
|
||||
self._client.ui.showErrorMessage(self.directorySearchError)
|
||||
self.directorySearchError = None
|
||||
|
||||
def updateInfo(self):
|
||||
if not self.currentlyUpdating and self.mediaDirectories:
|
||||
threads.deferToThread(self._updateInfoThread).addCallback(lambda x: self.checkForFileSwitchUpdate())
|
||||
|
||||
def setFilenameWatchlist(self, unfoundFilenames):
|
||||
self.filenameWatchlist = unfoundFilenames
|
||||
|
||||
def _updateInfoThread(self):
|
||||
with self.lock:
|
||||
try:
|
||||
self.currentlyUpdating = True
|
||||
dirsToSearch = self.mediaDirectories
|
||||
|
||||
if dirsToSearch:
|
||||
# Spin up hard drives to prevent premature timeout
|
||||
randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+u".txt"
|
||||
for directory in dirsToSearch:
|
||||
if not os.path.isdir(directory):
|
||||
self.directorySearchError = getMessage("cannot-find-directory-error").format(directory)
|
||||
|
||||
startTime = time.time()
|
||||
if os.path.isfile(os.path.join(directory, randomFilename)):
|
||||
randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+u".txt"
|
||||
if time.time() - startTime > constants.FOLDER_SEARCH_FIRST_FILE_TIMEOUT:
|
||||
self.folderSearchEnabled = False
|
||||
self.directorySearchError = getMessage("folder-search-first-file-timeout-error").format(directory)
|
||||
return
|
||||
|
||||
# Actual directory search
|
||||
newMediaFilesCache = {}
|
||||
startTime = time.time()
|
||||
for directory in dirsToSearch:
|
||||
for root, dirs, files in os.walk(directory):
|
||||
newMediaFilesCache[root] = files
|
||||
if time.time() - startTime > constants.FOLDER_SEARCH_TIMEOUT:
|
||||
self.directorySearchError = getMessage("folder-search-timeout-error").format(directory)
|
||||
self.folderSearchEnabled = False
|
||||
return
|
||||
|
||||
if self.mediaFilesCache <> newMediaFilesCache:
|
||||
self.mediaFilesCache = newMediaFilesCache
|
||||
self.newInfo = True
|
||||
finally:
|
||||
self.currentlyUpdating = False
|
||||
|
||||
def infoUpdated(self):
|
||||
self._client.fileSwitchFoundFiles()
|
||||
|
||||
def findFilepath(self, filename, highPriority=False):
|
||||
if filename is None:
|
||||
return
|
||||
|
||||
if self.mediaFilesCache is not None:
|
||||
for directory in self.mediaFilesCache:
|
||||
files = self.mediaFilesCache[directory]
|
||||
if len(files) > 0 and filename in files:
|
||||
filepath = os.path.join(directory, filename)
|
||||
if os.path.isfile(filepath):
|
||||
return filepath
|
||||
|
||||
if highPriority and self.folderSearchEnabled:
|
||||
directoryList = self.mediaDirectories
|
||||
# Spin up hard drives to prevent premature timeout
|
||||
randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+u".txt"
|
||||
for directory in directoryList:
|
||||
startTime = time.time()
|
||||
if os.path.isfile(os.path.join(directory, randomFilename)):
|
||||
randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+u".txt"
|
||||
if not self.folderSearchEnabled:
|
||||
return
|
||||
if time.time() - startTime > constants.FOLDER_SEARCH_FIRST_FILE_TIMEOUT:
|
||||
return
|
||||
|
||||
startTime = time.time()
|
||||
if filename and directoryList:
|
||||
for directory in directoryList:
|
||||
for root, dirs, files in os.walk(directory):
|
||||
if filename in files:
|
||||
return os.path.join(root,filename)
|
||||
if time.time() - startTime > constants.FOLDER_SEARCH_TIMEOUT:
|
||||
self.folderSearchEnabled = False
|
||||
self.directorySearchError = getMessage("folder-search-timeout-error").format(directory)
|
||||
return None
|
||||
return None
|
||||
|
||||
def areWatchedFilenamesInCache(self):
|
||||
if self.filenameWatchlist is not None:
|
||||
for filename in self.filenameWatchlist:
|
||||
if self.isFilenameInCache(filename):
|
||||
return True
|
||||
|
||||
def isFilenameInCache(self, filename):
|
||||
if filename is not None and self.mediaFilesCache is not None:
|
||||
for directory in self.mediaFilesCache:
|
||||
files = self.mediaFilesCache[directory]
|
||||
if filename in files:
|
||||
return True
|
||||
|
||||
def getDirectoryOfFilenameInCache(self, filename):
|
||||
if filename is not None and self.mediaFilesCache is not None:
|
||||
for directory in self.mediaFilesCache:
|
||||
files = self.mediaFilesCache[directory]
|
||||
if filename in files:
|
||||
return directory
|
||||
return None
|
||||
|
||||
def isDirectoryInList(self, directoryToFind, folderList):
|
||||
if directoryToFind and folderList:
|
||||
normedDirectoryToFind = os.path.normcase(os.path.normpath(directoryToFind))
|
||||
for listedFolder in folderList:
|
||||
normedListedFolder = os.path.normcase(os.path.normpath(listedFolder))
|
||||
if normedDirectoryToFind.startswith(normedListedFolder):
|
||||
return True
|
||||
return False
|
||||
|
||||
def notifyUserIfFileNotInMediaDirectory(self, filenameToFind, path):
|
||||
directoryToFind = os.path.dirname(path)
|
||||
if directoryToFind in self.mediaDirectoriesNotFound:
|
||||
return
|
||||
if self.mediaDirectories is not None and self.mediaFilesCache is not None:
|
||||
if directoryToFind in self.mediaFilesCache:
|
||||
return
|
||||
for directory in self.mediaFilesCache:
|
||||
files = self.mediaFilesCache[directory]
|
||||
if filenameToFind in files:
|
||||
return
|
||||
if directoryToFind in self.mediaFilesCache:
|
||||
return
|
||||
if self.isDirectoryInList(directoryToFind, self.mediaDirectories):
|
||||
return
|
||||
self._client.ui.showErrorMessage(getMessage("added-file-not-in-media-directory-error").format(directoryToFind))
|
||||
self.mediaDirectoriesNotFound.append(directoryToFind)
|
||||
@ -17,6 +17,8 @@ SHOW_TOOLTIPS = True
|
||||
WARN_ABOUT_MISSING_STRINGS = False # (If debug mode is enabled)
|
||||
FALLBACK_INITIAL_LANGUAGE = "en"
|
||||
FALLBACK_PUBLIC_SYNCPLAY_SERVERS = [[u'syncplay.pl:8995 (France)', u'syncplay.pl:8995'],[u'syncplay.pl:8996 (France)', u'syncplay.pl:8996'],[u'syncplay.pl:8997 (France)', u'syncplay.pl:8997'],[u'syncplay.pl:8998 (France)', u'syncplay.pl:8998'],[u'syncplay.pl:8999 (France)', u'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)
|
||||
|
||||
#Overriden by config
|
||||
SHOW_OSD = True # Sends Syncplay messages to media player OSD
|
||||
@ -51,8 +53,9 @@ AUTOPLAY_DELAY = 3.0
|
||||
SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear on some media players
|
||||
|
||||
# Options for the File Switch feature:
|
||||
FOLDER_SEARCH_TIMEOUT = 60.0 # Secs - How long to wait until searches in folder to update cache are aborted (may be longer than this if hard drive needs to spin up)
|
||||
FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL = 120.0 # Secs - Frequency of updating cache when someone is playing a file not in current cache
|
||||
FOLDER_SEARCH_FIRST_FILE_TIMEOUT = 10.0 # Secs - How long to wait to find the first file in folder search (to take account of HDD spin up)
|
||||
FOLDER_SEARCH_TIMEOUT = 3.0 # Secs - How long to wait until searches in folder to update cache are aborted (after first file is found)
|
||||
FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL = 30.0 # Secs - Frequency of updating cache when someone is playing a file not in current cache
|
||||
|
||||
#Usually there's no need to adjust these
|
||||
LAST_PAUSED_DIFF_THRESHOLD = 2
|
||||
@ -69,7 +72,7 @@ COMMANDS_AUTH = ['a','auth']
|
||||
COMMANDS_TOGGLE = ['t','toggle']
|
||||
MPC_MIN_VER = "1.6.4"
|
||||
VLC_MIN_VERSION = "2.2.1"
|
||||
VLC_INTERFACE_MIN_VERSION = "0.2.8"
|
||||
VLC_INTERFACE_MIN_VERSION = "0.2.9"
|
||||
VLC_LATENCY_ERROR_THRESHOLD = 2.0
|
||||
MPV_UNRESPONSIVE_THRESHOLD = 60.0
|
||||
CONTROLLED_ROOMS_MIN_VERSION = "1.3.0"
|
||||
@ -107,6 +110,8 @@ MPV_ICONPATH = "mpv.png"
|
||||
MPC_ICONPATH = "mpc-hc.png"
|
||||
MPC64_ICONPATH = "mpc-hc64.png"
|
||||
|
||||
MPV_ERROR_MESSAGES_TO_REPEAT = ['[ytdl_hook] Your version of youtube-dl is too old', '[ytdl_hook] youtube-dl failed', 'Failed to recognize file format.']
|
||||
|
||||
#Changing these is usually not something you're looking for
|
||||
PLAYER_ASK_DELAY = 0.1
|
||||
PING_MOVING_AVERAGE_WEIGHT = 0.85
|
||||
@ -116,7 +121,10 @@ MPC_RETRY_WAIT_TIME = 0.01
|
||||
MPC_MAX_RETRIES = 30
|
||||
MPC_PAUSE_TOGGLE_DELAY = 0.05
|
||||
MPV_NEWFILE_IGNORE_TIME = 1
|
||||
MPV_LOCK_WAIT_TIME = 0.2
|
||||
MPV_SENDMESSAGE_COOLDOWN_TIME = 0.05
|
||||
MPV_MAX_NEWFILE_COOLDOWN_TIME = 3
|
||||
STREAM_ADDITIONAL_IGNORE_TIME = 10
|
||||
MPV_LOCK_WAIT_TIME = 0.05
|
||||
VLC_OPEN_MAX_WAIT_TIME = 15
|
||||
VLC_MIN_PORT = 10000
|
||||
VLC_MAX_PORT = 55000
|
||||
@ -136,6 +144,7 @@ STYLE_ERRORNOTIFICATION = "color: red;"
|
||||
STYLE_DIFFERENTITEM_COLOR = 'red'
|
||||
STYLE_NOFILEITEM_COLOR = 'blue'
|
||||
STYLE_NOTCONTROLLER_COLOR = 'grey'
|
||||
STYLE_UNTRUSTEDITEM_COLOR = 'purple'
|
||||
|
||||
USERLIST_GUI_USERNAME_OFFSET = 21 # Pixels
|
||||
USERLIST_GUI_USERNAME_COLUMN = 0
|
||||
@ -149,6 +158,8 @@ MPV_NEW_VERSION = False
|
||||
VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek',
|
||||
'--play-and-pause', '--start-time=0']
|
||||
VLC_SLAVE_NONOSX_ARGS = ['--no-one-instance', '--no-one-instance-when-started-from-file']
|
||||
MPV_SUPERSEDE_IF_DUPLICATE_COMMANDS = ["no-osd set time-pos ", "loadfile "]
|
||||
MPV_REMOVE_BOTH_IF_DUPLICATE_COMMANDS = ["cycle pause"]
|
||||
MPLAYER_ANSWER_REGEX = "^ANS_([a-zA-Z_-]+)=(.+)$|^(Exiting)\.\.\. \((.+)\)$"
|
||||
VLC_ANSWER_REGEX = r"(?:^(?P<command>[a-zA-Z_]+)(?:\: )?(?P<argument>.*))"
|
||||
UI_COMMAND_REGEX = r"^(?P<command>[^\ ]+)(?:\ (?P<parameter>.+))?"
|
||||
@ -176,9 +187,13 @@ FILEITEM_SWITCH_ROLE = 1
|
||||
FILEITEM_SWITCH_NO_SWITCH = 0
|
||||
FILEITEM_SWITCH_FILE_SWITCH = 1
|
||||
FILEITEM_SWITCH_STREAM_SWITCH = 2
|
||||
PLAYLISTITEM_CURRENTLYPLAYING_ROLE = 3
|
||||
|
||||
SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params
|
||||
SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/"
|
||||
SYNCPLAY_PUBLIC_SERVER_LIST_URL = u"http://syncplay.pl/listpublicservers?{}" # Params
|
||||
|
||||
DEFAULT_TRUSTED_DOMAINS = [u"youtube.com",u"youtu.be"]
|
||||
TRUSTABLE_WEB_PROTOCOLS = [u"http://www.",u"https://www.",u"http://",u"https://"]
|
||||
|
||||
PRIVATE_FILE_FIELDS = ["path"]
|
||||
@ -1,5 +1,6 @@
|
||||
# coding:utf8
|
||||
from syncplay import constants
|
||||
|
||||
import messages_en
|
||||
import messages_ru
|
||||
import messages_de
|
||||
|
||||
@ -28,6 +28,8 @@ de = {
|
||||
|
||||
"current-offset-notification" : u"Aktueller Offset: {} Sekunden", # Offset
|
||||
|
||||
"media-directory-list-updated-notification" : u"Syncplay media directories have been updated.", # TODO: Translate
|
||||
|
||||
"room-join-notification" : u"<{}> hat den Raum '{}' betreten", # User
|
||||
"left-notification" : u"<{}> ist gegangen", # User
|
||||
"left-paused-notification" : u"<{}> ist gegangen, <{}> pausierte", # User who left, User who paused
|
||||
@ -136,8 +138,12 @@ de = {
|
||||
"invalid-seek-value" : u"Ungültige Zeitangabe",
|
||||
"invalid-offset-value" : u"Ungültiger Offset-Wert",
|
||||
|
||||
"switch-file-not-found-error" : u"Konnte nicht zur Datei '{0}' wechseln. Syncplay sucht im Ordner der aktuellen Datei und angegebenen Medien-Verzeichnissen.", # File not found, folder it was not found in
|
||||
"folder-search-timeout-error" : u"Die Suche nach Mediendateien in '{}' wurde abgebrochen weil sie zu lange gedauert hat. Dies tritt auf, wenn ein zu durchsuchender Medienordner zu viele Unterordner hat. Syncplay wird bis zum Neustart nur noch das Verzeichnis der aktuellen Datei durchsuchen.", #Folder
|
||||
"switch-file-not-found-error" : u"Konnte nicht zur Datei '{0}' wechseln. Syncplay looks in the specified media directories.", # File not found, folder it was not found in # TODO: Re-translate "Syncplay sucht im Ordner der aktuellen Datei und angegebenen Medien-Verzeichnissen." to reference to checking in "current media directory"
|
||||
"folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please select File->Set Media Directories in the menu bar and remove this directory or replace it with an appropriate sub-folder.", #Folder # TODO: Translate
|
||||
"folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please go to File->Set Media Directories and either remove the directory or resolve the issue (e.g. by changing power saving settings).", #Folder # TODO: Translate
|
||||
"added-file-not-in-media-directory-error" : u"You loaded a file in '{}' which is not a known media directory. You can add this as a media directory by selecting File->Set Media Directories in the menu bar.", #Folder # TODO: Translate
|
||||
"no-media-directories-error" : u"No media directories have been set. For shared playlist and file switching features to work properly please select File->Set Media Directories and specify where Syncplay should look to find media files.", # TODO: Translate
|
||||
"cannot-find-directory-error" : u"Could not find media directory '{}'. To update your list of media directories please select File->Set Media Directories from the menu bar and specify where Syncplay should look to find media files.", # TODO: Translate
|
||||
|
||||
"failed-to-load-server-list-error" : u"Konnte die Liste der öffentlichen Server nicht laden. Bitte besuche http://www.syncplay.pl/ [Englisch] mit deinem Browser.",
|
||||
|
||||
@ -191,7 +197,6 @@ de = {
|
||||
"pauseonleave-label" : u"Pausieren wenn ein Benutzer austritt",
|
||||
"readyatstart-label" : u"Standardmäßig auf \'Bereit\' stellen",
|
||||
"forceguiprompt-label" : u"Diesen Dialog nicht mehr anzeigen",
|
||||
"nostore-label" : u"Diese Konfiguration nicht speichern",
|
||||
"showosd-label" : u"OSD-Nachrichten anzeigen",
|
||||
|
||||
"showosdwarnings-label" : u"Zeige Warnungen (z.B. wenn Dateien verschieden)",
|
||||
@ -221,6 +226,7 @@ de = {
|
||||
"unpause-ifothersready-option" : u"Wiedergeben wenn bereits als Bereit gesetzt oder alle anderen bereit sind (Standard)",
|
||||
"unpause-ifminusersready-option" : u"Wiedergeben wenn bereits als Bereit gesetzt oder die minimale Anzahl anderer Nutzer bereit ist",
|
||||
"unpause-always" : u"Immer wiedergeben",
|
||||
"syncplay-trusteddomains-title": u"Trusted domains (for streaming services and hosted content)", # TODO: Translate into German
|
||||
|
||||
"help-label" : u"Hilfe",
|
||||
"reset-label" : u"Standardwerte zurücksetzen",
|
||||
@ -229,7 +235,8 @@ de = {
|
||||
|
||||
"contact-label" : u"Du hast eine Idee, einen Bug gefunden oder möchtest Feedback geben? Sende eine E-Mail an <a href=\"mailto:dev@syncplay.pl\">dev@syncplay.pl</a>, chatte auf dem <a href=\"https://webchat.freenode.net/?channels=#syncplay\">#Syncplay IRC-Kanal</a> auf irc.freenode.net oder <a href=\"https://github.com/Uriziel/syncplay/issues\">öffne eine Fehlermeldung auf GitHub</a>. Außerdem findest du auf <a href=\"http://syncplay.pl/\">http://syncplay.pl/</a> weitere Informationen, Hilfestellungen und Updates.",
|
||||
|
||||
"joinroom-menu-label" : u"Raum beitreten",
|
||||
"joinroom-label" : u"Raum beitreten",
|
||||
"joinroom-menu-label" : u"Raum beitreten {}", #TODO: Might want to fix this
|
||||
"seektime-menu-label" : u"Spule zu Zeit",
|
||||
"undoseek-menu-label" : u"Rückgängig",
|
||||
"play-menu-label" : u"Wiedergabe",
|
||||
@ -253,12 +260,14 @@ de = {
|
||||
"file-menu-label" : u"&Datei", # & precedes shortcut key
|
||||
"openmedia-menu-label" : u"&Mediendatei öffnen...",
|
||||
"openstreamurl-menu-label" : u"&Stream URL öffnen",
|
||||
"setmediadirectories-menu-label" : u"Set media &directories", # TODO: Translate
|
||||
"exit-menu-label" : u"&Beenden",
|
||||
"advanced-menu-label" : u"&Erweitert",
|
||||
"window-menu-label" : u"&Fenster",
|
||||
"setoffset-menu-label" : u"&Offset einstellen",
|
||||
"createcontrolledroom-menu-label" : u"&Zentral gesteuerten Raum erstellen",
|
||||
"identifyascontroller-menu-label" : u"Als Raumleiter &identifizieren",
|
||||
"settrusteddomains-menu-label" : u"Set &trusted domains", # TODO: Translate
|
||||
|
||||
"playback-menu-label" : u"&Wiedergabe",
|
||||
|
||||
@ -272,6 +281,11 @@ de = {
|
||||
"promptforstreamurl-msgbox-label" : u"Stream URL öffnen",
|
||||
"promptforstreamurlinfo-msgbox-label" : u"Stream URL",
|
||||
|
||||
"addfolder-label" : u"Add folder", # TODO: Translate
|
||||
|
||||
"adduris-msgbox-label" : u"Add URLs to playlist (one per line)", # TODO: Translate
|
||||
"trusteddomains-msgbox-label" : u"Domains it is okay to automatically switch to (one per line)", # TODO: Translate
|
||||
|
||||
"createcontrolledroom-msgbox-label" : u"Zentral gesteuerten Raum erstellen",
|
||||
"controlledroominfo-msgbox-label" : u"Namen des zentral gesteuerten Raums eingeben\r\n(siehe http://syncplay.pl/guide/ für eine Anleitung [Englisch]):",
|
||||
|
||||
@ -323,6 +337,7 @@ de = {
|
||||
"unpause-ifalreadyready-tooltip" : u"Wenn du nicht bereit bist und Play drückst wirst du als bereit gesetzt - zum Starten der Wiedergabe nochmal drücken.",
|
||||
"unpause-ifothersready-tooltip" : u"Wenn du Play drückst und nicht bereit bist, wird nur gestartet, wenn alle anderen bereit sind.",
|
||||
"unpause-ifminusersready-tooltip" : u"Wenn du Play drückst und nicht bereit bist, wird nur gestartet, wenn die minimale Anzahl anderer Benutzer bereit ist.",
|
||||
"trusteddomains-arguments-tooltip" : u"Domains that it is okay for Syncplay to automatically switch to when shared playlists is enabled.", # TODO: Translate into German
|
||||
|
||||
"help-tooltip" : u"Öffnet Hilfe auf syncplay.pl [Englisch]",
|
||||
"reset-tooltip" : u"Alle Einstellungen auf Standardwerte zurücksetzen.",
|
||||
@ -368,5 +383,28 @@ de = {
|
||||
"client-drop-server-error" : u"Client verloren: {} -- {}", # host, error
|
||||
"password-required-server-error" : u"Passwort nötig",
|
||||
"wrong-password-server-error" : u"Ungültiges Passwort",
|
||||
"hello-server-error" : u"Zu wenige Hello-Argumente"
|
||||
}
|
||||
"hello-server-error" : u"Zu wenige Hello-Argumente",
|
||||
|
||||
# Playlists TODO: Translate all this to German
|
||||
"playlist-selection-changed-notification" : u"{} changed the playlist selection", # Username
|
||||
"playlist-contents-changed-notification" : u"{} updated the playlist", # Username
|
||||
"cannot-find-file-for-playlist-switch-error" : u"Could not find file {} in media directories for playlist switch!", # Filename
|
||||
"cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename
|
||||
"cannot-add-unsafe-path-error" : u"Could not automatically load {} because it is not on a trusted domain. You can switch to the URL manually by double clicking it in the playlist, and add trusted domains via File->Advanced->Set Trusted Domains.", # Filename
|
||||
"sharedplaylistenabled-label" : u"Enable shared playlists",
|
||||
"removefromplaylist-menu-label" : u"Remove from playlist",
|
||||
"shuffleplaylist-menuu-label" : u"Shuffle playlist",
|
||||
"undoplaylist-menu-label" : u"Undo last change to playlist",
|
||||
"addfilestoplaylist-menu-label" : u"Add file(s) to bottom of playlist",
|
||||
"addurlstoplaylist-menu-label" : u"Add URL(s) to bottom of playlist",
|
||||
|
||||
"addusersfiletoplaylist-menu-label" : u"Add {} file to playlist", # item owner indicator
|
||||
"addusersstreamstoplaylist-menu-label" : u"Add {} stream to playlist", # item owner indicator
|
||||
"openusersstream-menu-label" : u"Open {} stream", # [username]'s
|
||||
"openusersfile-menu-label" : u"Open {} file", # [username]'s
|
||||
"item-is-yours-indicator" : u"your", # Goes with addusersfiletoplaylist/addusersstreamstoplaylist
|
||||
"item-is-others-indicator" : u"{}'s", # username - goes with addusersfiletoplaylist/addusersstreamstoplaylist
|
||||
|
||||
"playlist-instruction-item-message" : u"Drag file here to add it to the shared playlist.",
|
||||
"sharedplaylistenabled-tooltip" : u"Room operators can add files to a synced playlist to make it easy for everyone to watching the same thing. Configure media directories under 'Misc'.",
|
||||
}
|
||||
|
||||
@ -28,6 +28,8 @@ en = {
|
||||
|
||||
"current-offset-notification" : "Current offset: {} seconds", # Offset
|
||||
|
||||
"media-directory-list-updated-notification" : u"Syncplay media directories have been updated.",
|
||||
|
||||
"room-join-notification" : u"<{}> has joined the room: '{}'", # User
|
||||
"left-notification" : u"<{}> has left", # User
|
||||
"left-paused-notification" : u"<{}> left, <{}> paused", # User who left, User who paused
|
||||
@ -136,8 +138,12 @@ en = {
|
||||
"invalid-seek-value" : u"Invalid seek value",
|
||||
"invalid-offset-value" : u"Invalid offset value",
|
||||
|
||||
"switch-file-not-found-error" : u"Could not switch to file '{0}'. Syncplay looks in the folder of the currently playing file and specified media directories.", # File not found
|
||||
"folder-search-timeout-error" : u"The search for media in '{}' was aborted as it took too long. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. Until Syncplay is restarted only the directory of the currently open file will be checked.", #Folder
|
||||
"switch-file-not-found-error" : u"Could not switch to file '{0}'. Syncplay looks in specified media directories.", # File not found
|
||||
"folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please select File->Set Media Directories in the menu bar and remove this directory or replace it with an appropriate sub-folder.", #Folder
|
||||
"folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please go to File->Set Media Directories and either remove the directory or resolve the issue (e.g. by changing power saving settings).", #Folder
|
||||
"added-file-not-in-media-directory-error" : u"You loaded a file in '{}' which is not a known media directory. You can add this as a media directory by selecting File->Set Media Directories in the menu bar.", #Folder
|
||||
"no-media-directories-error" : u"No media directories have been set. For shared playlist and file switching features to work properly please select File->Set Media Directories and specify where Syncplay should look to find media files.",
|
||||
"cannot-find-directory-error" : u"Could not find media directory '{}'. To update your list of media directories please select File->Set Media Directories from the menu bar and specify where Syncplay should look to find media files.",
|
||||
|
||||
"failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.",
|
||||
|
||||
@ -193,7 +199,6 @@ en = {
|
||||
"pauseonleave-label" : "Pause when user leaves (e.g. if they are disconnected)",
|
||||
"readyatstart-label" : "Set me as 'ready to watch' by default",
|
||||
"forceguiprompt-label" : "Don't always show the Syncplay configuration window", # (Inverted)
|
||||
"nostore-label" : "Don't store this configuration", # (Inverted)
|
||||
"showosd-label" : "Enable OSD Messages",
|
||||
|
||||
"showosdwarnings-label" : "Include warnings (e.g. when files are different, users not ready)",
|
||||
@ -202,7 +207,7 @@ en = {
|
||||
"showdifferentroomosd-label" : "Include events in other rooms",
|
||||
"showslowdownosd-label" :"Include slowing down / reverting notifications",
|
||||
"language-label" : "Language:",
|
||||
"automatic-language" : "Default ({})", # Default language
|
||||
"automatic-language" : u"Default ({})", # Default language
|
||||
"showdurationnotification-label" : "Warn about media duration mismatches",
|
||||
"basics-label" : "Basics",
|
||||
"readiness-label" : u"Play/Pause",
|
||||
@ -223,6 +228,7 @@ en = {
|
||||
"unpause-ifothersready-option" : u"Unpause if already ready or others in room are ready (default)",
|
||||
"unpause-ifminusersready-option" : u"Unpause if already ready or if all others ready and min users ready",
|
||||
"unpause-always" : u"Always unpause",
|
||||
"syncplay-trusteddomains-title": u"Trusted domains (for streaming services and hosted content)",
|
||||
|
||||
"help-label" : "Help",
|
||||
"reset-label" : "Restore defaults",
|
||||
@ -231,7 +237,8 @@ en = {
|
||||
|
||||
"contact-label" : "Feel free to e-mail <a href=\"mailto:dev@syncplay.pl\"><nobr>dev@syncplay.pl</nobr></a>, chat via the <a href=\"https://webchat.freenode.net/?channels=#syncplay\"><nobr>#Syncplay IRC channel</nobr></a> on irc.freenode.net, <a href=\"https://github.com/Uriziel/syncplay/issues\"><nobr>raise an issue</nobr></a> via GitHub, <a href=\"https://www.facebook.com/SyncplaySoftware\"><nobr>like us on Facebook</nobr></a>, <a href=\"https://twitter.com/Syncplay/\"><nobr>follow us on Twitter</nobr></a>, or visit <a href=\"http://syncplay.pl/\"><nobr>http://syncplay.pl/</nobr></a>",
|
||||
|
||||
"joinroom-menu-label" : "Join room",
|
||||
"joinroom-label" : "Join room",
|
||||
"joinroom-menu-label" : u"Join room {}",
|
||||
"seektime-menu-label" : "Seek to time",
|
||||
"undoseek-menu-label" : "Undo seek",
|
||||
"play-menu-label" : "Play",
|
||||
@ -255,12 +262,14 @@ en = {
|
||||
"file-menu-label" : "&File", # & precedes shortcut key
|
||||
"openmedia-menu-label" : "&Open media file",
|
||||
"openstreamurl-menu-label" : "Open &media stream URL",
|
||||
"setmediadirectories-menu-label" : u"Set media &directories",
|
||||
"exit-menu-label" : "E&xit",
|
||||
"advanced-menu-label" : "&Advanced",
|
||||
"window-menu-label" : "&Window",
|
||||
"setoffset-menu-label" : "Set &offset",
|
||||
"createcontrolledroom-menu-label" : "&Create managed room",
|
||||
"identifyascontroller-menu-label" : "&Identify as room operator",
|
||||
"settrusteddomains-menu-label" : u"Set &trusted domains",
|
||||
|
||||
"playback-menu-label" : u"&Playback",
|
||||
|
||||
@ -274,6 +283,11 @@ en = {
|
||||
"promptforstreamurl-msgbox-label" : "Open media stream URL",
|
||||
"promptforstreamurlinfo-msgbox-label" : "Stream URL",
|
||||
|
||||
"addfolder-label" : u"Add folder",
|
||||
|
||||
"adduris-msgbox-label" : u"Add URLs to playlist (one per line)",
|
||||
"trusteddomains-msgbox-label" : u"Domains it is okay to automatically switch to (one per line)",
|
||||
|
||||
"createcontrolledroom-msgbox-label" : "Create managed room",
|
||||
"controlledroominfo-msgbox-label" : "Enter name of managed room\r\n(see http://syncplay.pl/guide/ for usage instructions):",
|
||||
|
||||
@ -323,6 +337,7 @@ en = {
|
||||
"unpause-ifalreadyready-tooltip" : u"If you press unpause when not ready it will set you as ready - press unpause again to unpause.",
|
||||
"unpause-ifothersready-tooltip" : u"If you press unpause when not ready, it will only upause if others are ready.",
|
||||
"unpause-ifminusersready-tooltip" : u"If you press unpause when not ready, it will only upause if others are ready and minimum users threshold is met.",
|
||||
"trusteddomains-arguments-tooltip" : u"Domains that it is okay for Syncplay to automatically switch to when shared playlists is enabled.",
|
||||
|
||||
"help-tooltip" : "Opens the Syncplay.pl user guide.",
|
||||
"reset-tooltip" : "Reset all settings to the default configuration.",
|
||||
@ -332,7 +347,7 @@ en = {
|
||||
"seektime-msgbox-label" : "Jump to specified time (in seconds / min:sec). Use +/- for relative seek.",
|
||||
"ready-tooltip" : "Indicates whether you are ready to watch.",
|
||||
"autoplay-tooltip" : "Auto-play when all users who have readiness indicator are ready and minimum user threshold met.",
|
||||
"switch-to-file-tooltip" : "Double click to switch to {}", # Filename
|
||||
"switch-to-file-tooltip" : u"Double click to switch to {}", # Filename
|
||||
|
||||
# In-userlist notes (GUI)
|
||||
"differentsize-note" : "Different size!",
|
||||
@ -345,8 +360,8 @@ en = {
|
||||
|
||||
# Server notifications
|
||||
"welcome-server-notification" : "Welcome to Syncplay server, ver. {0}", # version
|
||||
"client-connected-room-server-notification" : "{0}({2}) connected to room '{1}'", # username, host, room
|
||||
"client-left-server-notification" : "{0} left server", # name
|
||||
"client-connected-room-server-notification" : u"{0}({2}) connected to room '{1}'", # username, host, room
|
||||
"client-left-server-notification" : u"{0} left server", # name
|
||||
"no-salt-notification" : "PLEASE NOTE: To allow room operator passwords generated by this server instance to still work when the server is restarted, please add the following command line argument when running the Syncplay server in the future: --salt {}", #Salt
|
||||
|
||||
|
||||
@ -360,14 +375,37 @@ en = {
|
||||
"server-disable-ready-argument" : u"disable readiness feature",
|
||||
"server-motd-argument": "path to file from which motd will be fetched",
|
||||
"server-messed-up-motd-unescaped-placeholders": "Message of the Day has unescaped placeholders. All $ signs should be doubled ($$).",
|
||||
"server-messed-up-motd-too-long": "Message of the Day is too long - maximum of {} chars, {} given.",
|
||||
"server-messed-up-motd-too-long": u"Message of the Day is too long - maximum of {} chars, {} given.",
|
||||
|
||||
# Server errors
|
||||
"unknown-command-server-error" : "Unknown command {}", # message
|
||||
"unknown-command-server-error" : u"Unknown command {}", # message
|
||||
"not-json-server-error" : "Not a json encoded string {}", # message
|
||||
"not-known-server-error" : "You must be known to server before sending this command",
|
||||
"client-drop-server-error" : "Client drop: {} -- {}", # host, error
|
||||
"client-drop-server-error" : u"Client drop: {} -- {}", # host, error
|
||||
"password-required-server-error" : "Password required",
|
||||
"wrong-password-server-error" : "Wrong password supplied",
|
||||
"hello-server-error" : "Not enough Hello arguments",
|
||||
}
|
||||
|
||||
# Playlists
|
||||
"playlist-selection-changed-notification" : u"{} changed the playlist selection", # Username
|
||||
"playlist-contents-changed-notification" : u"{} updated the playlist", # Username
|
||||
"cannot-find-file-for-playlist-switch-error" : u"Could not find file {} in media directories for playlist switch!", # Filename
|
||||
"cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename
|
||||
"cannot-add-unsafe-path-error" : u"Could not automatically load {} because it is not on a trusted domain. You can switch to the URL manually by double clicking it in the playlist, and add trusted domains via File->Advanced->Set Trusted Domains.", # Filename
|
||||
"sharedplaylistenabled-label" : u"Enable shared playlists",
|
||||
"removefromplaylist-menu-label" : u"Remove from playlist",
|
||||
"shuffleplaylist-menuu-label" : u"Shuffle playlist",
|
||||
"undoplaylist-menu-label" : u"Undo last change to playlist",
|
||||
"addfilestoplaylist-menu-label" : u"Add file(s) to bottom of playlist",
|
||||
"addurlstoplaylist-menu-label" : u"Add URL(s) to bottom of playlist",
|
||||
|
||||
"addusersfiletoplaylist-menu-label" : u"Add {} file to playlist", # item owner indicator
|
||||
"addusersstreamstoplaylist-menu-label" : u"Add {} stream to playlist", # item owner indicator
|
||||
"openusersstream-menu-label" : u"Open {} stream", # [username]'s
|
||||
"openusersfile-menu-label" : u"Open {} file", # [username]'s
|
||||
"item-is-yours-indicator" : u"your", # Goes with addusersfiletoplaylist/addusersstreamstoplaylist
|
||||
"item-is-others-indicator" : u"{}'s", # username - goes with addusersfiletoplaylist/addusersstreamstoplaylist
|
||||
|
||||
"playlist-instruction-item-message" : u"Drag file here to add it to the shared playlist.",
|
||||
"sharedplaylistenabled-tooltip" : u"Room operators can add files to a synced playlist to make it easy for everyone to watching the same thing. Configure media directories under 'Misc'.",
|
||||
}
|
||||
|
||||
@ -28,6 +28,8 @@ ru = {
|
||||
|
||||
"current-offset-notification" : u"Текущее смещение: {} секунд(ы)", # Offset
|
||||
|
||||
"media-directory-list-updated-notification" : u"Syncplay media directories have been updated.", # TODO: Translate
|
||||
|
||||
"room-join-notification" : u"<{}> зашел(зашла) в комнату: '{}'", # User
|
||||
"left-notification" : u"<{}> покинул(а) комнату", # User
|
||||
"left-paused-notification" : u"<{}> покинул(а) комнату, <{}> приостановил(а) воспроизведение", # User who left, User who paused
|
||||
@ -136,8 +138,12 @@ ru = {
|
||||
"invalid-seek-value" : u"Некорректное значение для перемотки",
|
||||
"invalid-offset-value" : u"Некорректное смещение",
|
||||
|
||||
"switch-file-not-found-error" : u"Невозможно переключиться на файл '{0}'. Syncplay looks in the folder of the currently playing file and specified media directories.", # File not found # TODO: Translate last part into Russian
|
||||
"folder-search-timeout-error" : u"The search for media in '{}' was aborted as it took too long. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. Until Syncplay is restarted only the directory of the currently open file will be checked.", #Folder # TODO: Translate into Russian
|
||||
"switch-file-not-found-error" : u"Невозможно переключиться на файл '{0}'. Syncplay looks in the folder specified media directories.", # File not found # TODO: Translate last part into Russian
|
||||
"folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please select File->Set Media Directories in the menu bar and remove this directory or replace it with an appropriate sub-folder.", #Folder # TODO: Translate
|
||||
"folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please go to File->Set Media Directories and either remove the directory or resolve the issue (e.g. by changing power saving settings).", #Folder # TODO: Translate
|
||||
"added-file-not-in-media-directory-error" : u"You loaded a file in '{}' which is not a known media directory. You can add this as a media directory by selecting File->Set Media Directories in the menu bar.", #Folder #TODO: Translate
|
||||
"no-media-directories-error" : u"No media directories have been set. For shared playlist and file switching features to work properly please select File->Set Media Directories and specify where Syncplay should look to find media files.", # TODO: Translate
|
||||
"cannot-find-directory-error" : u"Could not find media directory '{}'. To update your list of media directories please select File->Set Media Directories from the menu bar and specify where Syncplay should look to find media files.", # TODO: Translate
|
||||
|
||||
"failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.", # TODO: Translate into Russian
|
||||
|
||||
@ -193,7 +199,6 @@ ru = {
|
||||
"readyatstart-label" : u"Выставить статус 'готово к просмотру' по умолчанию",
|
||||
"fastforwardondesync-label" : u"Ускорять видео при отставании (рекомендуется)",
|
||||
"forceguiprompt-label" : u"Не показывать больше этот диалог", # (Inverted)
|
||||
"nostore-label" : u"Не сохранять текущую конфигурацию", # (Inverted)
|
||||
"showosd-label" : u"Включить экранные сообщения (поверх видео)",
|
||||
|
||||
"showosdwarnings-label" : u"Показывать предупреждения (напр., когда файлы не совпадают)",
|
||||
@ -223,6 +228,7 @@ ru = {
|
||||
"unpause-ifothersready-option" : u"Unpause if already ready or others in room are ready (default)", # TODO: Translate into Russian
|
||||
"unpause-ifminusersready-option" : u"Unpause if already ready or if all others ready and min users ready", # TODO: Translate into Russian
|
||||
"unpause-always" : u"Always unpause", # TODO: Translate into Russian
|
||||
"syncplay-trusteddomains-title": u"Trusted domains (for streaming services and hosted content)", # TODO: Translate into Russian
|
||||
|
||||
"help-label" : u"Помощь",
|
||||
"reset-label" : u"Сброс настроек",
|
||||
@ -231,7 +237,8 @@ ru = {
|
||||
|
||||
"contact-label" : u"Есть идея, нашли ошибку или хотите оставить отзыв? Пишите на <a href=\"mailto:dev@syncplay.pl\">dev@syncplay.pl</a>, в <a href=\"https://webchat.freenode.net/?channels=#syncplay\">IRC канал #Syncplay</a> на irc.freenode.net или <a href=\"https://github.com/Uriziel/syncplay/issues\">задавайте вопросы через GitHub</a>. Кроме того, заходите на <a href=\"http://syncplay.pl/\">http://syncplay.pl/</a> за инорфмацией, помощью и обновлениями!",
|
||||
|
||||
"joinroom-menu-label" : u"Зайти в комнату",
|
||||
"joinroom-label" : u"Зайти в комнату",
|
||||
"joinroom-menu-label" : u"Зайти в комнату {}", #TODO: Might want to fix this
|
||||
"seektime-menu-label" : u"Перемотать",
|
||||
"undoseek-menu-label" : u"Отменить перемотку",
|
||||
"play-menu-label" : u"Play",
|
||||
@ -255,12 +262,14 @@ ru = {
|
||||
"file-menu-label" : u"&Файл", # & precedes shortcut key
|
||||
"openmedia-menu-label" : u"&Открыть видеофайл",
|
||||
"openstreamurl-menu-label" : u"Открыть URL &потокового вещания",
|
||||
"setmediadirectories-menu-label" : u"Set media &directories", # TODO: Translate
|
||||
"exit-menu-label" : u"&Выход",
|
||||
"advanced-menu-label" : u"&Дополнительно",
|
||||
"window-menu-label" : u"&Окна",
|
||||
"setoffset-menu-label" : u"Установить &смещение",
|
||||
"createcontrolledroom-menu-label" : u"&Создать управляемую комнату",
|
||||
"identifyascontroller-menu-label" : u"&Войти как оператор комнаты",
|
||||
"settrusteddomains-menu-label" : u"Set &trusted domains", # TODO: Translate
|
||||
|
||||
"playback-menu-label" : u"&Воспроизведение",
|
||||
|
||||
@ -274,6 +283,11 @@ ru = {
|
||||
"promptforstreamurl-msgbox-label" : u"Открыть URL потокового вещания",
|
||||
"promptforstreamurlinfo-msgbox-label" : u"URL потока",
|
||||
|
||||
"addfolder-label" : u"Add folder", # TODO: Translate
|
||||
|
||||
"adduris-msgbox-label" : u"Add URLs to playlist (one per line)", # TODO: Translate
|
||||
"trusteddomains-msgbox-label" : u"Domains it is okay to automatically switch to (one per line)", # TODO: Translate
|
||||
|
||||
"createcontrolledroom-msgbox-label" : u"Создать управляемую комнату",
|
||||
"controlledroominfo-msgbox-label" : u"Введите имя управляемой комнаты\r\n(см. инструкцию на странице http://syncplay.pl/guide/):",
|
||||
|
||||
@ -308,7 +322,7 @@ ru = {
|
||||
"pauseonleave-tooltip" : u"Приостановить воспроизведение, если Вы покинули комнату или кто-то из зрителей отключился от сервера.",
|
||||
"readyatstart-tooltip" : u"Отметить Вас готовым к просмотру сразу же (по умолчанию Вы отмечены не готовым)",
|
||||
"forceguiprompt-tooltip" : u"Окно настройки не будет отображаться при открытии файла в Syncplay.", # (Inverted)
|
||||
"nostore-tooltip" : u"Запустить Syncplay с данной конфигурацией, но не сохранять изменения навсегда.", # (Inverted)
|
||||
"nostore-tooltip" : u"Запустить Syncplay с данной конфигурацией, но не сохранять изменения навсегда.",
|
||||
"rewindondesync-tooltip" : u"Перематывать назад, когда это необходимо для синхронизации. Отключение этой опции может привести к большим рассинхронизациям!",
|
||||
"fastforwardondesync-tooltip" : u"Перематывать вперед при рассинхронизации с оператором комнаты (или если включена опция 'Никогда не замедлять и не перематывать видео другим').",
|
||||
"showosd-tooltip" : u"Отправлять сообщения Syncplay в видеопроигрыватель и отображать их поверх видео (OSD - On Screen Display).",
|
||||
@ -323,6 +337,7 @@ ru = {
|
||||
"unpause-ifalreadyready-tooltip" : u"If you press unpause when not ready it will set you as ready - press unpause again to unpause.", # TODO: Translate into Russian
|
||||
"unpause-ifothersready-tooltip" : u"If you press unpause when not ready, it will only upause if others are ready.", # TODO: Translate into Russian
|
||||
"unpause-ifminusersready-tooltip" : u"If you press unpause when not ready, it will only upause if others are ready and minimum users threshold is met.", # TODO: Translate into Russian
|
||||
"trusteddomains-arguments-tooltip" : u"Domains that it is okay for Syncplay to automatically switch to when shared playlists is enabled.", # TODO: Translate into Russian
|
||||
|
||||
"help-tooltip" : u"Открыть Руководство Пользователя на Syncplay.pl.",
|
||||
"reset-tooltip" : u"Сбрасывает все настройки Syncplay в начальное состояние.",
|
||||
@ -370,4 +385,26 @@ ru = {
|
||||
"wrong-password-server-error" : u"Указан неверный пароль.",
|
||||
"hello-server-error" : u"Не хватает аргументов Hello.",
|
||||
|
||||
}
|
||||
# Playlists TODO: Translate all this to Russian
|
||||
"playlist-selection-changed-notification" : u"{} changed the playlist selection", # Username
|
||||
"playlist-contents-changed-notification" : u"{} updated the playlist", # Username
|
||||
"cannot-find-file-for-playlist-switch-error" : u"Could not find file {} in media directories for playlist switch!", # Filename
|
||||
"cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename
|
||||
"cannot-add-unsafe-path-error" : u"Could not automatically load {} because it is not on a trusted domain. You can switch to the URL manually by double clicking it in the playlist, and add trusted domains via File->Advanced->Set Trusted Domains.", # Filename # TODO: Translate
|
||||
"sharedplaylistenabled-label" : u"Enable shared playlists",
|
||||
"removefromplaylist-menu-label" : u"Remove from playlist",
|
||||
"shuffleplaylist-menuu-label" : u"Shuffle playlist",
|
||||
"undoplaylist-menu-label" : u"Undo last change to playlist",
|
||||
"addfilestoplaylist-menu-label" : u"Add file(s) to bottom of playlist",
|
||||
"addurlstoplaylist-menu-label" : u"Add URL(s) to bottom of playlist",
|
||||
|
||||
"addusersfiletoplaylist-menu-label" : u"Add {} file to playlist", # item owner indicator
|
||||
"addusersstreamstoplaylist-menu-label" : u"Add {} stream to playlist", # item owner indicator
|
||||
"openusersstream-menu-label" : u"Open {} stream", # [username]'s
|
||||
"openusersfile-menu-label" : u"Open {} file", # [username]'s
|
||||
"item-is-yours-indicator" : u"your", # Goes with addusersfiletoplaylist/addusersstreamstoplaylist
|
||||
"item-is-others-indicator" : u"{}'s", # username - goes with addusersfiletoplaylist/addusersstreamstoplaylist
|
||||
|
||||
"playlist-instruction-item-message" : u"Drag file here to add it to the shared playlist.",
|
||||
"sharedplaylistenabled-tooltip" : u"Room operators can add files to a synced playlist to make it easy for everyone to watching the same thing. Configure media directories under 'Misc'.",
|
||||
}
|
||||
|
||||
@ -103,7 +103,7 @@ 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
|
||||
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()
|
||||
@ -419,8 +419,8 @@ class MPCHCAPIPlayer(BasePlayer):
|
||||
self.__positionUpdate.wait(constants.MPC_LOCK_WAIT_TIME)
|
||||
return self._mpcApi.lastFilePosition
|
||||
|
||||
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
||||
def askForStatus(self):
|
||||
try:
|
||||
if self._mpcApi.filePlaying and self.__preventAsking.wait(0) and self.__fileUpdate.acquire(0):
|
||||
self.__fileUpdate.release()
|
||||
position = self.__getPosition()
|
||||
@ -429,7 +429,9 @@ class MPCHCAPIPlayer(BasePlayer):
|
||||
if self.__preventAsking.wait(0) and self.__fileUpdate.acquire(0):
|
||||
self.__client.updatePlayerStatus(paused, position)
|
||||
self.__fileUpdate.release()
|
||||
return
|
||||
else:
|
||||
self.__echoGlobalStatus()
|
||||
except MpcHcApi.PlayerNotReadyException:
|
||||
self.__echoGlobalStatus()
|
||||
|
||||
def __echoGlobalStatus(self):
|
||||
|
||||
@ -88,7 +88,7 @@ class MplayerPlayer(BasePlayer):
|
||||
self._listener.sendLine("get_property {}".format(property_))
|
||||
|
||||
def displayMessage(self, message, duration=(constants.OSD_DURATION * 1000), secondaryOSD=False):
|
||||
self._listener.sendLine(u'{} "{!s}" {} {}'.format(self.OSD_QUERY, message, duration, constants.MPLAYER_OSD_LEVEL).encode('utf-8'))
|
||||
self._listener.sendLine(u'{} "{!s}" {} {}'.format(self.OSD_QUERY, self._stripNewlines(message), duration, constants.MPLAYER_OSD_LEVEL).encode('utf-8'))
|
||||
|
||||
def setSpeed(self, value):
|
||||
self._setProperty('speed', "{:.2f}".format(value))
|
||||
@ -105,7 +105,7 @@ class MplayerPlayer(BasePlayer):
|
||||
self.setPosition(self._client.getGlobalPosition())
|
||||
|
||||
def setPosition(self, value):
|
||||
self._position = value
|
||||
self._position = max(value,0)
|
||||
self._setProperty(self.POSITION_QUERY, "{}".format(value))
|
||||
time.sleep(0.03)
|
||||
|
||||
@ -129,10 +129,17 @@ class MplayerPlayer(BasePlayer):
|
||||
def _getPosition(self):
|
||||
self._getProperty(self.POSITION_QUERY)
|
||||
|
||||
def _stripNewlines(self, text):
|
||||
text = text.replace("\r", "")
|
||||
text = text.replace("\n", "")
|
||||
return text
|
||||
|
||||
def _quoteArg(self, arg):
|
||||
arg = arg.replace('\\', '\\\\')
|
||||
arg = arg.replace("'", "\\'")
|
||||
arg = arg.replace('"', '\\"')
|
||||
arg = arg.replace("\r", "")
|
||||
arg = arg.replace("\n", "")
|
||||
return u'"{}"'.format(arg)
|
||||
|
||||
def _fileIsLoaded(self):
|
||||
@ -142,7 +149,7 @@ class MplayerPlayer(BasePlayer):
|
||||
pass
|
||||
|
||||
def _storePosition(self, value):
|
||||
self._position = value
|
||||
self._position = max(value,0)
|
||||
|
||||
def _storePauseState(self, value):
|
||||
self._paused = value
|
||||
@ -216,7 +223,7 @@ class MplayerPlayer(BasePlayer):
|
||||
|
||||
@staticmethod
|
||||
def isValidPlayerPath(path):
|
||||
if "mplayer" in path and MplayerPlayer.getExpandedPath(path):
|
||||
if "mplayer" in path and MplayerPlayer.getExpandedPath(path) and not "mplayerc.exe" in path: # "mplayerc.exe" is Media Player Classic (not Home Cinema):
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -259,6 +266,10 @@ class MplayerPlayer(BasePlayer):
|
||||
|
||||
class __Listener(threading.Thread):
|
||||
def __init__(self, playerController, playerPath, filePath, args):
|
||||
self.sendQueue = []
|
||||
self.readyToSend = True
|
||||
self.lastSendTime = None
|
||||
self.lastNotReadyTime = None
|
||||
self.__playerController = playerController
|
||||
if self.__playerController.getPlayerPathErrors(playerPath,filePath):
|
||||
raise ValueError()
|
||||
@ -315,7 +326,70 @@ class MplayerPlayer(BasePlayer):
|
||||
self.__playerController.lineReceived(line)
|
||||
self.__playerController.drop()
|
||||
|
||||
def sendLine(self, line):
|
||||
def isReadyForSend(self):
|
||||
self.checkForReadinessOverride()
|
||||
return self.readyToSend
|
||||
|
||||
def setReadyToSend(self, newReadyState):
|
||||
oldState = self.readyToSend
|
||||
self.readyToSend = newReadyState
|
||||
self.lastNotReadyTime = time.time() if newReadyState == False else None
|
||||
if self.readyToSend == True:
|
||||
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:
|
||||
self.processSendQueue()
|
||||
|
||||
def checkForReadinessOverride(self):
|
||||
if self.lastNotReadyTime and time.time() - self.lastNotReadyTime > constants.MPV_MAX_NEWFILE_COOLDOWN_TIME:
|
||||
self.setReadyToSend(True)
|
||||
|
||||
def sendLine(self, line, notReadyAfterThis=None):
|
||||
self.checkForReadinessOverride()
|
||||
if self.readyToSend == False and "print_text ANS_pause" in line:
|
||||
self.__playerController._client.ui.showDebugMessage("<mpv> Not ready to get status update, so skipping")
|
||||
return
|
||||
try:
|
||||
if self.sendQueue:
|
||||
if constants.MPV_SUPERSEDE_IF_DUPLICATE_COMMANDS:
|
||||
for command in constants.MPV_SUPERSEDE_IF_DUPLICATE_COMMANDS:
|
||||
if line.startswith(command):
|
||||
for itemID, deletionCandidate in enumerate(self.sendQueue):
|
||||
if deletionCandidate.startswith(command):
|
||||
self.__playerController._client.ui.showDebugMessage(u"<mpv> Remove duplicate (supersede): {}".format(self.sendQueue[itemID]))
|
||||
self.sendQueue.remove(self.sendQueue[itemID])
|
||||
break
|
||||
break
|
||||
if constants.MPV_REMOVE_BOTH_IF_DUPLICATE_COMMANDS:
|
||||
for command in constants.MPV_REMOVE_BOTH_IF_DUPLICATE_COMMANDS:
|
||||
if line == command:
|
||||
for itemID, deletionCandidate in enumerate(self.sendQueue):
|
||||
if deletionCandidate == command:
|
||||
self.__playerController._client.ui.showDebugMessage(u"<mpv> Remove duplicate (delete both): {}".format(self.sendQueue[itemID]))
|
||||
self.__playerController._client.ui.showDebugMessage(self.sendQueue[itemID])
|
||||
return
|
||||
except:
|
||||
self.__playerController._client.ui.showDebugMessage("<mpv> Problem removing duplicates, etc")
|
||||
self.sendQueue.append(line)
|
||||
self.processSendQueue()
|
||||
if notReadyAfterThis:
|
||||
self.setReadyToSend(False)
|
||||
|
||||
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))
|
||||
time.sleep(constants.MPV_SENDMESSAGE_COOLDOWN_TIME)
|
||||
try:
|
||||
lineToSend = self.sendQueue.pop()
|
||||
if lineToSend:
|
||||
self.lastSendTime = time.time()
|
||||
self.actuallySendLine(lineToSend)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
def actuallySendLine(self, line):
|
||||
try:
|
||||
if not isinstance(line, unicode):
|
||||
line = line.decode('utf8')
|
||||
|
||||
@ -3,6 +3,7 @@ import subprocess
|
||||
from syncplay.players.mplayer import MplayerPlayer
|
||||
from syncplay.messages import getMessage
|
||||
from syncplay import constants
|
||||
from syncplay.utils import isURL
|
||||
import os, sys, time
|
||||
|
||||
class MpvPlayer(MplayerPlayer):
|
||||
@ -75,14 +76,14 @@ class OldMpvPlayer(MpvPlayer):
|
||||
OSD_QUERY = 'show_text'
|
||||
|
||||
def _setProperty(self, property_, value):
|
||||
self._listener.sendLine("no-osd set {} {}".format(property_, value))
|
||||
self._listener.sendLine(u"no-osd set {} {}".format(property_, value))
|
||||
|
||||
def setPaused(self, value):
|
||||
if self._paused <> value:
|
||||
self._paused = not self._paused
|
||||
self._listener.sendLine('cycle pause')
|
||||
|
||||
def mpvVersionErrorCheck(self, line):
|
||||
def mpvErrorCheck(self, line):
|
||||
if "Error parsing option" in line or "Error parsing commandline option" in line:
|
||||
self.quitReason = getMessage("mpv-version-error")
|
||||
|
||||
@ -90,11 +91,11 @@ class OldMpvPlayer(MpvPlayer):
|
||||
self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("mpv-version-error"), True)
|
||||
self.drop()
|
||||
|
||||
elif "[ytdl_hook] Your version of youtube-dl is too old" in line:
|
||||
if constants and any(errormsg in line for errormsg in constants.MPV_ERROR_MESSAGES_TO_REPEAT):
|
||||
self._client.ui.showErrorMessage(line)
|
||||
|
||||
def _handleUnknownLine(self, line):
|
||||
self.mpvVersionErrorCheck(line)
|
||||
self.mpvErrorCheck(line)
|
||||
if "Playing: " in line:
|
||||
newpath = line[9:]
|
||||
oldpath = self._filepath
|
||||
@ -129,7 +130,12 @@ class NewMpvPlayer(OldMpvPlayer):
|
||||
|
||||
if self.lastMPVPositionUpdate is None:
|
||||
return self._client.getGlobalPosition()
|
||||
|
||||
if self._recentlyReset:
|
||||
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.drop()
|
||||
@ -144,7 +150,7 @@ class NewMpvPlayer(OldMpvPlayer):
|
||||
if self._recentlyReset():
|
||||
self._position = 0
|
||||
elif self._fileIsLoaded():
|
||||
self._position = value
|
||||
self._position = max(value,0)
|
||||
else:
|
||||
self._position = self._client.getGlobalPosition()
|
||||
|
||||
@ -157,12 +163,18 @@ class NewMpvPlayer(OldMpvPlayer):
|
||||
def askForStatus(self):
|
||||
self._positionAsk.clear()
|
||||
self._pausedAsk.clear()
|
||||
self._getPaused()
|
||||
self._getPosition()
|
||||
if not self._listener.isReadyForSend:
|
||||
self._client.ui.showDebugMessage("mpv not ready for update")
|
||||
return
|
||||
|
||||
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())
|
||||
|
||||
def _getPausedAndPosition(self):
|
||||
self._listener.sendLine(u"print_text ANS_pause=${pause}\r\nprint_text ANS_time-pos=${=time-pos}")
|
||||
|
||||
def _preparePlayer(self):
|
||||
if self.delayedFilePath:
|
||||
self.openFile(self.delayedFilePath)
|
||||
@ -175,7 +187,7 @@ class NewMpvPlayer(OldMpvPlayer):
|
||||
|
||||
def _loadFile(self, filePath):
|
||||
self._clearFileLoaded()
|
||||
self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath)))
|
||||
self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath)), notReadyAfterThis=True)
|
||||
|
||||
def setPosition(self, value):
|
||||
super(self.__class__, self).setPosition(value)
|
||||
@ -184,19 +196,29 @@ class NewMpvPlayer(OldMpvPlayer):
|
||||
def openFile(self, filePath, resetPosition=False):
|
||||
if resetPosition:
|
||||
self.lastResetTime = time.time()
|
||||
if isURL(filePath):
|
||||
self.lastResetTime += constants.STREAM_ADDITIONAL_IGNORE_TIME
|
||||
self._loadFile(filePath)
|
||||
if self._paused != self._client.getGlobalPaused():
|
||||
self.setPaused(self._client.getGlobalPaused())
|
||||
if resetPosition == False:
|
||||
self.setPosition(self._client.getGlobalPosition())
|
||||
else:
|
||||
self._storePosition(0)
|
||||
|
||||
def _handleUnknownLine(self, line):
|
||||
self.mpvVersionErrorCheck(line)
|
||||
self.mpvErrorCheck(line)
|
||||
|
||||
if line == "<SyncplayUpdateFile>" or "Playing:" in line:
|
||||
self._listener.setReadyToSend(False)
|
||||
self._clearFileLoaded()
|
||||
|
||||
elif line == "</SyncplayUpdateFile>":
|
||||
self._onFileUpdate()
|
||||
self._listener.setReadyToSend(True)
|
||||
|
||||
elif "Failed" in line or "failed" in line or "No video or audio streams selected" in line or "error" in line:
|
||||
self._listener.setReadyToSend(True)
|
||||
|
||||
def _recentlyReset(self):
|
||||
if not self.lastResetTime:
|
||||
|
||||
@ -22,7 +22,6 @@ class VlcPlayer(BasePlayer):
|
||||
SLAVE_ARGS = constants.VLC_SLAVE_ARGS
|
||||
if not sys.platform.startswith('darwin'):
|
||||
SLAVE_ARGS.extend(constants.VLC_SLAVE_NONOSX_ARGS)
|
||||
random.seed()
|
||||
vlcport = random.randrange(constants.VLC_MIN_PORT, constants.VLC_MAX_PORT) if (constants.VLC_MIN_PORT < constants.VLC_MAX_PORT) else constants.VLC_MIN_PORT
|
||||
|
||||
def __init__(self, client, playerPath, filePath, args):
|
||||
@ -141,6 +140,10 @@ class VlcPlayer(BasePlayer):
|
||||
return fileURL
|
||||
|
||||
def openFile(self, filePath, resetPosition=False):
|
||||
if not utils.isURL(filePath):
|
||||
normedPath = os.path.normpath(filePath)
|
||||
if os.path.isfile(normedPath):
|
||||
filePath = normedPath
|
||||
if utils.isASCII(filePath):
|
||||
self._listener.sendLine('load-file: {}'.format(filePath.encode('ascii', 'ignore')))
|
||||
else:
|
||||
|
||||
@ -140,6 +140,10 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
user, isReady = values["username"], values["isReady"]
|
||||
manuallyInitiated = values["manuallyInitiated"] if values.has_key("manuallyInitiated") else True
|
||||
self._client.setReady(user, isReady, manuallyInitiated)
|
||||
elif command == "playlistIndex":
|
||||
self._client.playlist.changeToPlaylistIndex(values['index'], values['user'])
|
||||
elif command == "playlistChange":
|
||||
self._client.playlist.changePlaylist(values['files'], values['user'])
|
||||
|
||||
def sendSet(self, setting):
|
||||
self.sendMessage({"Set": setting})
|
||||
@ -247,6 +251,21 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
}
|
||||
})
|
||||
|
||||
def setPlaylist(self, files):
|
||||
self.sendSet({
|
||||
"playlistChange": {
|
||||
"files": files
|
||||
}
|
||||
})
|
||||
|
||||
def setPlaylistIndex(self, index):
|
||||
self.sendSet({
|
||||
"playlistIndex": {
|
||||
"index": index
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
def handleError(self, error):
|
||||
self.dropWithError(error["message"])
|
||||
|
||||
@ -358,6 +377,10 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
elif command == "ready":
|
||||
manuallyInitiated = set_[1]['manuallyInitiated'] if set_[1].has_key("manuallyInitiated") else False
|
||||
self._factory.setReady(self._watcher, set_[1]['isReady'], manuallyInitiated=manuallyInitiated)
|
||||
elif command == "playlistChange":
|
||||
self._factory.setPlaylist(self._watcher, set_[1]['files'])
|
||||
elif command == "playlistIndex":
|
||||
self._factory.setPlaylistIndex(self._watcher, set_[1]['index'])
|
||||
|
||||
def sendSet(self, setting):
|
||||
self.sendMessage({"Set": setting})
|
||||
@ -389,6 +412,22 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
}
|
||||
})
|
||||
|
||||
def setPlaylist(self, username, files):
|
||||
self.sendSet({
|
||||
"playlistChange": {
|
||||
"user": username,
|
||||
"files": files
|
||||
}
|
||||
})
|
||||
|
||||
def setPlaylistIndex(self, username, index):
|
||||
self.sendSet({
|
||||
"playlistIndex": {
|
||||
"user": username,
|
||||
"index": index
|
||||
}
|
||||
})
|
||||
|
||||
def sendUserSetting(self, username, room, file_, event):
|
||||
room = {"name": room.getName()}
|
||||
user = {username: {}}
|
||||
|
||||
@ -72,8 +72,13 @@ class SyncFactory(Factory):
|
||||
self.sendJoinMessage(watcher)
|
||||
else:
|
||||
self.sendRoomSwitchMessage(watcher)
|
||||
|
||||
room = watcher.getRoom()
|
||||
roomSetByName = room.getSetBy().getName() if room.getSetBy() else None
|
||||
watcher.setPlaylist(roomSetByName, room.getPlaylist())
|
||||
watcher.setPlaylistIndex(roomSetByName, room.getPlaylistIndex())
|
||||
if RoomPasswordProvider.isControlledRoom(roomName):
|
||||
for controller in watcher.getRoom().getControllers():
|
||||
for controller in room.getControllers():
|
||||
watcher.sendControlledRoomAuthStatus(True, controller, roomName)
|
||||
|
||||
def sendRoomSwitchMessage(self, watcher):
|
||||
@ -132,6 +137,24 @@ class SyncFactory(Factory):
|
||||
watcher.setReady(isReady)
|
||||
self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), manuallyInitiated))
|
||||
|
||||
def setPlaylist(self, watcher, files):
|
||||
room = watcher.getRoom()
|
||||
if room.canControl(watcher):
|
||||
watcher.getRoom().setPlaylist(files, watcher)
|
||||
self._roomManager.broadcastRoom(watcher, lambda w: w.setPlaylist(watcher.getName(), files))
|
||||
else:
|
||||
watcher.setPlaylist(room.getName(), room.getPlaylist())
|
||||
watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex())
|
||||
|
||||
|
||||
def setPlaylistIndex(self, watcher, index):
|
||||
room = watcher.getRoom()
|
||||
if room.canControl(watcher):
|
||||
watcher.getRoom().setPlaylistIndex(index, watcher)
|
||||
self._roomManager.broadcastRoom(watcher, lambda w: w.setPlaylistIndex(watcher.getName(), index))
|
||||
else:
|
||||
watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex())
|
||||
|
||||
class RoomManager(object):
|
||||
def __init__(self):
|
||||
self._rooms = {}
|
||||
@ -214,6 +237,10 @@ class Room(object):
|
||||
self._watchers = {}
|
||||
self._playState = self.STATE_PAUSED
|
||||
self._setBy = None
|
||||
self._playlist = []
|
||||
self._playlistIndex = None
|
||||
self.__lastUpdate = time.time()
|
||||
self.__position = 0
|
||||
|
||||
def __str__(self, *args, **kwargs):
|
||||
return self.getName()
|
||||
@ -222,10 +249,15 @@ class Room(object):
|
||||
return self._name
|
||||
|
||||
def getPosition(self):
|
||||
if self._watchers:
|
||||
age = time.time() - self.__lastUpdate
|
||||
if self._watchers and age > 1:
|
||||
watcher = min(self._watchers.values())
|
||||
self._setBy = watcher
|
||||
return watcher.getPosition()
|
||||
self.__position = watcher.getPosition()
|
||||
self.__lastUpdate = time.time()
|
||||
return self.__position
|
||||
elif self.__position is not None:
|
||||
return self.__position + (age if self._playState == self.STATE_PLAYING else 0)
|
||||
else:
|
||||
return 0
|
||||
|
||||
@ -234,6 +266,7 @@ class Room(object):
|
||||
self._setBy = setBy
|
||||
|
||||
def setPosition(self, position, setBy=None):
|
||||
self.__position = position
|
||||
for watcher in self._watchers.itervalues():
|
||||
watcher.setPosition(position)
|
||||
self._setBy = setBy
|
||||
@ -258,6 +291,8 @@ class Room(object):
|
||||
return
|
||||
del self._watchers[watcher.getName()]
|
||||
watcher.setRoom(None)
|
||||
if not self._watchers:
|
||||
self.__position = 0
|
||||
|
||||
def isEmpty(self):
|
||||
return not bool(self._watchers)
|
||||
@ -268,16 +303,33 @@ class Room(object):
|
||||
def canControl(self, watcher):
|
||||
return True
|
||||
|
||||
def setPlaylist(self, files, setBy=None):
|
||||
self._playlist = files
|
||||
|
||||
def setPlaylistIndex(self, index, setBy=None):
|
||||
self._playlistIndex = index
|
||||
|
||||
def getPlaylist(self):
|
||||
return self._playlist
|
||||
|
||||
def getPlaylistIndex(self):
|
||||
return self._playlistIndex
|
||||
|
||||
class ControlledRoom(Room):
|
||||
def __init__(self, name):
|
||||
Room.__init__(self, name)
|
||||
self._controllers = {}
|
||||
|
||||
def getPosition(self):
|
||||
if self._controllers:
|
||||
age = time.time() - self.__lastUpdate
|
||||
if self._controllers and age > 1:
|
||||
watcher = min(self._controllers.values())
|
||||
self._setBy = watcher
|
||||
return watcher.getPosition()
|
||||
self.__position = watcher.getPosition()
|
||||
self.__lastUpdate = time.time()
|
||||
return self.__position
|
||||
elif self.__position is not None:
|
||||
return self.__position + (age if self._playState == self.STATE_PLAYING else 0)
|
||||
else:
|
||||
return 0
|
||||
|
||||
@ -297,6 +349,14 @@ class ControlledRoom(Room):
|
||||
if self.canControl(setBy):
|
||||
Room.setPosition(self, position, setBy)
|
||||
|
||||
def setPlaylist(self, files, setBy=None):
|
||||
if self.canControl(setBy):
|
||||
self._playlist = files
|
||||
|
||||
def setPlaylistIndex(self, index, setBy=None):
|
||||
if self.canControl(setBy):
|
||||
self._playlistIndex = index
|
||||
|
||||
def canControl(self, watcher):
|
||||
return watcher.getName() in self._controllers
|
||||
|
||||
@ -370,6 +430,12 @@ class Watcher(object):
|
||||
def sendSetReady(self, username, isReady, manuallyInitiated=True):
|
||||
self._connector.sendSetReady(username, isReady, manuallyInitiated)
|
||||
|
||||
def setPlaylistIndex(self, username, index):
|
||||
self._connector.setPlaylistIndex(username, index)
|
||||
|
||||
def setPlaylist(self, username, files):
|
||||
self._connector.setPlaylist(username, files)
|
||||
|
||||
def __lt__(self, b):
|
||||
if self.getPosition() is None or self._file is None:
|
||||
return False
|
||||
|
||||
@ -34,6 +34,11 @@ class ConfigurationGetter(object):
|
||||
"playerPath": None,
|
||||
"perPlayerArguments": None,
|
||||
"mediaSearchDirectories": None,
|
||||
"sharedPlaylistEnabled": True,
|
||||
"loopAtEndOfPlaylist": False,
|
||||
"loopSingleFiles" : False,
|
||||
"onlySwitchToTrustedDomains": True,
|
||||
"trustedDomains": constants.DEFAULT_TRUSTED_DOMAINS,
|
||||
"file": None,
|
||||
"playerArgs": [],
|
||||
"playerClass": None,
|
||||
@ -51,6 +56,7 @@ class ConfigurationGetter(object):
|
||||
"unpauseAction": constants.UNPAUSE_IFOTHERSREADY_MODE,
|
||||
"autoplayInitialState" : None,
|
||||
"autoplayMinUsers" : -1,
|
||||
"autoplayRequireSameFilenames": True,
|
||||
"clearGUIData": False,
|
||||
"language" : "",
|
||||
"checkForUpdatesAutomatically" : None,
|
||||
@ -87,6 +93,7 @@ class ConfigurationGetter(object):
|
||||
"dontSlowDownWithMe",
|
||||
"pauseOnLeave",
|
||||
"readyAtStart",
|
||||
"autoplayRequireSameFilenames",
|
||||
"clearGUIData",
|
||||
"rewindOnDesync",
|
||||
"slowOnDesync",
|
||||
@ -100,7 +107,11 @@ class ConfigurationGetter(object):
|
||||
"showDifferentRoomOSD",
|
||||
"showSameRoomOSD",
|
||||
"showNonControllerOSD",
|
||||
"showDurationNotification"
|
||||
"showDurationNotification",
|
||||
"sharedPlaylistEnabled",
|
||||
"loopAtEndOfPlaylist",
|
||||
"loopSingleFiles",
|
||||
"onlySwitchToTrustedDomains"
|
||||
]
|
||||
self._tristate = [
|
||||
"checkForUpdatesAutomatically",
|
||||
@ -110,6 +121,7 @@ class ConfigurationGetter(object):
|
||||
self._serialised = [
|
||||
"perPlayerArguments",
|
||||
"mediaSearchDirectories",
|
||||
"trustedDomains",
|
||||
]
|
||||
|
||||
self._numeric = [
|
||||
@ -121,9 +133,23 @@ class ConfigurationGetter(object):
|
||||
|
||||
self._iniStructure = {
|
||||
"server_data": ["host", "port", "password"],
|
||||
"client_settings": ["name", "room", "playerPath", "perPlayerArguments", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "unpauseAction", "pauseOnLeave", "readyAtStart", "autoplayMinUsers", "autoplayInitialState", "mediaSearchDirectories"],
|
||||
"gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showDurationNotification"],
|
||||
"general": ["language", "checkForUpdatesAutomatically", "lastCheckedForUpdates"]
|
||||
"client_settings": ["name", "room", "playerPath",
|
||||
"perPlayerArguments", "slowdownThreshold",
|
||||
"rewindThreshold", "fastforwardThreshold",
|
||||
"slowOnDesync", "rewindOnDesync",
|
||||
"fastforwardOnDesync", "dontSlowDownWithMe",
|
||||
"forceGuiPrompt", "filenamePrivacyMode",
|
||||
"filesizePrivacyMode", "unpauseAction",
|
||||
"pauseOnLeave", "readyAtStart", "autoplayMinUsers",
|
||||
"autoplayInitialState", "mediaSearchDirectories",
|
||||
"sharedPlaylistEnabled", "loopAtEndOfPlaylist",
|
||||
"loopSingleFiles",
|
||||
"onlySwitchToTrustedDomains", "trustedDomains"],
|
||||
"gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD",
|
||||
"showDifferentRoomOSD", "showSameRoomOSD",
|
||||
"showNonControllerOSD", "showDurationNotification"],
|
||||
"general": ["language", "checkForUpdatesAutomatically",
|
||||
"lastCheckedForUpdates"]
|
||||
}
|
||||
|
||||
self._playerFactory = PlayerFactory()
|
||||
@ -352,7 +378,7 @@ class ConfigurationGetter(object):
|
||||
for name in constants.CONFIG_NAMES:
|
||||
path = location + os.path.sep + name
|
||||
if os.path.isfile(path) and (os.name == 'nt' or path != os.path.join(os.getenv('HOME', '.'), constants.DEFAULT_CONFIG_NAME_LINUX)):
|
||||
loadedPaths.append("'" + os.path.normpath(path) + "'")
|
||||
loadedPaths.append(u"'{}'".format(os.path.normpath(path)))
|
||||
self._parseConfigFile(path, createConfig=False)
|
||||
self._checkConfig()
|
||||
return loadedPaths
|
||||
@ -409,6 +435,15 @@ class ConfigurationGetter(object):
|
||||
qt4reactor.install()
|
||||
return self._config
|
||||
|
||||
def setConfigOption(self, option, value):
|
||||
path = self._getConfigurationFilePath()
|
||||
backup = self._config.copy()
|
||||
self._parseConfigFile(path)
|
||||
self._config[option] = value
|
||||
backup[option] = value
|
||||
self._saveConfig(path)
|
||||
self._config = backup
|
||||
|
||||
class SafeConfigParserUnicode(SafeConfigParser):
|
||||
def write(self, fp):
|
||||
"""Write an .ini-format representation of the configuration state."""
|
||||
|
||||
@ -71,7 +71,7 @@ class ConfigDialog(QtGui.QDialog):
|
||||
moreToggling = False
|
||||
|
||||
def automaticUpdatePromptCheck(self):
|
||||
if self.automaticupdatesCheckbox.checkState() == Qt.PartiallyChecked and not self.nostoreCheckbox.isChecked():
|
||||
if self.automaticupdatesCheckbox.checkState() == Qt.PartiallyChecked:
|
||||
reply = QtGui.QMessageBox.question(self, "Syncplay",
|
||||
getMessage("promptforupdate-label"), QtGui.QMessageBox.StandardButton.Yes | QtGui.QMessageBox.StandardButton.No)
|
||||
if reply == QtGui.QMessageBox.Yes:
|
||||
@ -87,9 +87,9 @@ class ConfigDialog(QtGui.QDialog):
|
||||
if self.showmoreCheckbox.isChecked():
|
||||
self.tabListFrame.show()
|
||||
self.resetButton.show()
|
||||
self.nostoreCheckbox.show()
|
||||
self.playerargsTextbox.show()
|
||||
self.playerargsLabel.show()
|
||||
self.runButton.show()
|
||||
self.saveMoreState(True)
|
||||
self.tabListWidget.setCurrentRow(0)
|
||||
self.ensureTabListIsVisible()
|
||||
@ -97,9 +97,9 @@ class ConfigDialog(QtGui.QDialog):
|
||||
else:
|
||||
self.tabListFrame.hide()
|
||||
self.resetButton.hide()
|
||||
self.nostoreCheckbox.hide()
|
||||
self.playerargsTextbox.hide()
|
||||
self.playerargsLabel.hide()
|
||||
self.runButton.hide()
|
||||
self.saveMoreState(False)
|
||||
self.stackedLayout.setCurrentIndex(0)
|
||||
newHeight = self.connectionSettingsGroup.minimumSizeHint().height()+self.mediaplayerSettingsGroup.minimumSizeHint().height()+self.bottomButtonFrame.minimumSizeHint().height()+3
|
||||
@ -112,12 +112,6 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.setFixedWidth(self.minimumSizeHint().width())
|
||||
self.executablepathCombobox.setFixedWidth(self.mediapathTextbox.width())
|
||||
|
||||
def runButtonTextUpdate(self):
|
||||
if self.nostoreCheckbox.isChecked():
|
||||
self.runButton.setText(getMessage("run-label"))
|
||||
else:
|
||||
self.runButton.setText(getMessage("storeandrun-label"))
|
||||
|
||||
def openHelp(self):
|
||||
self.QtGui.QDesktopServices.openUrl(QUrl("http://syncplay.pl/guide/client/"))
|
||||
|
||||
@ -365,16 +359,23 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.mediadirectory = os.path.dirname(fileName)
|
||||
self.saveMediaBrowseSettings()
|
||||
|
||||
def _saveDataAndLeave(self):
|
||||
def _runWithoutStoringConfig(self):
|
||||
self._saveDataAndLeave(storeConfiguration=False)
|
||||
|
||||
def _saveDataAndLeave(self, storeConfiguration=True):
|
||||
self.config['noStore'] = storeConfiguration
|
||||
if storeConfiguration:
|
||||
self.automaticUpdatePromptCheck()
|
||||
self.loadLastUpdateCheckDate()
|
||||
|
||||
self.config["perPlayerArguments"] = self.perPlayerArgs
|
||||
self.config["mediaSearchDirectories"] = utils.convertMultilineStringToList(self.mediasearchTextEdit.toPlainText())
|
||||
self.config["trustedDomains"] = utils.convertMultilineStringToList(self.trusteddomainsTextEdit.toPlainText())
|
||||
|
||||
self.processWidget(self, lambda w: self.saveValues(w))
|
||||
if self.hostCombobox.currentText():
|
||||
self.config['host'] = self.hostCombobox.currentText() if ":" in self.hostCombobox.currentText() else self.hostCombobox.currentText() + ":" + unicode(constants.DEFAULT_PORT)
|
||||
self.config['host'] = self.config['host'].replace(" ","").replace("\t", "").replace("\n","").replace("\r","")
|
||||
else:
|
||||
self.config['host'] = None
|
||||
self.config['playerPath'] = unicode(self.safenormcaseandpath(self.executablepathCombobox.currentText()))
|
||||
@ -504,6 +505,7 @@ class ConfigDialog(QtGui.QDialog):
|
||||
|
||||
self.perPlayerArgs = self.config["perPlayerArguments"]
|
||||
self.mediaSearchDirectories = self.config["mediaSearchDirectories"]
|
||||
self.trustedDomains = self.config["trustedDomains"]
|
||||
|
||||
self.connectionSettingsGroup = QtGui.QGroupBox(getMessage("connection-group-title"))
|
||||
self.loadSavedPublicServerList()
|
||||
@ -794,10 +796,24 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.othersyncSettingsLayout.setAlignment(Qt.AlignLeft)
|
||||
self.othersyncSettingsLayout.addWidget(self.fastforwardCheckbox, 3, 0,1,2, Qt.AlignLeft)
|
||||
|
||||
|
||||
## Trusted domains
|
||||
|
||||
self.trusteddomainsSettingsGroup = QtGui.QGroupBox(getMessage("syncplay-trusteddomains-title"))
|
||||
self.trusteddomainsSettingsLayout = QtGui.QVBoxLayout()
|
||||
self.trusteddomainsSettingsGroup.setLayout(self.trusteddomainsSettingsLayout)
|
||||
|
||||
self.trusteddomainsTextEdit = QPlainTextEdit(utils.getListAsMultilineString(self.trustedDomains))
|
||||
self.trusteddomainsTextEdit.setObjectName(constants.LOAD_SAVE_MANUALLY_MARKER + "trusteddomains-arguments")
|
||||
self.trusteddomainsTextEdit.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap)
|
||||
self.trusteddomainsSettingsLayout.addWidget(self.trusteddomainsTextEdit)
|
||||
self.trusteddomainsSettingsGroup.setMaximumHeight(self.trusteddomainsSettingsGroup.minimumSizeHint().height())
|
||||
|
||||
self.othersyncSettingsGroup.setLayout(self.othersyncSettingsLayout)
|
||||
self.othersyncSettingsGroup.setMaximumHeight(self.othersyncSettingsGroup.minimumSizeHint().height())
|
||||
self.syncSettingsLayout.addWidget(self.othersyncSettingsGroup)
|
||||
self.syncSettingsLayout.addWidget(self.desyncSettingsGroup)
|
||||
self.syncSettingsLayout.addWidget(self.trusteddomainsSettingsGroup)
|
||||
self.syncSettingsFrame.setLayout(self.syncSettingsLayout)
|
||||
self.desyncSettingsGroup.setMaximumHeight(self.desyncSettingsGroup.minimumSizeHint().height())
|
||||
self.syncSettingsLayout.setAlignment(Qt.AlignTop)
|
||||
@ -819,27 +835,27 @@ class ConfigDialog(QtGui.QDialog):
|
||||
|
||||
self.showSameRoomOSDCheckbox = QCheckBox(getMessage("showsameroomosd-label"))
|
||||
self.showSameRoomOSDCheckbox.setObjectName("showSameRoomOSD")
|
||||
self.showSameRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png"))
|
||||
self.showSameRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png"))
|
||||
self.osdSettingsLayout.addWidget(self.showSameRoomOSDCheckbox)
|
||||
|
||||
self.showNonControllerOSDCheckbox = QCheckBox(getMessage("shownoncontrollerosd-label"))
|
||||
self.showNonControllerOSDCheckbox.setObjectName("showNonControllerOSD")
|
||||
self.showNonControllerOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png"))
|
||||
self.showNonControllerOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png"))
|
||||
self.osdSettingsLayout.addWidget(self.showNonControllerOSDCheckbox)
|
||||
|
||||
self.showDifferentRoomOSDCheckbox = QCheckBox(getMessage("showdifferentroomosd-label"))
|
||||
self.showDifferentRoomOSDCheckbox.setObjectName("showDifferentRoomOSD")
|
||||
self.showDifferentRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png"))
|
||||
self.showDifferentRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png"))
|
||||
self.osdSettingsLayout.addWidget(self.showDifferentRoomOSDCheckbox)
|
||||
|
||||
self.slowdownOSDCheckbox = QCheckBox(getMessage("showslowdownosd-label"))
|
||||
self.slowdownOSDCheckbox.setObjectName("showSlowdownOSD")
|
||||
self.slowdownOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png"))
|
||||
self.slowdownOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png"))
|
||||
self.osdSettingsLayout.addWidget(self.slowdownOSDCheckbox)
|
||||
|
||||
self.showOSDWarningsCheckbox = QCheckBox(getMessage("showosdwarnings-label"))
|
||||
self.showOSDWarningsCheckbox.setObjectName("showOSDWarnings")
|
||||
self.showOSDWarningsCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png"))
|
||||
self.showOSDWarningsCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png"))
|
||||
self.osdSettingsLayout.addWidget(self.showOSDWarningsCheckbox)
|
||||
|
||||
self.subitems['showOSD'] = ["showSameRoomOSD", "showDifferentRoomOSD", "showSlowdownOSD", "showOSDWarnings", "showNonControllerOSD"]
|
||||
@ -899,24 +915,26 @@ class ConfigDialog(QtGui.QDialog):
|
||||
|
||||
self.bottomButtonFrame = QtGui.QFrame()
|
||||
self.bottomButtonLayout = QtGui.QHBoxLayout()
|
||||
self.helpButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'help.png'), getMessage("help-label"))
|
||||
self.helpButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + u'help.png'), getMessage("help-label"))
|
||||
self.helpButton.setObjectName("help")
|
||||
self.helpButton.setMaximumSize(self.helpButton.sizeHint())
|
||||
self.helpButton.pressed.connect(self.openHelp)
|
||||
|
||||
self.resetButton = QtGui.QPushButton(QtGui.QIcon(resourcespath + 'cog_delete.png'),getMessage("reset-label"))
|
||||
self.resetButton = QtGui.QPushButton(QtGui.QIcon(resourcespath + u'cog_delete.png'),getMessage("reset-label"))
|
||||
self.resetButton.setMaximumSize(self.resetButton.sizeHint())
|
||||
self.resetButton.setObjectName("reset")
|
||||
self.resetButton.pressed.connect(self.resetSettings)
|
||||
|
||||
self.runButton = QtGui.QPushButton(QtGui.QIcon(resourcespath + 'accept.png'), getMessage("storeandrun-label"))
|
||||
self.runButton = QtGui.QPushButton(QtGui.QIcon(resourcespath + u'accept.png'), getMessage("run-label"))
|
||||
self.runButton.pressed.connect(self._saveDataAndLeave)
|
||||
self.runButton.setToolTip(getMessage("nostore-tooltip"))
|
||||
self.storeAndRunButton = QtGui.QPushButton(QtGui.QIcon(resourcespath + u'accept.png'), getMessage("storeandrun-label"))
|
||||
self.storeAndRunButton.pressed.connect(self._runWithoutStoringConfig)
|
||||
self.bottomButtonLayout.addWidget(self.helpButton)
|
||||
self.bottomButtonLayout.addWidget(self.resetButton)
|
||||
self.bottomButtonLayout.addWidget(self.runButton)
|
||||
self.bottomButtonLayout.addWidget(self.storeAndRunButton)
|
||||
self.bottomButtonFrame.setLayout(self.bottomButtonLayout)
|
||||
if config['noStore'] == True:
|
||||
self.runButton.setText(getMessage("run-label"))
|
||||
self.bottomButtonLayout.setContentsMargins(5,0,5,0)
|
||||
self.mainLayout.addWidget(self.bottomButtonFrame, 1, 0, 1, 2)
|
||||
|
||||
@ -925,12 +943,11 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.bottomCheckboxLayout = QtGui.QGridLayout()
|
||||
self.alwaysshowCheckbox = QCheckBox(getMessage("forceguiprompt-label"))
|
||||
|
||||
self.nostoreCheckbox = QCheckBox(getMessage("nostore-label"))
|
||||
self.enableplaylistsCheckbox = QCheckBox(getMessage("sharedplaylistenabled-label"))
|
||||
self.bottomCheckboxLayout.addWidget(self.showmoreCheckbox)
|
||||
self.bottomCheckboxLayout.addWidget(self.nostoreCheckbox, 0, 2, Qt.AlignRight)
|
||||
self.bottomCheckboxLayout.addWidget(self.enableplaylistsCheckbox, 0, 2, Qt.AlignRight)
|
||||
self.alwaysshowCheckbox.setObjectName(constants.INVERTED_STATE_MARKER + "forceGuiPrompt")
|
||||
self.nostoreCheckbox.setObjectName("noStore")
|
||||
self.nostoreCheckbox.toggled.connect(self.runButtonTextUpdate)
|
||||
self.enableplaylistsCheckbox.setObjectName("sharedPlaylistEnabled")
|
||||
self.bottomCheckboxFrame.setLayout(self.bottomCheckboxLayout)
|
||||
|
||||
self.mainLayout.addWidget(self.bottomCheckboxFrame, 2, 0, 1, 2)
|
||||
@ -939,11 +956,11 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.tabListLayout = QtGui.QHBoxLayout()
|
||||
self.tabListFrame = QtGui.QFrame()
|
||||
self.tabListWidget = QtGui.QListWidget()
|
||||
self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + "house.png"),getMessage("basics-label")))
|
||||
self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + "control_pause_blue.png"),getMessage("readiness-label")))
|
||||
self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + "film_link.png"),getMessage("sync-label")))
|
||||
self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + "comments.png"),getMessage("messages-label")))
|
||||
self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + "cog.png"),getMessage("misc-label")))
|
||||
self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + u"house.png"),getMessage("basics-label")))
|
||||
self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + u"control_pause_blue.png"),getMessage("readiness-label")))
|
||||
self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + u"film_link.png"),getMessage("sync-label")))
|
||||
self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + u"comments.png"),getMessage("messages-label")))
|
||||
self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + u"cog.png"),getMessage("misc-label")))
|
||||
self.tabListLayout.addWidget(self.tabListWidget)
|
||||
self.tabListFrame.setLayout(self.tabListLayout)
|
||||
self.tabListFrame.setFixedWidth(self.tabListFrame.minimumSizeHint().width())
|
||||
@ -1029,15 +1046,15 @@ class ConfigDialog(QtGui.QDialog):
|
||||
if sys.platform.startswith('win'):
|
||||
resourcespath = utils.findWorkingDir() + "\\resources\\"
|
||||
else:
|
||||
resourcespath = utils.findWorkingDir() + "/resources/"
|
||||
self.posixresourcespath = utils.findWorkingDir().replace("\\","/") + "/resources/"
|
||||
resourcespath = utils.findWorkingDir() + u"/resources/"
|
||||
self.posixresourcespath = utils.findWorkingDir().replace(u"\\","/") + u"/resources/"
|
||||
self.resourcespath = resourcespath
|
||||
|
||||
super(ConfigDialog, self).__init__()
|
||||
|
||||
self.setWindowTitle(getMessage("config-window-title"))
|
||||
self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & ~Qt.WindowContextHelpButtonHint)
|
||||
self.setWindowIcon(QtGui.QIcon(resourcespath + "syncplay.png"))
|
||||
self.setWindowIcon(QtGui.QIcon(resourcespath + u"syncplay.png"))
|
||||
|
||||
self.stackedLayout = QtGui.QStackedLayout()
|
||||
self.stackedFrame = QtGui.QFrame()
|
||||
@ -1059,10 +1076,10 @@ class ConfigDialog(QtGui.QDialog):
|
||||
|
||||
if self.getMoreState() == False:
|
||||
self.tabListFrame.hide()
|
||||
self.nostoreCheckbox.hide()
|
||||
self.resetButton.hide()
|
||||
self.playerargsTextbox.hide()
|
||||
self.playerargsLabel.hide()
|
||||
self.runButton.hide()
|
||||
newHeight = self.connectionSettingsGroup.minimumSizeHint().height()+self.mediaplayerSettingsGroup.minimumSizeHint().height()+self.bottomButtonFrame.minimumSizeHint().height()+3
|
||||
if self.error:
|
||||
newHeight +=self.errorLabel.height()+3
|
||||
|
||||
@ -23,6 +23,12 @@ class ConsoleUI(threading.Thread):
|
||||
def drop(self):
|
||||
pass
|
||||
|
||||
def setPlaylist(self, newPlaylist, newIndexFilename=None):
|
||||
pass
|
||||
|
||||
def setPlaylistIndexFilename(self, filename):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
while True:
|
||||
@ -77,6 +83,9 @@ class ConsoleUI(threading.Thread):
|
||||
def userListChange(self):
|
||||
pass
|
||||
|
||||
def fileSwitchFoundFiles(self):
|
||||
pass
|
||||
|
||||
def showMessage(self, message, noTimestamp=False):
|
||||
message = message.encode(sys.stdout.encoding, 'replace')
|
||||
if noTimestamp:
|
||||
|
||||