Merge pull request #50 from Uriziel/areYouReady

User readiness indicator feature, update check, etc
This commit is contained in:
Etoh 2015-01-25 18:57:48 +00:00
commit 8c99bcc794
23 changed files with 1159 additions and 504 deletions

View File

@ -68,6 +68,7 @@ NSIS_SCRIPT_TEMPLATE = r"""
LangString ^StartMenu $${LANG_ENGLISH} "Start Menu"
LangString ^Desktop $${LANG_ENGLISH} "Desktop"
LangString ^QuickLaunchBar $${LANG_ENGLISH} "Quick Launch Bar"
LangString ^AutomaticUpdates $${LANG_ENGLISH} "Check for updates automatically"
LangString ^UninstConfig $${LANG_ENGLISH} "Delete configuration file."
LangString ^SyncplayLanguage $${LANG_POLISH} "pl"
@ -88,6 +89,7 @@ NSIS_SCRIPT_TEMPLATE = r"""
LangString ^StartMenu $${LANG_RUSSIAN} "в меню Пуск"
LangString ^Desktop $${LANG_RUSSIAN} "на рабочем столе"
LangString ^QuickLaunchBar $${LANG_RUSSIAN} "в меню быстрого запуска"
LangString ^AutomaticUpdates $${LANG_RUSSIAN} "Проверять обновления автоматически"; TODO: Confirm Russian translation ("Check for updates automatically")
LangString ^UninstConfig $${LANG_RUSSIAN} "Удалить файл настроек."
LangString ^SyncplayLanguage $${LANG_GERMAN} "de"
@ -98,6 +100,7 @@ NSIS_SCRIPT_TEMPLATE = r"""
LangString ^StartMenu $${LANG_GERMAN} "Startmenü"
LangString ^Desktop $${LANG_GERMAN} "Desktop"
LangString ^QuickLaunchBar $${LANG_GERMAN} "Schnellstartleiste"
LangString ^AutomaticUpdates $${LANG_GERMAN} "Automatisch nach Updates suchen"; TODO: Confirm German translation ("Check for updates automatically")
LangString ^UninstConfig $${LANG_GERMAN} "Konfigurationsdatei löschen."
; Remove text to save space
@ -117,11 +120,13 @@ NSIS_SCRIPT_TEMPLATE = r"""
Var Icon_Syncplay_Handle
Var CheckBox_Associate
Var CheckBox_VLC
Var CheckBox_AutomaticUpdates
Var CheckBox_StartMenuShortcut
Var CheckBox_DesktopShortcut
Var CheckBox_QuickLaunchShortcut
Var CheckBox_Associate_State
Var CheckBox_VLC_State
Var CheckBox_AutomaticUpdates_State
Var CheckBox_StartMenuShortcut_State
Var CheckBox_DesktopShortcut_State
Var CheckBox_QuickLaunchShortcut_State
@ -237,10 +242,10 @@ NSIS_SCRIPT_TEMPLATE = r"""
$${NSD_CreateGroupBox} 1u 27u 264u 30u "$$(^DirSubText)"
Pop $$GroupBox_DirSub
$${NSD_CreateLabel} 0u 111u 265u 8u "$$(^SpaceRequired)$$SizeMB"
$${NSD_CreateLabel} 0u 122u 132 8u "$$(^SpaceRequired)$$SizeMB"
Pop $$Label_Size
$${NSD_CreateLabel} 0u 122u 265u 8u "$$(^SpaceAvailable)$$AvailibleSpaceGiB.$$AvailibleSpaceGB"
$${NSD_CreateLabel} 321u 122u 132 8u "$$(^SpaceAvailable)$$AvailibleSpaceGiB.$$AvailibleSpaceGB"
Pop $$Label_Space
$${NSD_CreateCheckBox} 8u 59u 187u 10u "$$(^Associate)"
@ -253,16 +258,20 @@ NSIS_SCRIPT_TEMPLATE = r"""
$${NSD_CreateCheckBox} 8u 72u 250u 10u "$$(^VLC)"
Pop $$CheckBox_VLC
$${NSD_CreateLabel} 8u 85u 187u 10u "$$(^Shortcut)"
$${NSD_CreateCheckBox} 8u 85u 250u 10u "$$(^AutomaticUpdates)"
Pop $$CheckBox_AutomaticUpdates
$${NSD_Check} $$CheckBox_AutomaticUpdates
$${NSD_CreateLabel} 8u 98u 187u 10u "$$(^Shortcut)"
Pop $$Label_Shortcut
$${NSD_CreateCheckbox} 8u 98u 60u 10u "$$(^StartMenu)"
$${NSD_CreateCheckbox} 8u 111u 60u 10u "$$(^StartMenu)"
Pop $$CheckBox_StartMenuShortcut
$${NSD_CreateCheckbox} 78u 98u 70u 10u "$$(^Desktop)"
$${NSD_CreateCheckbox} 78u 111u 70u 10u "$$(^Desktop)"
Pop $$CheckBox_DesktopShortcut
$${NSD_CreateCheckbox} 158u 98u 130u 10u "$$(^QuickLaunchBar)"
$${NSD_CreateCheckbox} 158u 111u 130u 10u "$$(^QuickLaunchBar)"
Pop $$CheckBox_QuickLaunchShortcut
$${If} $$CheckBox_Associate_State == $${BST_CHECKED}
@ -287,6 +296,10 @@ NSIS_SCRIPT_TEMPLATE = r"""
$${NSD_Check} $$CheckBox_QuickLaunchShortcut
$${EndIf}
$${If} $$CheckBox_AutomaticUpdates_State == $${BST_CHECKED}
$${NSD_Check} $$CheckBox_AutomaticUpdates
$${EndIf}
nsDialogs::Show
$${NSD_FreeIcon} $$Icon_Syncplay_Handle
@ -297,6 +310,7 @@ NSIS_SCRIPT_TEMPLATE = r"""
$${NSD_GetText} $$Text_Directory $$INSTDIR
$${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
$${NSD_GetState} $$CheckBox_DesktopShortcut $$CheckBox_DesktopShortcut_State
$${NSD_GetState} $$CheckBox_QuickLaunchShortcut $$CheckBox_QuickLaunchShortcut_State
@ -444,6 +458,11 @@ NSIS_SCRIPT_TEMPLATE = r"""
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Syncplay" "NoRepair" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Syncplay" "EstimatedSize" "$$SizeHex"
WriteINIStr $$APPDATA\syncplay.ini general language $$(^SyncplayLanguage)
$${If} $$CheckBox_AutomaticUpdates_State == $${BST_CHECKED}
WriteINIStr $$APPDATA\syncplay.ini general CheckForUpdatesAutomatically "True"
$${Else}
WriteINIStr $$APPDATA\syncplay.ini general CheckForUpdatesAutomatically "False"
$${EndIf}
FunctionEnd
Function un.installConfirm
@ -624,7 +643,8 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock
'resources/mpv.png','resources/vlc.png', 'resources/house.png', 'resources/film_link.png',
'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/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 = ["resources/icon.ico", "resources/syncplay.png"]
resources.extend(guiIcons)

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

BIN
resources/cog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

View File

@ -5,7 +5,7 @@
Principal author: Etoh
Other contributors: DerGenaue, jb
Project: http://syncplay.pl/
Version: 0.2.3
Version: 0.2.4
Note:
* This interface module is intended to be used in conjunction with Syncplay.
@ -68,6 +68,9 @@ You may also need to re-copy the syncplay.lua file when you update VLC.
display-osd: [placement on screen <center/left/right/top/bottom/top-left/top-right/bottom-left/bottom-right>], [duration in seconds], [message]
? >> display-osd-error: no-input
display-secondary-osd: [placement on screen <center/left/right/top/bottom/top-left/top-right/bottom-left/bottom-right>], [duration in seconds], [message]
? >> display-secondary-osd-error: no-input
load-file: [filepath]
* >> load-file-attempted
@ -78,7 +81,7 @@ You may also need to re-copy the syncplay.lua file when you update VLC.
--]==========================================================================]
local connectorversion = "0.2.3"
local connectorversion = "0.2.4"
local durationdelay = 500000 -- Pause for get_duration command etc for increased reliability (uses microseconds)
local loopsleepduration = 5000 -- Pause for every event loop (uses microseconds)
local quitcheckfrequency = 20 -- Check whether VLC has closed every X loops
@ -108,12 +111,18 @@ local newinputstate
local oldtitle = 0
local newtitle = 0
local channel1
local channel2
local l
local running = true
function radixsafe_tonumber(str)
-- Version of tonumber that works with any radix character (but not thousand seperators)
-- Based on the public domain VLC common.lua us_tonumber() function
str = string.gsub(tostring(str), "[^0-9]", ".")
str = string.gsub(tostring(str), "[^0-9]", ".")
local s, i, d = string.match(str, "^([+-]?)(%d*)%.?(%d*)$")
if not s or not i or not d then
return nil
@ -277,8 +286,8 @@ function set_time ( timetoset)
end
errormsg = set_var("time", radixsafe_tonumber(realtime))
return errormsg
else
return noinput
else
return noinput
end
end
@ -379,17 +388,19 @@ function get_duration ()
if input then
local item = vlc.input.item()
if (item and item:duration()) then
-- Try to get duration, which might not be available straight away
local i = 0
repeat
vlc.misc.mwait(vlc.misc.mdate() + durationdelay)
local i = 0
response = 0
repeat
vlc.misc.mwait(vlc.misc.mdate() + durationdelay)
if item and item:duration() then
response = item:duration()
i = i + 1
until response > 0 or i > 5
else
errormsg = noinput
end
if response < 1 then
response = 0
end
end
i = i + 1
until response > 1 or i > 5
else
errormsg = noinput
end
@ -400,11 +411,16 @@ end
function display_osd ( argument )
-- [Used by display-osd command]
local errormsg
local osdarray
local input = vlc.object.input()
if input then
if input and vlc.osd and vlc.object.vout() then
if not channel1 then
channel1 = vlc.osd.channel_register()
end
if not channel2 then
channel2 = vlc.osd.channel_register()
end
osdarray = get_args(argument,3)
--position, duration, message -> message, , position, duration (converted from seconds to microseconds)
local osdduration = radixsafe_tonumber(osdarray[2]) * 1000 * 1000
@ -415,6 +431,28 @@ function display_osd ( argument )
return errormsg
end
function display_secondary_osd ( argument )
-- [Used by display-secondary-osd command]
local errormsg
local osdarray
local input = vlc.object.input()
if input and vlc.osd and vlc.object.vout() then
if not channel1 then
channel1 = vlc.osd.channel_register()
end
if not channel2 then
channel2 = vlc.osd.channel_register()
end
osdarray = get_args(argument,3)
--position, duration, message -> message, , position, duration (converted from seconds to microseconds)
local osdduration = radixsafe_tonumber(osdarray[2]) * 1000 * 1000
vlc.osd.message(osdarray[3],channel2,osdarray[1],osdduration)
else
errormsg = noinput
end
return errormsg
end
function load_file (filepath)
-- [Used by load-file command]
@ -445,6 +483,7 @@ function do_command ( command, argument)
elseif command == "set-rate" then errormsg = set_var("rate", radixsafe_tonumber(argument))
elseif command == "set-title" then errormsg = set_var("title", radixsafe_tonumber(argument))
elseif command == "display-osd" then errormsg = display_osd(argument)
elseif command == "display-secondary-osd" then errormsg = display_secondary_osd(argument)
elseif command == "load-file" then response = load_file(argument)
elseif command == "close-vlc" then quit_vlc()
else errormsg = unknowncommand
@ -498,7 +537,7 @@ while running == true do
--accept new connections and select active clients
local quitcheckcounter = 0
local fd = l:accept()
local buffer, inputbuffer, responsebuffer = ""
local buffer, inputbuffer, responsebuffer = "", "", ""
while fd >= 0 and running == true do
-- handle read mode
@ -560,7 +599,6 @@ while running == true do
quitcheckcounter = 0
end
end
end
end

BIN
resources/tick_checkbox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

BIN
resources/world_explore.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 987 B

View File

@ -1,3 +1,4 @@
version = '1.3.0'
milestone = 'Akki'
milestone = 'Chami'
release_number = '2'
projectURL = 'http://syncplay.pl/'

View File

@ -68,6 +68,7 @@ class SyncplayClient(object):
self.serverVersion = "0.0.0"
self.lastLeftTime = 0
self.lastPausedOnLeaveTime = None
self.lastLeftUser = u""
self.protocolFactory = SyncClientFactory(self)
self.ui = UiManager(self, ui)
@ -104,8 +105,9 @@ class SyncplayClient(object):
self._userOffset = 0.0
self._speedChanged = False
self.behindFirstDetected = None
self.autoPlay = False
self._warnings = self._WarningManager(self._player, self.userlist, self.ui)
self._warnings = self._WarningManager(self._player, self.userlist, self.ui, 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)
@ -125,6 +127,8 @@ class SyncplayClient(object):
def initPlayer(self, player):
self._player = player
if not self._player.secondaryOSDSupported:
constants.OSD_WARNING_MESSAGE_DURATION = constants.NO_SECONDARY_OSD_WARNING_DURATION
self.scheduleAskPlayer()
def scheduleAskPlayer(self, when=constants.PLAYER_ASK_DELAY):
@ -158,6 +162,20 @@ class SyncplayClient(object):
pauseChange, seeked = self._determinePlayerStateChange(paused, position)
self._playerPosition = position
self._playerPaused = paused
if pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION):
if not paused and not self.userlist.currentUser.isReady() and not self.userlist.areAllOtherUsersInRoomReady():
paused = True
self._player.setPaused(paused)
self._playerPaused = paused
self.changeReadyState(True, manuallyInitiated=True)
pauseChange = False
self.ui.showMessage(getMessage("ready-to-unpause-notification"))
else:
lastPausedDiff = time.time() - self.lastPausedOnLeaveTime if self.lastPausedOnLeaveTime else None
if lastPausedDiff is not None and lastPausedDiff < constants.LAST_PAUSED_DIFF_THRESHOLD:
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:
@ -301,8 +319,10 @@ class SyncplayClient(object):
self.ui.showMessage(getMessage("current-offset-notification").format(self._userOffset))
def onDisconnect(self):
self.resetAutoPlayState()
if self._config['pauseOnLeave']:
self.setPaused(True)
self.lastPausedOnLeaveTime = time.time()
def removeUser(self, username):
if self.userlist.isUserInYourRoom(username):
@ -379,13 +399,14 @@ class SyncplayClient(object):
import random
random.seed()
random_number = random.randrange(1000, 9999)
self.userlist.currentUser.username = "Anonymous" + str(random_number)
self.userlist.currentUser.username = "Anonymous" + str(random_number) # Not localised as this would give away locale
def getUsername(self):
return self.userlist.currentUser.username
def setRoom(self, roomName):
self.userlist.currentUser.room = roomName
self.resetAutoPlayState()
def sendRoom(self):
room = self.userlist.currentUser.room
@ -402,6 +423,8 @@ class SyncplayClient(object):
self.identifyAsController(storedRoomPassword)
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)
self.reIdentifyAsController()
def getRoom(self):
@ -470,15 +493,45 @@ class SyncplayClient(object):
return wrapper
return requireMinVersionDecorator
def changeAutoPlayState(self, newState):
self.autoPlay = newState
self.autoPlayCheck()
def autoPlayCheck(self):
if self.autoPlay and self.userlist.currentUser.canControl() and self.userlist.isReadinessSupported() and self.userlist.areAllUsersInRoomReady():
self.setPaused(False)
def resetAutoPlayState(self):
self.autoPlay = False
self.ui.updateAutoPlayState(False)
@requireMinServerVersion(constants.USER_READY_MIN_VERSION)
def toggleReady(self, manuallyInitiated=True):
self._protocol.setReady(not self.userlist.currentUser.isReady(), manuallyInitiated)
@requireMinServerVersion(constants.USER_READY_MIN_VERSION)
def changeReadyState(self, newState, manuallyInitiated=True):
oldState = self.userlist.currentUser.isReady()
if newState != oldState:
self.toggleReady(manuallyInitiated)
def setReady(self, username, isReady, manuallyInitiated=True):
oldReadyState = self.userlist.isReady(username)
if oldReadyState is None:
oldReadyState = False
self.userlist.setReady(username, isReady)
self.ui.userListChange()
if oldReadyState != isReady:
self._warnings.checkReadyStates()
@requireMinServerVersion(constants.CONTROLLED_ROOMS_MIN_VERSION)
def createControlledRoom(self, roomName):
controlPassword = utils.RandomStringGenerator.generate_room_password()
self.ui.showMessage(u"Attempting to create controlled room '{}' with password '{}'...".format(roomName, controlPassword))
self.lastControlPasswordAttempt = controlPassword
self._protocol.requestControlledRoom(roomName, controlPassword)
def controlledRoomCreated(self, roomName, controlPassword):
self.ui.showMessage(u"Created controlled room '{}' with password '{}'. Please save this information for future reference!".format(roomName, controlPassword))
self.ui.showMessage(getMessage("created-controlled-room-notification").format(roomName, controlPassword))
self.setRoom(roomName)
self.sendRoom()
self._protocol.requestControlledRoom(roomName, controlPassword)
@ -498,7 +551,8 @@ class SyncplayClient(object):
self._protocol.requestControlledRoom(self.getRoom(), controlPassword)
def controllerIdentificationError(self, username, room):
self.ui.showErrorMessage(getMessage("failed-to-identify-as-controller-notification").format(username))
if username == self.getUsername():
self.ui.showErrorMessage(getMessage("failed-to-identify-as-controller-notification").format(username))
def controllerIdentificationSuccess(self, username, roomname):
self.userlist.setUserAsController(username)
@ -517,55 +571,132 @@ class SyncplayClient(object):
if self.controlpasswords.has_key(room):
return self.controlpasswords[room]
def checkForUpdate(self, userInitiated):
try:
import urllib, syncplay, sys, messages, json
params = urllib.urlencode({'version': syncplay.version, 'milestone': syncplay.milestone, 'release_number': syncplay.release_number,
'language': messages.messages["CURRENT"], 'platform': sys.platform, 'userInitiated': userInitiated})
f = urllib.urlopen(constants.SYNCPLAY_UPDATE_URL.format(params))
response = f.read()
response = response.replace("<p>","").replace("</p>","").replace("<br />","").replace("&#8220;","\"").replace("&#8221;","\"") # Fix Wordpress
response = json.loads(response)
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
except:
return "failed", getMessage("update-check-failed-notification").format(syncplay.version), constants.SYNCPLAY_DOWNLOAD_URL
class _WarningManager(object):
def __init__(self, player, userlist, ui):
def __init__(self, player, userlist, ui, client):
self._client = client
self._player = player
self._userlist = userlist
self._ui = ui
self._warnings = {
"room-files-not-same": {
"timer": task.LoopingCall(self.__displayMessageOnOSD,
"room-files-not-same",),
"displayedFor": 0,
},
"alone-in-the-room": {
"timer": task.LoopingCall(self.__displayMessageOnOSD,
"alone-in-the-room",),
"displayedFor": 0,
},
}
"room-file-differences": {
"timer": task.LoopingCall(self.__displayMessageOnOSD, "room-file-differences",
lambda: self._checkRoomForSameFiles(OSDOnly=True),),
"displayedFor": 0,
},
"alone-in-the-room": {
"timer": task.LoopingCall(self.__displayMessageOnOSD, "alone-in-the-room",
lambda: self._checkIfYouReAloneInTheRoom(OSDOnly=True)),
"displayedFor": 0,
},
"not-all-ready": {
"timer": task.LoopingCall(self.__displayMessageOnOSD, "not-all-ready",
lambda: self.checkReadyStates(),),
"displayedFor": 0,
},
}
self.pausedTimer = task.LoopingCall(self.__displayPausedMessagesOnOSD)
self.pausedTimer.start(constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, True)
def checkWarnings(self):
self._checkIfYouReAloneInTheRoom()
self._checkRoomForSameFiles()
self._checkIfYouReAloneInTheRoom(OSDOnly=False)
self._checkRoomForSameFiles(OSDOnly=False)
self.checkReadyStates()
def _checkRoomForSameFiles(self):
def _checkRoomForSameFiles(self, OSDOnly):
if not self._userlist.areAllFilesInRoomSame():
self._ui.showMessage(getMessage("room-files-not-same"), True)
if constants.SHOW_OSD_WARNINGS and not self._warnings["room-files-not-same"]['timer'].running:
self._warnings["room-files-not-same"]['timer'].start(constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, True)
elif self._warnings["room-files-not-same"]['timer'].running:
self._warnings["room-files-not-same"]['timer'].stop()
self._displayReadySameWarning()
if not OSDOnly and constants.SHOW_OSD_WARNINGS and not self._warnings["room-file-differences"]['timer'].running:
self._warnings["room-file-differences"]['timer'].start(constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, True)
elif self._warnings["room-file-differences"]['timer'].running:
self._warnings["room-file-differences"]['timer'].stop()
def _checkIfYouReAloneInTheRoom(self):
def _checkIfYouAreOnlyUserInRoomWhoSupportsReadiness(self):
self._userlist._onlyUserInRoomWhoSupportsReadiness()
def _checkIfYouReAloneInTheRoom(self, OSDOnly):
if self._userlist.areYouAloneInRoom():
self._ui.showMessage(getMessage("alone-in-the-room"), True)
if constants.SHOW_OSD_WARNINGS and not self._warnings["alone-in-the-room"]['timer'].running:
self._warnings["alone-in-the-room"]['timer'].start(constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, True)
self._ui.showOSDMessage(getMessage("alone-in-the-room"), constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, secondaryOSD=True)
if not OSDOnly:
self._ui.showMessage(getMessage("alone-in-the-room"), True)
if constants.SHOW_OSD_WARNINGS and not self._warnings["alone-in-the-room"]['timer'].running:
self._warnings["alone-in-the-room"]['timer'].start(constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, True)
elif self._warnings["alone-in-the-room"]['timer'].running:
self._warnings["alone-in-the-room"]['timer'].stop()
def __displayMessageOnOSD(self, warningName):
def checkReadyStates(self):
if not self._client:
return
if self._client.getPlayerPaused() or not self._userlist.currentUser.isReady():
self._warnings["not-all-ready"]["displayedFor"] = 0
if self._userlist.areYouAloneInRoom() or not self._userlist.currentUser.canControl():
if self._warnings["not-all-ready"]['timer'].running:
self._warnings["not-all-ready"]['timer'].stop()
elif not self._userlist.areAllUsersInRoomReady():
self._displayReadySameWarning()
if constants.SHOW_OSD_WARNINGS and not self._warnings["not-all-ready"]['timer'].running:
self._warnings["not-all-ready"]['timer'].start(constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, True)
elif self._warnings["not-all-ready"]['timer'].running:
self._warnings["not-all-ready"]['timer'].stop()
self._displayReadySameWarning()
elif self._client.getPlayerPaused() or not self._userlist.currentUser.isReady():
self._displayReadySameWarning()
def _displayReadySameWarning(self):
if not self._client._player:
return
osdMessage = None
if not self._userlist.areAllFilesInRoomSame():
fileDifferencesMessage = getMessage("room-file-differences").format(self._userlist.getFileDifferencesForRoom())
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()))
else:
osdMessage = u"{}{}{}".format(fileDifferencesMessage, self._client._player.osdMessageSeparator, getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady()))
else:
osdMessage = fileDifferencesMessage
elif self._userlist.isReadinessSupported():
if self._userlist.areAllUsersInRoomReady():
osdMessage = getMessage("all-users-ready").format(self._userlist.readyUserCount())
else:
osdMessage = getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady())
if osdMessage:
self._ui.showOSDMessage(osdMessage, constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, secondaryOSD=True)
def __displayMessageOnOSD(self, warningName, warningFunction):
if constants.OSD_WARNING_MESSAGE_DURATION > self._warnings[warningName]["displayedFor"]:
self._ui.showOSDMessage(getMessage(warningName), constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL)
warningFunction()
self._warnings[warningName]["displayedFor"] += constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL
else:
self._warnings[warningName]["displayedFor"] = 0
self._warnings[warningName]["timer"].stop()
try:
self._warnings[warningName]["timer"].stop()
except:
pass
def __displayPausedMessagesOnOSD(self):
if self._client and self._client._player and self._client.getPlayerPaused():
self._checkRoomForSameFiles(OSDOnly=True)
self.checkReadyStates()
elif not self._userlist.currentUser.isReady(): # CurrentUser should always be reminded they are set to not ready
self.checkReadyStates()
class SyncplayUser(object):
def __init__(self, username=None, room=None, file_=None):
self.ready = None
self.username = username
self.room = room
self.file = file_
@ -573,10 +704,10 @@ class SyncplayUser(object):
def setFile(self, filename, duration, size):
file_ = {
"name": filename,
"duration": duration,
"size":size
}
"name": filename,
"duration": duration,
"size": size
}
self.file = file_
def isFileSame(self, file_):
@ -611,6 +742,12 @@ class SyncplayUser(object):
else:
return False
def isReady(self):
return self.ready
def setReady(self, ready):
self.ready = ready
class SyncplayUserlist(object):
def __init__(self, ui, client):
self.currentUser = SyncplayUser()
@ -619,6 +756,14 @@ class SyncplayUserlist(object):
self._client = client
self._roomUsersChanged = True
def isReadinessSupported(self):
if not utils.meetsMinVersion(self._client.serverVersion,constants.USER_READY_MIN_VERSION):
return False
elif self.onlyUserInRoomWhoSupportsReadiness():
return False
else:
return True
def isRoomSame(self, room):
if room and self.currentUser.room and self.currentUser.room == room:
return True
@ -644,30 +789,49 @@ class SyncplayUserlist(object):
message += getMessage("playing-notification/room-addendum").format(room)
self.ui.showMessage(message, hideFromOSD)
if self.currentUser.file and not self.currentUser.isFileSame(file_) and self.currentUser.room == room:
message = getMessage("file-different-notification").format(username)
self.ui.showMessage(message, hideFromOSD)
differences = []
differentName = not utils.sameFilename(self.currentUser.file['name'], file_['name'])
differentSize = not utils.sameFilesize(self.currentUser.file['size'], file_['size'])
differentDuration = not utils.sameFileduration(self.currentUser.file['duration'], file_['duration'])
if differentName:
differences.append("filename")
if differentSize:
differences.append("size")
if differentDuration:
differences.append("duration")
message = getMessage("file-differences-notification") + ", ".join(differences)
self.ui.showMessage(message, hideFromOSD)
message = getMessage("file-differences-notification").format(self.getFileDifferencesForUser(self.currentUser.file, file_))
self.ui.showMessage(message, True)
def getFileDifferencesForUser(self, currentUserFile, otherUserFile):
differences = []
differentName = not utils.sameFilename(currentUserFile['name'], otherUserFile['name'])
differentSize = not utils.sameFilesize(currentUserFile['size'], otherUserFile['size'])
differentDuration = not utils.sameFileduration(currentUserFile['duration'], otherUserFile['duration'])
if differentName: differences.append(getMessage("file-difference-filename"))
if differentSize: differences.append(getMessage("file-difference-filesize"))
if differentDuration: differences.append(getMessage("file-difference-duration"))
return ", ".join(differences)
def addUser(self, username, room, file_, noMessage=False, isController=None):
def getFileDifferencesForRoom(self):
differences = []
differentName = False
differentSize = False
differentDuration = False
for otherUser in self._users.itervalues():
if otherUser.room == self.currentUser.room:
if not utils.sameFilename(self.currentUser.file['name'], otherUser.file['name']):
differentName = True
if not utils.sameFilesize(self.currentUser.file['size'], otherUser.file['size']):
differentSize = True
if not utils.sameFileduration(self.currentUser.file['duration'], otherUser.file['duration']):
differentDuration = True
if differentName: differences.append(getMessage("file-difference-filename"))
if differentSize: differences.append(getMessage("file-difference-filesize"))
if differentDuration: differences.append(getMessage("file-difference-duration"))
return ", ".join(differences)
def addUser(self, username, room, file_, noMessage=False, isController=None, isReady=None):
if username == self.currentUser.username:
if isController is not None:
self.currentUser.setControllerStatus(isController)
self.currentUser.setReady(isReady)
return
user = SyncplayUser(username, room, file_)
if isController is not None:
user.setControllerStatus(isController)
self._users[username] = user
user.setReady(isReady)
if not noMessage:
self.__showUserChangeMessage(username, room, file_)
self.userListChange(room)
@ -716,11 +880,46 @@ class SyncplayUserlist(object):
user = self._users[username]
user.setControllerStatus(True)
def areAllFilesInRoomSame(self):
def areAllUsersInRoomReady(self):
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.file and not self.currentUser.isFileSame(user.file):
if user.canControl():
return False
if user.room == self.currentUser.room and user.isReady() == False:
return False
return True
def areAllOtherUsersInRoomReady(self):
for user in self._users.itervalues():
if user.room == self.currentUser.room and user.isReady() == False:
return False
return True
def readyUserCount(self):
readyCount = 0
if self.currentUser.isReady():
readyCount += 1
for user in self._users.itervalues():
if user.room == self.currentUser.room and user.isReady():
readyCount += 1
return readyCount
def usersInRoomNotReady(self):
notReady = []
if not self.currentUser.isReady():
notReady.append(self.currentUser.username)
for user in self._users.itervalues():
if user.room == self.currentUser.room and user.isReady() == False:
notReady.append(user.username)
return ", ".join(notReady)
def areAllFilesInRoomSame(self):
if self.currentUser.file:
for user in self._users.itervalues():
if user.room == self.currentUser.room and user.file and not self.currentUser.isFileSame(user.file):
if user.canControl():
return False
return True
def areYouAloneInRoom(self):
@ -728,6 +927,12 @@ class SyncplayUserlist(object):
if user.room == self.currentUser.room:
return False
return True
def onlyUserInRoomWhoSupportsReadiness(self):
for user in self._users.itervalues():
if user.room == self.currentUser.room and user.isReady() is not None:
return False
return True
def isUserInYourRoom(self, username):
for user in self._users.itervalues():
@ -744,6 +949,22 @@ class SyncplayUserlist(object):
return True
return False
def isReady(self, username):
if self.currentUser.username == username:
return self.currentUser.isReady()
for user in self._users.itervalues():
if user.username == username:
return user.isReady()
return None
def setReady(self, username, isReady):
if self.currentUser.username == username:
self.currentUser.setReady(isReady)
elif self._users.has_key(username):
self._users[username].setReady(isReady)
self._client.autoPlayCheck()
def userListChange(self, room = None):
if room is not None and self.isRoomSame(room):
self._roomUsersChanged = True
@ -766,6 +987,7 @@ class SyncplayUserlist(object):
rooms[self.currentUser.room].append(self.currentUser)
rooms = self.sortList(rooms)
self.ui.showUserList(self.currentUser, rooms)
self._client.autoPlayCheck()
def clearList(self):
self._users = {}
@ -780,22 +1002,44 @@ class UiManager(object):
def __init__(self, client, ui):
self._client = client
self.__ui = ui
self.lastPrimaryOSDMessage = None
self.lastPrimaryOSDEndTime = None
self.lastSecondaryOSDMessage = None
self.lastSecondaryOSDEndTime = None
self.lastError = ""
def showDebugMessage(self, message):
if constants.DEBUG_MODE and message.rstrip():
print "{}{}".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()),message.rstrip())
def showMessage(self, message, noPlayer=False, noTimestamp=False):
if not noPlayer: self.showOSDMessage(message)
def showMessage(self, message, noPlayer=False, noTimestamp=False, secondaryOSD=False):
if not noPlayer: self.showOSDMessage(message, duration=constants.OSD_DURATION, secondaryOSD=secondaryOSD)
self.__ui.showMessage(message, noTimestamp)
def updateAutoPlayState(self, newState):
self.__ui.updateAutoPlayState(newState)
def showUserList(self, currentUser, rooms):
self.__ui.showUserList(currentUser, rooms)
def showOSDMessage(self, message, duration=constants.OSD_DURATION):
if constants.SHOW_OSD and self._client._player:
self._client._player.displayMessage(message, duration * 1000)
def showOSDMessage(self, message, duration=constants.OSD_DURATION, secondaryOSD=False):
if secondaryOSD and not constants.SHOW_OSD_WARNINGS:
return
if not self._client._player:
return
if constants.SHOW_OSD and self._client and self._client._player:
if not self._client._player.secondaryOSDSupported:
if secondaryOSD:
self.lastSecondaryOSDMessage = message
self.lastSecondaryOSDEndTime = time.time() + constants.NO_SECONDARY_OSD_WARNING_DURATION
if self.lastPrimaryOSDEndTime and time.time() < self.lastPrimaryOSDEndTime:
message = u"{}{}{}".format(message, self._client._player.osdMessageSeparator, self.lastPrimaryOSDMessage)
else:
self.lastPrimaryOSDMessage = message
self.lastPrimaryOSDEndTime = time.time() + constants.OSD_DURATION
if self.lastSecondaryOSDEndTime and time.time() < self.lastSecondaryOSDEndTime:
message = u"{}{}{}".format(self.lastSecondaryOSDMessage, self._client._player.osdMessageSeparator, message)
self._client._player.displayMessage(message, duration * 1000, secondaryOSD)
def setControllerStatus(self, username, isController):
self.__ui.setControllerStatus(username, isController)

View File

@ -1,8 +1,9 @@
# You might want to change these
DEFAULT_PORT = 8999
OSD_DURATION = 3
OSD_WARNING_MESSAGE_DURATION = 15
MPC_OSD_POSITION = 2 #Right corner, 1 for left
OSD_WARNING_MESSAGE_DURATION = 5
NO_SECONDARY_OSD_WARNING_DURATION = 13
MPC_OSD_POSITION = 1 #Right corner, 1 for left
MPLAYER_OSD_LEVEL = 1
UI_TIME_FORMAT = "[%X] "
CONFIG_NAMES = [".syncplay", "syncplay.ini"] #Syncplay searches first to last
@ -12,7 +13,6 @@ RECENT_CLIENT_THRESHOLD = "1.2.9" #This and higher considered 'recent' clients
WARN_OLD_CLIENTS = True #Use MOTD to inform old clients to upgrade
LIST_RELATIVE_CONFIGS = True # Print list of relative configs loaded
SHOW_CONTACT_INFO = True # Displays dev contact details below list in GUI
SHOW_BUTTON_LABELS = True # If disabled, only shows icons for main GUI buttons
SHOW_TOOLTIPS = True
WARN_ABOUT_MISSING_STRINGS = False # (If debug mode is enabled)
FALLBACK_INITIAL_LANGUAGE = "en"
@ -28,6 +28,7 @@ SHOW_DURATION_NOTIFICATION = True
DEBUG_MODE = False
#Changing these might be ok
AUTOMATIC_UPDATE_CHECK_FREQUENCY = 7 * 86400 # Days converted into seconds
DEFAULT_REWIND_THRESHOLD = 4
MINIMUM_REWIND_THRESHOLD = 3
DEFAULT_FASTFORWARD_THRESHOLD = 5
@ -45,9 +46,9 @@ PROTOCOL_TIMEOUT = 12.5
RECONNECT_RETRIES = 10
SERVER_STATE_INTERVAL = 1
WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1
MERGE_PLAYPAUSE_BUTTONS = False
SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear on some media players
#Usually there's no need to adjust these
LAST_PAUSED_DIFF_THRESHOLD = 2
FILENAME_STRIP_REGEX = u"[-~_\.\[\](): ]"
CONTROL_PASSWORD_STRIP_REGEX = u"[^a-zA-Z0-9\-]"
ROOM_NAME_STRIP_REGEX = u"^(\+)(?P<roomnamebase>.*)(:)(\w{12})$"
@ -58,10 +59,12 @@ COMMANDS_ROOM = ["r", "room"]
COMMANDS_HELP = ['help', 'h', '?', '/?', r'\?']
COMMANDS_CREATE = ['c','create']
COMMANDS_AUTH = ['a','auth']
COMMANDS_TOGGLE = ['t','toggle']
MPC_MIN_VER = "1.6.4"
VLC_MIN_VERSION = "2.0.0"
VLC_INTERFACE_MIN_VERSION = "0.2.1"
VLC_INTERFACE_MIN_VERSION = "0.2.4"
CONTROLLED_ROOMS_MIN_VERSION = "1.3.0"
USER_READY_MIN_VERSION = "1.3.0"
MPC_PATHS = [
r"c:\program files (x86)\mpc-hc\mpc-hc.exe",
r"c:\program files\mpc-hc\mpc-hc.exe",
@ -115,7 +118,10 @@ STYLE_SUBCHECKBOX = "QCheckBox, QLabel {{ margin-left: 6px; padding-left: 21px;
STYLE_SUBLABEL = "QCheckBox, QLabel {{ margin-left: 6px; padding-left: 16px; background:url('{}') left no-repeat }}" #Graphic path
STYLE_ERRORLABEL = "QLabel { color : black; border-style: outset; border-width: 2px; border-radius: 7px; border-color: red; padding: 2px; background: #FFAAAA; }"
STYLE_SUCCESSLABEL = "QLabel { color : black; border-style: outset; border-width: 2px; border-radius: 7px; border-color: green; padding: 2px; background: #AAFFAA; }"
STYLE_READY_PUSHBUTTON = "QPushButton { text-align: left; padding: 10px 5px 10px 5px;}"
STYLE_AUTO_PLAY_PUSHBUTTON = "QPushButton { text-align: left; padding: 5px 5px 5px 5px; }"
STYLE_NOTIFICATIONBOX = "Username { color: #367AA9; font-weight:bold; }"
STYLE_CONTACT_INFO = u"<span style=\"color: grey\"><strong><small>{}</span><br /><br />" # Contact info message
STYLE_USERNAME = "color: #367AA9; font-weight:bold;"
STYLE_ERRORNOTIFICATION = "color: red;"
STYLE_DIFFERENTITEM_COLOR = 'red'
@ -137,7 +143,7 @@ VLC_SLAVE_NONOSX_ARGS = ['--no-one-instance', '--no-one-instance-when-started-fr
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>.+))?"
UI_OFFSET_REGEX = r"^(?:o|offset)\ ?(?P<sign>[/+-])?(?P<time>\d{1,4}(?:[^\d\.](?:\d{1,6})){0,2}(?:\.(?:\d{1,3}))?)$"
UI_OFFSET_REGEX = r"^(?:o|offset)\ ?(?P<sign>[/+-])?(?P<time>\d{1,9}(?:[^\d\.](?:\d{1,9})){0,2}(?:\.(?:\d{1,3}))?)$"
UI_SEEK_REGEX = r"^(?:s|seek)?\ ?(?P<sign>[+-])?(?P<time>\d{1,4}(?:[^\d\.](?:\d{1,6})){0,2}(?:\.(?:\d{1,3}))?)$"
PARSE_TIME_REGEX = r'(:?(?:(?P<hours>\d+?)[^\d\.])?(?:(?P<minutes>\d+?))?[^\d\.])?(?P<seconds>\d+?)(?:\.(?P<miliseconds>\d+?))?$'
SERVER_MAX_TEMPLATE_LENGTH = 10000
@ -152,3 +158,6 @@ CONFIG_NAME_MARKER = ":"
CONFIG_VALUE_MARKER = "="
USERITEM_CONTROLLER_ROLE = 0
USERITEM_READY_ROLE = 1
SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params
SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/"

View File

@ -7,7 +7,7 @@ en = {
# Client notifications
"config-cleared-notification" : "Settings cleared. Changes will be saved when you store a valid configuration.",
"relative-config-notification" : "Loaded relative configuration file(s): {}",
"relative-config-notification" : u"Loaded relative configuration file(s): {}",
"connection-attempt-notification" : "Attempting to connect to {}:{}", # Port, IP
"reconnection-attempt-notification" : "Connection with server lost, attempting to reconnect",
@ -21,31 +21,48 @@ en = {
"slowdown-notification" : "Slowing down due to time difference with <{}>", # User
"revert-notification" : "Reverting speed back to normal",
"pause-notification" : "<{}> paused", # User
"unpause-notification" : "<{}> unpaused", # User
"seek-notification" : "<{}> jumped from {} to {}", # User, from time, to time
"pause-notification" : u"<{}> paused", # User
"unpause-notification" : u"<{}> unpaused", # User
"seek-notification" : u"<{}> jumped from {} to {}", # User, from time, to time
"current-offset-notification" : "Current offset: {} seconds", # Offset
"room-join-notification" : "<{}> has joined the room: '{}'", # User
"left-notification" : "<{}> has left", # User
"left-paused-notification" : "<{}> left, <{}> paused", # User who left, User who paused
"playing-notification" : "<{}> is playing '{}' ({})", # User, file, duration
"playing-notification/room-addendum" : " in room: '{}'", # Room
"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
"playing-notification" : u"<{}> is playing '{}' ({})", # User, file, duration
"playing-notification/room-addendum" : u" in room: '{}'", # Room
"identifying-as-controller-notification" : u"Identifying as room controller with password '{}'...",
"failed-to-identify-as-controller-notification" : u"<{}> failed to identify as a room controller.",
"authenticated-as-controller-notification" : u"<{}> authenticated as a room controller",
"not-all-ready" : u"Not ready: {}", # Usernames
"all-users-ready" : u"Everyone is ready ({} users)", #Number of ready users
"ready-to-unpause-notification" : u"You are now set as ready - unpause again to unpause",
"identifying-as-controller-notification" : u"Identifying as room operator with password '{}'...",
"failed-to-identify-as-controller-notification" : u"<{}> failed to identify as a room operator.",
"authenticated-as-controller-notification" : u"<{}> authenticated as a room operator",
"created-controlled-room-notification" : u"Created managed room '{}' with password '{}'. Please save this information for future reference!", # RoomName, controlPassword
"file-different-notification" : "File you are playing appears to be different from <{}>'s", # User
"file-differences-notification" : "Your file differs in the following way(s): ",
"room-files-not-same" : "Not all files played in the room are the same",
"alone-in-the-room": "You're alone in the room",
"file-differences-notification" : u"Your file differs in the following way(s): {}", # Differences
"room-file-differences" : u"File differences: {}", # File differences (filename, size, and/or duration)
"file-difference-filename" : u"name",
"file-difference-filesize" : u"size",
"file-difference-duration" : u"duration",
"alone-in-the-room": u"You're alone in the room",
"different-filesize-notification" : " (their file size is different from yours!)",
"file-played-by-notification" : "File: {} is being played by:", # File
"different-filesize-notification" : u" (their file size is different from yours!)",
"userlist-playing-notification" : u"{} is playing:", #Username
"file-played-by-notification" : u"File: {} is being played by:", # File
"no-file-played-notification" : u"{} is not playing a file", # Username
"notplaying-notification" : "People who are not playing any file:",
"userlist-room-notification" : "In room '{}':", # Room
"userlist-room-notification" : u"In room '{}':", # Room
"userlist-file-notification" : "File",
"controller-userlist-userflag" : "Operator",
"ready-userlist-userflag" : "Ready",
"update-check-failed-notification" : u"Could not automatically check whether Syncplay {} is up to date. Want to visit http://syncplay.pl/ to manually check for updates?", #Syncplay version
"syncplay-uptodate-notification" : u"Syncplay is up to date",
"syncplay-updateavailable-notification" : u"A new version of Syncplay is available. Do you want to download it?",
"mplayer-file-required-notification" : "Syncplay using mplayer requires you to provide file when starting",
"mplayer-file-required-notification/example" : "Usage example: syncplay [options] [url|path/]filename",
@ -59,13 +76,15 @@ en = {
"commandlist-notification/pause" : "\tp - toggle pause",
"commandlist-notification/seek" : "\t[s][+-]time - seek to the given value of time, if + or - is not specified it's absolute time in seconds or min:sec",
"commandlist-notification/help" : "\th - this help",
"commandlist-notification/create" : "\tc [name] - create controlled room using name of current room",
"commandlist-notification/auth" : "\ta [password] - authenticate as room controller with controller password",
"commandlist-notification/toggle" : u"\tt - toggles whether you are ready to watch or not",
"commandlist-notification/create" : "\tc [name] - create managed room using name of current room",
"commandlist-notification/auth" : "\ta [password] - authenticate as room operator with controller password",
"syncplay-version-notification" : "Syncplay version: {}", # syncplay.version
"more-info-notification" : "More info available at: {}", # projectURL
"gui-data-cleared-notification" : "Syncplay has cleared the path and window state data used by the GUI.",
"language-changed-msgbox-label" : "Language will be changed when you run Syncplay.",
"promptforupdate-label" : u"Is it okay for Syncplay to automatically check for updates from time to time?",
"vlc-version-mismatch": "Warning: You are running VLC version {}, but Syncplay is designed to run on VLC {} and above.", # VLC version, VLC min version
"vlc-interface-version-mismatch": "Warning: You are running version {} of the Syncplay interface module for VLC, but Syncplay is designed to run with version {} and above.", # VLC interface version, VLC interface min version
@ -106,6 +125,9 @@ en = {
"vlc-failed-versioncheck": "This version of VLC is not supported by Syncplay. Please use VLC 2.",
"vlc-failed-other" : "When trying to load the syncplay.lua interface script VLC has provided the following error: {}", # Syncplay Error
"invalid-seek-value" : u"Invalid seek value",
"invalid-offset-value" : u"Invalid offset value",
# Client arguments
"argument-description" : 'Solution to synchronize playback of multiple MPlayer and MPC-HC instances over the network.',
"argument-epilog" : 'If no options supplied _config values will be used',
@ -151,32 +173,36 @@ en = {
"privacy-dontsend-option" : "Don't send",
"filename-privacy-label" : "Filename information:",
"filesize-privacy-label" : "File size information:",
"checkforupdatesautomatically-label" : "Check for Syncplay updates automatically",
"slowondesync-label" : "Slow down on minor desync (not supported on MPC-HC)",
"rewindondesync-label" : "Rewind on major desync (highly recommended)",
"rewindondesync-label" : "Rewind on major desync (recommended)",
"fastforwardondesync-label" : "Fast-forward if lagging behind (recommended)",
"dontslowdownwithme-label" : "Never slow down or rewind others (experimental)",
"pauseonleave-label" : "Pause when user leaves (e.g. if they are disconnected)",
"forceguiprompt-label" : "Don't always show this dialog", # (Inverted)
"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)",
"showosdwarnings-label" : "Include warnings (e.g. when files are different, users not ready)",
"showsameroomosd-label" : "Include events in your room",
"shownoncontrollerosd-label" : "Include events from non-controllers in controlled rooms",
"shownoncontrollerosd-label" : "Include events from non-operators in managed rooms",
"showdifferentroomosd-label" : "Include events in other rooms",
"showslowdownosd-label" :"Include slowing down / reverting notifications",
"showcontactinfo-label" : "Show contact info box",
"language-label" : "Language",
"automatic-language" : "Automatic ({})", # Automatic language #
"language-label" : "Language:",
"automatic-language" : "Default ({})", # Default language
"showdurationnotification-label" : "Warn about media duration mismatches",
"basics-label" : "Basics",
"misc-label" : u"Misc",
"core-behaviour-title" : "Core room behaviour",
"syncplay-internals-title" : u"Syncplay internals",
"sync-label" : "Sync",
"sync-lagging-title" : "If others are lagging behind...",
"sync-other-title" : "Other sync options",
"sync-otherslagging-title" : "If others are lagging behind...",
"sync-youlaggging-title" : "If you are lagging behind...",
"messages-label" : "Messages",
"messages-osd-title" : "On-screen Display settings",
"messages-other-title" : "Other display settings",
"privacy-label" : "Privacy",
"privacy-label" : "Privacy", # Currently unused, but will be brought back if more space is needed in Misc tab
"privacy-title" : "Privacy settings",
"help-label" : "Help",
@ -184,14 +210,18 @@ en = {
"run-label" : "Run Syncplay",
"storeandrun-label" : "Store configuration and run Syncplay",
"contact-label" : "Have an idea, bug report or feedback? E-mail <a href=\"mailto:dev@syncplay.pl\">dev@syncplay.pl</a>, chat via the <a href=\"https://webchat.freenode.net/?channels=#syncplay\">#Syncplay IRC channel</a> on irc.freenode.net or <a href=\"https://github.com/Uriziel/syncplay/issues/new\">raise an issue via GitHub</a>. Also check out <a href=\"http://syncplay.pl/\">http://syncplay.pl/</a> for info, help and updates.",
"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/new\"><nobr>raise an issue</nobr></a> via GitHub or visit <a href=\"http://syncplay.pl/\"><nobr>http://syncplay.pl/</nobr></a>",
"joinroom-guibuttonlabel" : "Join room",
"seektime-guibuttonlabel" : "Seek to time",
"undoseek-guibuttonlabel" : "Undo seek",
"togglepause-guibuttonlabel" : "Toggle pause",
"play-guibuttonlabel" : "Play",
"pause-guibuttonlabel" : "Pause",
"joinroom-menu-label" : "Join room",
"seektime-menu-label" : "Seek to time",
"undoseek-menu-label" : "Undo seek",
"play-menu-label" : "Play",
"pause-menu-label" : "Pause",
"playbackbuttons-menu-label" : u"Show playback buttons",
"autoplay-menu-label" : u"Show auto-play button",
"autoplay-guipushbuttonlabel" : u"Auto-play when everyone is ready",
"ready-guipushbuttonlabel" : u"I'm ready to watch!",
"roomuser-heading-label" : "Room / User",
"size-heading-label" : "Size",
@ -199,31 +229,35 @@ en = {
"filename-heading-label" : "Filename",
"notifications-heading-label" : "Notifications",
"userlist-heading-label" : "List of who is playing what",
"othercommands-heading-label" : "Other commands",
"room-heading-label" : "Room",
"seek-heading-label" : "Seek",
"browseformedia-label" : "Browse for media files",
"file-menu-label" : "&File", # & precedes shortcut key
"openmedia-menu-label" : "&Open media file",
"openstreamurl-menu-label" : "Open &media stream URL",
"exit-menu-label" : "E&xit",
"advanced-menu-label" : "&Advanced",
"window-menu-label" : "&Window",
"setoffset-menu-label" : "Set &offset",
"createcontrolledroom-menu-label" : "&Create controlled room",
"identifyascontroller-menu-label" : "&Identify as room controller",
"createcontrolledroom-menu-label" : "&Create managed room",
"identifyascontroller-menu-label" : "&Identify as room operator",
"playback-menu-label" : u"&Playback",
"help-menu-label" : "&Help",
"userguide-menu-label" : "Open user &guide",
"update-menu-label" : "Check for &update",
"setoffset-msgbox-label" : "Set offset",
"offsetinfo-msgbox-label" : "Offset (see http://syncplay.pl/guide/ for usage instructions):",
"promptforstreamurl-msgbox-label" : "Open media stream URL",
"promptforstreamurlinfo-msgbox-label" : "Stream URL",
"createcontrolledroom-msgbox-label" : "Create controlled room",
"controlledroominfo-msgbox-label" : "Enter name of controlled room\r\n(see http://syncplay.pl/guide/ for usage instructions):",
"createcontrolledroom-msgbox-label" : "Create managed room",
"controlledroominfo-msgbox-label" : "Enter name of managed room\r\n(see http://syncplay.pl/guide/ for usage instructions):",
"identifyascontroller-msgbox-label" : "Identify as Room Controller",
"identifyascontroller-msgbox-label" : "Identify as room operator",
"identifyinfo-msgbox-label" : "Enter controller password for this room\r\n(see http://syncplay.pl/guide/ for usage instructions):",
"megabyte-suffix" : " MB",
@ -241,38 +275,35 @@ en = {
"more-tooltip" : "Display less frequently used settings.",
"slowdown-threshold-tooltip" : "Time ahead of slowest client before temporarily reducing playback speed (default: {} secs).".format(constants.DEFAULT_SLOWDOWN_KICKIN_THRESHOLD),
"rewind-threshold-tooltip" : "Time ahead slowest client before seeking to get back in sync (default: {} secs).".format(constants.DEFAULT_REWIND_THRESHOLD),
"fastforward-threshold-tooltip" : "Time behind room controller before seeking to get back in sync (default: {} secs).".format(constants.DEFAULT_FASTFORWARD_THRESHOLD),
"fastforward-threshold-tooltip" : "Time behind room operator before seeking to get back in sync (default: {} secs).".format(constants.DEFAULT_FASTFORWARD_THRESHOLD),
"filename-privacy-tooltip" : "Privacy mode for sending currently playing filename to server.",
"filesize-privacy-tooltip" : "Privacy mode for sending size of currently playing file to server.",
"privacy-sendraw-tooltip" : "Send this information without obfuscation. This is the default option with most functionality.",
"privacy-sendhashed-tooltip" : "Send a hashed version of the information, making it less visible to other clients.",
"privacy-dontsend-tooltip" : "Do not send this information to the server. This provides for maximum privacy.",
"checkforupdatesautomatically-tooltip" : "Regularly check with the Syncplay website to see whether a new version of Syncplay is available.",
"slowondesync-tooltip" : "Reduce playback rate temporarily when needed to bring you back in sync with other viewers. Not supported on MPC-HC.",
"dontslowdownwithme-tooltip" : "Means others do not get slowed down or rewinded if your playback is lagging. Useful for room controllers.",
"dontslowdownwithme-tooltip" : "Means others do not get slowed down or rewinded if your playback is lagging. Useful for room operators.",
"pauseonleave-tooltip" : "Pause playback if you get disconnected or someone leaves from your room.",
"readyatstart-tooltip" : "Set yourself as 'ready' at start (otherwise you are set as 'not ready' until you change your readiness state)",
"forceguiprompt-tooltip" : "Configuration dialogue is not shown when opening a file with Syncplay.", # (Inverted)
"nostore-tooltip" : "Run Syncplay with the given configuration, but do not permanently store the changes.", # (Inverted)
"rewindondesync-tooltip" : "Jump back when needed to get back in sync. Disabling this option can result in major desyncs!",
"fastforwardondesync-tooltip" : "Jump forward when out of sync with room controller (or your pretend position if 'Never slow down or rewind others' enabled).",
"fastforwardondesync-tooltip" : "Jump forward when out of sync with room operator (or your pretend position if 'Never slow down or rewind others' enabled).",
"showosd-tooltip" : "Sends Syncplay messages to media player OSD.",
"showosdwarnings-tooltip" : "Show warnings if playing different file, alone in room.",
"showosdwarnings-tooltip" : "Show warnings if playing different file, alone in room, users not ready, etc.",
"showsameroomosd-tooltip" : "Show OSD notifications for events relating to room user is in.",
"shownoncontrollerosd-tooltip" : "Show OSD notifications for events relating to non-controllers who are in controlled rooms.",
"shownoncontrollerosd-tooltip" : "Show OSD notifications for events relating to non-operators who are in managed rooms.",
"showdifferentroomosd-tooltip" : "Show OSD notifications for events relating to room user is not in.",
"showslowdownosd-tooltip" :"Show notifications of slowing down / reverting on time difference.",
"showcontactinfo-tooltip" : "Show information box about contacting Syncplay developers in main Syncplay window.",
"showdurationnotification-tooltip" : "Useful for when a segment in a multi-part file is missing, but can result in false positives.",
"language-tooltip" : u"Language to be used by Syncplay.",
"help-tooltip" : "Opens the Syncplay.pl user guide.",
"reset-tooltip" : "Reset all settings to the default configuration.",
"togglepause-tooltip" : "Pause/unpause media.",
"play-tooltip" : "Unpause media.",
"pause-tooltip" : "Pause media.",
"undoseek-tooltip" : "Seek to where you were before the most recent seek.",
"joinroom-tooltip" : "Leave current room and joins specified room.",
"seektime-tooltip" : "Jump to specified time (in seconds / min:sec). Use +/- for relative seek.",
"seektime-msgbox-label" : "Jump to specified time (in seconds / min:sec). Use +/- for relative seek.",
# In-userlist notes (GUI)
"differentsize-note" : "Different size!",
@ -296,7 +327,8 @@ en = {
"server-port-argument" : 'server TCP port',
"server-password-argument" : 'server password',
"server-isolate-room-argument" : 'should rooms be isolated?',
"server-salt-argument" : "random string used to generate controlled room passwords",
"server-salt-argument" : "random string used to generate managed room passwords",
"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.",
@ -327,7 +359,7 @@ ru = {
"retrying-notification" : u"%s, следующая попытка через %d секунд(ы)...", # Seconds
"rewind-notification" : u"Перемотано из-за разницы во времени с <{}>", # User
"fastforward-notification" : "Fast-forwarded due to time difference with <{}>", # User # TODO: Translate into Russian
"fastforward-notification" : u"Fast-forwarded due to time difference with <{}>", # User # TODO: Translate into Russian
"slowdown-notification" : u"Воспроизведение замедлено из-за разницы во времени с <{}>", # User
"revert-notification" : u"Возвращаемся к нормальной скорости воспроизведения",
@ -343,19 +375,36 @@ ru = {
"playing-notification" : u"<{}> включил '{}' ({})", # User, file, duration
"playing-notification/room-addendum" : u" в комнате: '{}'", # Room
"identifying-as-controller-notification" : u"Identifying as room controller with password '{}'...", # TODO: Translate into Russian
"failed-to-identify-as-controller-notification" : u"<{}> failed to identify as a room controller.", # TODO: Translate into Russian
"authenticated-as-controller-notification" : u"<{}> authenticated as a room controller", # TODO: Translate into Russian
"not-all-ready" : u"Not ready: {}", # Usernames # TODO: Translate into Russian
"all-users-ready" : u"Everyone is ready ({} users)", #Number of ready users # TODO: Translate into Russian
"ready-to-unpause-notification" : u"You are now set as ready - unpause again to unpause", # TODO: Translate into Russian
"identifying-as-controller-notification" : u"Identifying as room operator with password '{}'...", # TODO: Translate into Russian
"failed-to-identify-as-controller-notification" : u"<{}> failed to identify as a room operator.", # TODO: Translate into Russian
"authenticated-as-controller-notification" : u"<{}> authenticated as a room operator", # TODO: Translate into Russian
"created-controlled-room-notification" : u"Created managed room '{}' with password '{}'. Please save this information for future reference!", # RoomName, controlPassword # TODO: Translate into Russian
"file-different-notification" : u"Вероятно, файл, который Вы смотрите, отличается от того, который смотрит <{}>.", # User
"file-differences-notification" : u"Ваш файл отличается: ",
"room-files-not-same" : u"Не все пользователи в этой комнате смотрят один и тот же файл.",
"file-differences-notification" : u"Ваш файл отличается: {}", # Differences
"room-file-differences" : u"File differences: {}", # File differences (filename, size, and/or duration)
"file-difference-filename" : u"name", # TODO: Translate into Russian
"file-difference-filesize" : u"size", # TODO: Translate into Russian
"file-difference-duration" : u"duration", # TODO: Translate into Russian
"alone-in-the-room" : u"В этой комнате кроме Вас никого нет.",
"different-filesize-notification" : u" (размер Вашего файла не совпадает с размером их файла!)",
"userlist-playing-notification" : u"{} is playing:", #Username # TODO: Translate into Russian (same as playing-notification?)
"file-played-by-notification" : u"Файл: {} просматривают:", # File
"no-file-played-notification" : u"{} is not playing a file", # Username # TODO: Translate into Russian
"notplaying-notification" : u"Люди, которые не смотрят ничего:",
"userlist-room-notification" : u"В комнате '{}':", # Room
"userlist-file-notification" : u"File", # TODO: Translate into Russian (Файл?)
"controller-userlist-userflag" : u"Operator", # TODO: Translate into Russian (this is to indicate a user is a controller in the ConsoleUI userlist)
"ready-userlist-userflag" : u"Ready", # TODO: Translate into Russian (this is to indicate a user is ready to watch in the ConsoleUI userlist)
"update-check-failed-notification" : u"Could not automatically check whether Syncplay {} is up to date. Want to visit http://syncplay.pl/ to manually check for updates?", #Syncplay version # TODO: Translate into Russian
"syncplay-uptodate-notification" : u"Syncplay is up to date", # TODO: Translate into Russian
"syncplay-updateavailable-notification" : u"A new version of Syncplay is available. Do you want to download it?", # TODO: Translate into Russian
"mplayer-file-required-notification" : u"Для использования Syncplay с mplayer необходимо передать файл в качестве параметра",
"mplayer-file-required-notification/example" : u"Пример использования: syncplay [options] [url|path/]filename",
@ -369,13 +418,15 @@ ru = {
"commandlist-notification/pause" : u"\tp - вкл./выкл. паузу",
"commandlist-notification/seek" : u"\t[s][+-]time - перемотать к заданному моменту времени, если не указан + или -, то время считается абсолютным (от начала файла) в секундах или мин:сек",
"commandlist-notification/help" : u"\th - помощь",
"commandlist-notification/create" : u"\tc [name] - create controlled room using name of current room", # TODO: Translate into Russian
"commandlist-notification/auth" : u"\ta [password] - authenticate as room controller with controller password", # TODO: Translate into Russian
"commandlist-notification/toggle" : u"\tt - toggles whether you are ready to watch or not", # TODO: Translate into Russian
"commandlist-notification/create" : u"\tc [name] - create managed room using name of current room", # TODO: Translate into Russian
"commandlist-notification/auth" : u"\ta [password] - authenticate as room operator with controller password", # TODO: Translate into Russian
"syncplay-version-notification" : u"Версия Syncplay: {}", # syncplay.version
"more-info-notification" : u"Больше информации на {}", # projectURL
"gui-data-cleared-notification" : u"Syncplay очистил путь и информацию о состоянии окна, использованного GUI.",
"language-changed-msgbox-label" : u"Language will be changed when you run Syncplay.",
"language-changed-msgbox-label" : u"Language will be changed when you run Syncplay.", # TODO: Translate into Russian
"promptforupdate-label" : u"Is it okay for Syncplay to automatically check for updates from time to time?", # TODO: Translate into Russian
"vlc-version-mismatch" : u"Внимание: Вы используете VLC устаревшей версии {}. К сожалению, Syncplay способен работать с VLC {} и выше.", # VLC version, VLC min version
"vlc-interface-version-mismatch" : u"Внимание: В используете модуль интерфейса Syncplay устаревшей версии {} для VLC. К сожалению, Syncplay способен работать с версией {} и выше.", # VLC interface version, VLC interface min version
@ -416,6 +467,9 @@ ru = {
"vlc-failed-versioncheck" : u"Данная версия VLC не поддерживается Syncplay. Пожалуйста, используйте VLC версии 2 или выше.",
"vlc-failed-other" : u"Во время загрузки скрипта интерфейса syncplay.lua в VLC произошла следующая ошибка: {}", # Syncplay Error
"invalid-seek-value" : u"Invalid seek value", # TODO: Translate into Russian
"invalid-offset-value" : u"Invalid offset value", # TODO: Translate into Russian
# Client arguments
"argument-description" : u'Решение для синхронного воспроизведения в VLC, MPlayer или MPC-HC через Интернет.',
"argument-epilog" : u'Если параметр не будет передан, то будет использоваться значение, указанное в _config.',
@ -461,10 +515,12 @@ ru = {
"privacy-dontsend-option" : u"не отпр.",
"filename-privacy-label" : u"Имя файла:",
"filesize-privacy-label" : u"Размер файла:",
"checkforupdatesautomatically-label" : u"Проверять обновления автоматически", # TODO: Confirm Russian translation
"slowondesync-label" : u"Замедлять при небольших рассинхронизациях (не поддерживаетя в MPC-HC)",
"rewindondesync-label" : u"Перемотка при больших рассинхронизациях (настоятельно рекомендуется)",
"dontslowdownwithme-label" : u"Никогда не замедлять или перематывать видео другим", # TODO: Update new wording into Russian (should state "Experimental" in brackets at the end)
"pauseonleave-label" : u"Приостанавливать, когда кто-то уходит (например, отключился)",
"readyatstart-label" : u"Set me as 'ready to watch' by default", # TODO: Translate into Russian
"fastforwardondesync-label" : u"Fast-forward if lagging behind (recommended)", # TODO: Translate into Russian
"forceguiprompt-label" : u"Не показывать больше этот диалог", # (Inverted)
"nostore-label" : u"Не сохранять текущую конфигурацию", # (Inverted)
@ -472,17 +528,19 @@ ru = {
"showosdwarnings-label" : u"Показывать предупреждения (напр., когда файлы не совпадают)",
"showsameroomosd-label" : u"Показывать события Вашей комнаты",
"shownoncontrollerosd-label" : "Include events from non-controllers in controlled rooms", # TODO: Translate into Russiann
"shownoncontrollerosd-label" : u"Include events from non-operators in managed rooms", # TODO: Translate into Russiann
"showdifferentroomosd-label" : u"Показывать события других комнат",
"showslowdownosd-label" : u"Показывать уведомления о замедлении/перемотке",
"showcontactinfo-label" : u"Отображать контактную информацию разработчиков",
"language-label" : u"Language", # TODO: Translate into Russian
"automatic-language" : "Automatic ({})", # Automatic language # TODO: Translate into Russian
"language-label" : u"Language:", # TODO: Translate into Russian
"automatic-language" : u"Default ({})", # Automatic language # TODO: Translate into Russian
"showdurationnotification-label" : u"Предупреждать о несовпадении продолжительности видео",
"basics-label" : u"Основное",
"misc-label" : u"Misc", # TODO: Translate into Russian
"core-behaviour-title" : "Core room behaviour", # TODO: Translate into Russian
"syncplay-internals-title" : u"Syncplay internals", # TODO: Translate into Russian
"sync-label" : u"Синхронизация",
"sync-lagging-title" : u"If others are lagging behind...", # TODO: Translate into Russian
"sync-other-title" : u"Other sync options", # TODO: Translate into Russian
"sync-otherslagging-title" : u"If others are lagging behind...", # TODO: Translate into Russian
"sync-youlaggging-title" : u"If you are lagging behind...", # TODO: Translate into Russian
"messages-label" : u"Сообщения",
"messages-osd-title" : u"On-screen Display settings", # TODO: Translate into Russian
"messages-other-title" : u"Other display settings", # TODO: Translate into Russian
@ -496,46 +554,56 @@ 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/new\">задавайте вопросы через GitHub</a>. Кроме того, заходите на <a href=\"http://syncplay.pl/\">http://syncplay.pl/</a> за инорфмацией, помощью и обновлениями!",
"joinroom-guibuttonlabel" : u"Зайти в комнату",
"seektime-guibuttonlabel" : u"Перемотать",
"undoseek-guibuttonlabel" : u"Отменить перемотку",
"togglepause-guibuttonlabel" : u"Вкл./выкл. паузу",
"play-guibuttonlabel" : u"Play",
"pause-guibuttonlabel" : u"Пауза",
"joinroom-menu-label" : u"Зайти в комнату",
"seektime-menu-label" : u"Перемотать",
"undoseek-menu-label" : u"Отменить перемотку",
"play-menu-label" : u"Play",
"pause-menu-label" : u"Пауза",
"playbackbuttons-menu-label" : u"Show playback buttons", # TODO: Translate into Russian
"autoplay-menu-label" : u"Show auto-play button", # TODO: Translate into Russian
"autoplay-guipushbuttonlabel" : u"Auto-play when everyone is ready", # TODO: Translate into Russian
"ready-guipushbuttonlabel" : u"I'm ready to watch!", # TODO: Translate into Russian
"roomuser-heading-label" : u"Комната / Пользователь",
"size-heading-label" : "Size", # TODO: Translate into Russian
"duration-heading-label" : "Length", # TODO: Translate into Russian
"filename-heading-label" : "Filename", # TODO: Translate into Russian
"size-heading-label" : u"Size", # TODO: Translate into Russian
"duration-heading-label" : u"Length", # TODO: Translate into Russian
"filename-heading-label" : u"Filename", # TODO: Translate into Russian
"notifications-heading-label" : u"Уведомления",
"userlist-heading-label" : u"Кто что смотрит",
"othercommands-heading-label" : u"Другие команды",
"room-heading-label" : u"Комната",
"seek-heading-label" : u"Перемотка",
"browseformedia-label" : u"Выбрать видеофайл",
"file-menu-label" : u"&Файл", # & precedes shortcut key
"openmedia-menu-label" : u"&Открыть видеофайл",
"openstreamurl-menu-label" : u"Open &media stream URL", # TODO: Translate into Russian
"exit-menu-label" : u"&Выход",
"advanced-menu-label" : u"&Дополнительно",
"window-menu-label" : u"&Window", # TODO: Translate into Russian
"setoffset-menu-label" : u"Установить &смещение",
"createcontrolledroom-menu-label" : u"&Create controlled room", # TODO: Translate into Russianv
"identifyascontroller-menu-label" : u"&Identify as room controller", # TODO: Translate into Russian
"createcontrolledroom-menu-label" : u"&Create managed room", # TODO: Translate into Russian
"identifyascontroller-menu-label" : u"&Identify as room operator", # TODO: Translate into Russian
"playback-menu-label" : u"&Playback", # TODO: Translate into Russian
"help-menu-label" : u"&Помощь",
"userguide-menu-label" : u"&Руководство Пользователя",
"update-menu-label" : u"Check for &update", # TODO: Translate into Russian
"setoffset-msgbox-label" : u"Установить смещение",
"offsetinfo-msgbox-label" : u"Смещение (см. как использовать на http://syncplay.pl/guide/):",
"createcontrolledroom-msgbox-label" : u"Create controlled room", # TODO: Translate into Russian
"controlledroominfo-msgbox-label" : u"Enter name of controlled room\r\n(see http://syncplay.pl/guide/ for usage instructions):", # TODO: Translate into Russian
"promptforstreamurl-msgbox-label" : u"Open media stream URL", # TODO: Translate into Russian
"promptforstreamurlinfo-msgbox-label" : u"Stream URL", # TODO: Translate into Russian
"identifyascontroller-msgbox-label" : u"Identify as Room Controller", # TODO: Translate into Russian
"createcontrolledroom-msgbox-label" : u"Create managed room", # TODO: Translate into Russian
"controlledroominfo-msgbox-label" : u"Enter name of managed room\r\n(see http://syncplay.pl/guide/ for usage instructions):", # TODO: Translate into Russian
"identifyascontroller-msgbox-label" : u"Identify as room operator", # TODO: Translate into Russian
"identifyinfo-msgbox-label" : u"Enter controller password for this room\r\n(see http://syncplay.pl/guide/ for usage instructions):", # TODO: Translate into Russian
"megabyte-suffix" : " MB", # Technically it is a mebibyte # TODO: Translate into Russian
"megabyte-suffix" : u" MB", # Technically it is a mebibyte # TODO: Translate into Russian
# Tooltips
@ -550,38 +618,35 @@ ru = {
"more-tooltip" : u"Показать дополнительные настройки.",
"slowdown-threshold-tooltip" : u"Отставание самого медленного клиента, необходимое для временного уменьшения скорости видео (по умолчанию: {} сек.).".format(constants.DEFAULT_SLOWDOWN_KICKIN_THRESHOLD),
"rewind-threshold-tooltip" : u"Отставание самого медленного клиента, необходимое для перемотки назад в целях синхронизации (по умолчанию: {} сек.).".format(constants.DEFAULT_REWIND_THRESHOLD),
"fastforward-threshold-tooltip" : u"Time behind room controller before seeking to get back in sync (default: {} secs).".format(constants.DEFAULT_FASTFORWARD_THRESHOLD), # TODO: Translate into Russian
"fastforward-threshold-tooltip" : u"Time behind room operator before seeking to get back in sync (default: {} secs).".format(constants.DEFAULT_FASTFORWARD_THRESHOLD), # TODO: Translate into Russian
"filename-privacy-tooltip" : u"Режим приватности для передачи имени воспроизводимого файла на сервер.",
"filesize-privacy-tooltip" : u"Режим приватности для передачи размера воспроизводимого файла на сервер.",
"privacy-sendraw-tooltip" : u"Отправляет эту информацию без шифрования. Рекомендуемая опция с наибольшей функциональностью.",
"privacy-sendhashed-tooltip" : u"Отправляет хэш-сумму этой информации, делая ее невидимой для других пользователей.",
"privacy-dontsend-tooltip" : u"Не отправлять эту информацию на сервер. Предоставляет наибольшую приватность.",
"checkforupdatesautomatically-tooltip" : u"Regularly check with the Syncplay website to see whether a new version of Syncplay is available.", # TODO: Translate into Russian
"slowondesync-tooltip" : u"Временно уменьшить скорость воспроизведения в целях синхронизации с другими зрителями. Не поддерживается в MPC-HC.",
"dontslowdownwithme-tooltip" : u"Ваши лаги не будут влиять на других зрителей.",
"pauseonleave-tooltip" : u"Приостановить воспроизведение, если Вы покинули комнату или кто-то из зрителей отключился от сервера.",
"readyatstart-tooltip" : u"Set yourself as 'ready' at start (otherwise you are set as 'not ready' until you change your readiness state)", # TODO: Translate into Russian
"forceguiprompt-tooltip" : u"Окно настройки не будет отображаться при открытии файла в Syncplay.", # (Inverted)
"nostore-tooltip" : u"Запустить Syncplay с данной конфигурацией, но не сохранять изменения навсегда.", # (Inverted)
"rewindondesync-tooltip" : u"Перематывать назад, когда это необходимо для синхронизации. Отключение этой опции может привести к большим рассинхронизациям!",
"fastforwardondesync-tooltip" : u"Jump forward when out of sync with room controller (or your pretend position if 'Never slow down or rewind others' enabled).", # TODO: Translate into Russian
"fastforwardondesync-tooltip" : u"Jump forward when out of sync with room operator (or your pretend position if 'Never slow down or rewind others' enabled).", # TODO: Translate into Russian
"showosd-tooltip" : u"Отправлять сообщения Syncplay в видеопроигрыватель и отображать их поверх видео (OSD - On Screen Display).",
"showosdwarnings-tooltip" : u"Показывать OSC-предупреждения, если проигрываются разные файлы или если Вы в комнате больше никого нет.",
"showsameroomosd-tooltip" : u"Показывать OSD-уведомления о событиях, относящихся к комнате, в которой Вы находитесь.",
"shownoncontrollerosd-tooltip" : u"Show OSD notifications for events relating to non-controllers who are in controllerd rooms.", # TODO: Translate into Russian
"shownoncontrollerosd-tooltip" : u"Show OSD notifications for events relating to non-operators who are in controllerd rooms.", # TODO: Translate into Russian
"showdifferentroomosd-tooltip" : u"Показывать OSD-уведомления о событиях, относящихся к любым другим комнатам.",
"showslowdownosd-tooltip" : u"Показывать уведомления о замедлении или перемотке в целях синхронизации.",
"showcontactinfo-tooltip" : u"Показывать информационных блок с контактной информацией разработчиков Syncplay на главном окне Syncplay.",
"showdurationnotification-tooltip" : u"Полезно, когда сегмент составного файла отсутствует. Возможны ложные срабатывания.",
"language-tooltip" : u"Language to be used by Syncplay.", # TODO: Translate into Russian
"help-tooltip" : u"Открыть Руководство Пользователя на Syncplay.pl.",
"reset-tooltip" : u"Сбрасывает все настройки Syncplay в начальное состояние.",
"togglepause-tooltip" : u"Приостановить/продолжить просмотр.",
"play-tooltip" : u"Продолжить просмотр.",
"pause-tooltip" : u"Приостановить просмотр.",
"undoseek-tooltip" : u"Перейти к тому месту, которое Вы просматривали до перемотки.",
"joinroom-tooltip" : u"Покинуть комнату и зайти в другую, указанную комнату.",
"seektime-tooltip" : u"Перемотать к определенному моменту времени (указывать в секундах или мин:сек). Используйте +/-, чтобы перемотать вперед/назад относительно настоящего момента.",
"seektime-msgbox-label" : u"Перемотать к определенному моменту времени (указывать в секундах или мин:сек). Используйте +/-, чтобы перемотать вперед/назад относительно настоящего момента.",
# In-userlist notes (GUI)
"differentsize-note" : u"Размер файла не совпадает!",
@ -604,7 +669,8 @@ ru = {
"server-port-argument" : u'номер TCP порта сервера',
"server-password-argument" : u'пароль к серверу',
"server-isolate-room-argument" : u'должны ли комнаты быть изолированными?',
"server-salt-argument" : u"random string used to generate controlled room passwords", # TODO: Translate into Russian (note: as you may already be aware, 'salt' means Соль (криптография))
"server-salt-argument" : u"random string used to generate managed room passwords", # TODO: Translate into Russian (note: as you may already be aware, 'salt' means Соль (криптография))
"server-disable-ready-argument" : u"disable readiness feature", # TODO: Translate into Russian
"server-motd-argument" : u"путь к файлу, из которого будет извлекаться MOTD-сообщение",
"server-messed-up-motd-unescaped-placeholders" : u"MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).",
"server-messed-up-motd-too-long" : u"MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).",
@ -652,19 +718,36 @@ de = {
"playing-notification" : u"<{}> spielt '{}' ({})", # User, file, duration
"playing-notification/room-addendum" : u" in Raum: '{}'", # Room
"identifying-as-controller-notification" : u"Identifiziere als Raumleiter mit Passwort '{}'...", # TODO: find a better translation to "room controller"
"not-all-ready" : u"Not ready: {}", # Usernames # TODO: Translate into German
"all-users-ready" : u"Everyone is ready ({} users)", #Number of ready users # TODO: Translate into German
"ready-to-unpause-notification" : u"You are now set as ready - unpause again to unpause", # TODO: Translate into German
"identifying-as-controller-notification" : u"Identifiziere als Raumleiter mit Passwort '{}'...", # TODO: find a better translation to "room operator"
"failed-to-identify-as-controller-notification" : u"<{}> konnte sich nicht als Raumleiter identifizieren.",
"authenticated-as-controller-notification" : u"<{}> authentifizierte sich als Raumleiter",
"created-controlled-room-notification" : u"Created managed room '{}' with password '{}'. Please save this information for future reference!", # RoomName, controlPassword # TODO: Translate into German
"file-different-notification" : u"Deine Datei scheint sich von <{}>s zu unterscheiden", # User
"file-differences-notification" : u"Deine Datei unterscheidet sich auf folgende Art: ",
"room-files-not-same" : u"Nicht alle Dateien im Raum sind gleich",
"file-differences-notification" : u"Deine Datei unterscheidet sich auf folgende Art: {}",
"room-file-differences" : u"File differences: {}", # File differences (filename, size, and/or duration) # TODO: Translate into German
"file-difference-filename" : u"name", # TODO: Translate into German
"file-difference-filesize" : u"size", # TODO: Translate into German
"file-difference-duration" : u"duration", # TODO: Translate into German
"alone-in-the-room": u"Du bist alleine im Raum",
"different-filesize-notification" : u" (ihre Dateigröße ist anders als deine!)",
"userlist-playing-notification" : u"{} is playing:", #Username # TODO: Translate into German (same as playing-notification?)
"file-played-by-notification" : u"Datei: {} wird gespielt von:", # File
"no-file-played-notification" : u"{} is not playing a file", # Username # TODO: Translate into German
"notplaying-notification" : u"Personen im Raum, die keine Dateien spielen:",
"userlist-room-notification" : u"In Raum '{}':", # Room
"userlist-file-notification" : u"File", # TODO: Translate into German (Datei?)
"controller-userlist-userflag" : u"Operator", # TODO: Translate into German (this is to indicate a user is a room operator in the ConsoleUI userlist)
"ready-userlist-userflag" : u"Ready", # TODO: Translate into German (this is to indicate a user is ready to watch in the ConsoleUI userlist)
"update-check-failed-notification" : u"Could not automatically check whether Syncplay {} is up to date. Want to visit http://syncplay.pl/ to manually check for updates?", #Syncplay version # TODO: Translate into German
"syncplay-uptodate-notification" : u"Syncplay is up to date", # TODO: Translate into German
"syncplay-updateavailable-notification" : u"A new version of Syncplay is available. Do you want to download it?", # TODO: Translate into German
"mplayer-file-required-notification" : u"Syncplay für mplayer benötigt eine Datei-Angabe beim Start",
"mplayer-file-required-notification/example" : u"Anwendungsbeispiel: syncplay [optionen] [url|pfad/]Dateiname",
@ -678,6 +761,7 @@ de = {
"commandlist-notification/pause" : u"\tp - Pausieren / weiter",
"commandlist-notification/seek" : u"\t[s][+-]Zeit - zu einer bestimmten Zeit spulen, ohne + oder - wird als absolute Zeit gewertet; Angabe in Sekunden oder Minuten:Sekunden",
"commandlist-notification/help" : u"\th - Diese Hilfe",
"commandlist-notification/toggle" : u"\tt - toggles whether you are ready to watch or not", # TODO: Translate into German
"commandlist-notification/create" : u"\tc [name] - erstelle zentral gesteuerten Raum mit dem aktuellen Raumnamen",
"commandlist-notification/auth" : u"\ta [password] - authentifiziere als Raumleiter mit Passwort",
"syncplay-version-notification" : u"Syncplay Version: {}", # syncplay.version
@ -685,6 +769,7 @@ de = {
"gui-data-cleared-notification" : u"Syncplay hat die Pfad und Fensterdaten der Syncplay-GUI zurückgesetzt.",
"language-changed-msgbox-label" : u"Die Sprache wird geändert, wenn du Syncplay neu startest.",
"promptforupdate-label" : u"Is it okay for Syncplay to automatically check for updates from time to time?", # TODO: Translate into German
"vlc-version-mismatch": u"Warnung: Du nutzt VLC Version {}, aber Syncplay wurde für VLC ab Version {} entwickelt.", # VLC version, VLC min version
"vlc-interface-version-mismatch": u"Warnung: Du nutzt Version {} des VLC-Syncplay Interface-Moduls, Syncplay benötigt aber mindestens Version {}.", # VLC interface version, VLC interface min version
@ -699,7 +784,7 @@ de = {
"server-timeout-error" : u"Timeout: Verbindung zum Server fehlgeschlagen",
"mpc-slave-error" : u"Kann MPC nicht im Slave-Modus starten!",
"mpc-version-insufficient-error" : u"MPC-Version nicht ausreichend, bitte nutze `mpc-hc` >= `{}`",
"mpv-version-error" : "Syncplay ist nicht kompatibel mit dieser Version von mpv. Bitte benutze eine andere Version (z.B. Git HEAD).",
"mpv-version-error" : u"Syncplay ist nicht kompatibel mit dieser Version von mpv. Bitte benutze eine andere Version (z.B. Git HEAD).",
"player-file-open-error" : u"Fehler beim Öffnen der Datei durch den Player",
"player-path-error" : u"Ungültiger Player-Pfad",
"hostname-empty-error" : u"Hostname darf nicht leer sein",
@ -725,6 +810,9 @@ de = {
"vlc-failed-versioncheck": u"Diese VLC-Version wird von Syncplay nicht unterstützt. Bitte nutze VLC 2.0",
"vlc-failed-other" : u"Beim Laden des syncplay.lua Interface-Skripts durch VLC trat folgender Fehler auf: {}", # Syncplay Error
"invalid-seek-value" : u"Invalid seek value", # TODO: Translate into German
"invalid-offset-value" : u"Invalid offset value", # TODO: Translate into German
# Client arguments
"argument-description" : u'Anwendung, um mehrere MPlayer, MPC-HC und VLC-Instanzen über das Internet zu synchronisieren.',
"argument-epilog" : u'Wenn keine Optionen angegeben sind, werden die _config-Werte verwendet',
@ -770,9 +858,11 @@ de = {
"privacy-dontsend-option" : u"Nicht senden",
"filename-privacy-label" : u"Dateiname:",
"filesize-privacy-label" : u"Dateigröße:",
"checkforupdatesautomatically-label" : u"Automatisch nach Updates suche", # TODO: Confirm German translation
"slowondesync-label" : u"Verlangsamen wenn nicht synchron (nicht unterstützt mit MPC-HC)",
"dontslowdownwithme-label" : u"Nie verlangsamen oder andere zurückspulen (Experimentell)",
"pauseonleave-label" : u"Pausieren wenn ein Benutzer austritt",
"readyatstart-label" : u"Set me as 'ready to watch' by default", # TODO: Translate into German
"forceguiprompt-label" : u"Diesen Dialog nicht mehr anzeigen",
"nostore-label" : u"Diese Konfiguration nicht speichern",
"showosd-label" : u"OSD-Nachrichten anzeigen",
@ -782,14 +872,16 @@ de = {
"shownoncontrollerosd-label" : u"Zeige Ereignisse von nicht geführten Räumen in geführten Räumen.",
"showdifferentroomosd-label" : u"Zeige Ereignisse in anderen Räumen",
"showslowdownosd-label" : u"Zeige Verlangsamungs/Zurücksetzungs-Benachrichtigung",
"showcontactinfo-label" : u"Zeige Kontaktinformationen",
"language-label" : u"Sprache",
"automatic-language" : "Automatisch ({})", # Automatic language
"language-label" : u"Sprache:",
"automatic-language" : u"Automatisch ({})", # Default language # TODO: Change to German translation of "default"
"showdurationnotification-label" : u"Zeige Warnung wegen unterschiedlicher Dauer",
"basics-label" : u"Basics",
"misc-label" : u"Misc", # TODO: Translate into German
"core-behaviour-title" : u"Core room behaviour", # TODO: Translate into German
"syncplay-internals-title" : u"Syncplay internals", # TODO: Translate into German
"sync-label" : u"Synchronisation",
"sync-lagging-title" : u"Wenn andere laggen...",
"sync-other-title" : u"Weitere Synchronisationsoptionen",
"sync-otherslagging-title" : u"Wenn andere laggen...",
"sync-youlaggging-title" : u"If you are lagging behind...", # TODO: Translate into German
"messages-label" : u"Nachrichten",
"messages-osd-title" : u"OSD-(OnScreenDisplay)-Einstellungen",
"messages-other-title" : u"Weitere Display-Einstellungen",
@ -803,12 +895,16 @@ 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/new\">ö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-guibuttonlabel" : u"Raum beitreten",
"seektime-guibuttonlabel" : u"Spule zu Zeit",
"undoseek-guibuttonlabel" : u"Rückgängig",
"togglepause-guibuttonlabel" : u"Pause umschalten",
"play-guibuttonlabel" : u"Wiedergabe",
"pause-guibuttonlabel" : u"Pause",
"joinroom-menu-label" : u"Raum beitreten",
"seektime-menu-label" : u"Spule zu Zeit",
"undoseek-menu-label" : u"Rückgängig",
"play-menu-label" : u"Wiedergabe",
"pause-menu-label" : u"Pause",
"playbackbuttons-menu-label" : u"Show playback buttons", # TODO: Translate into German
"autoplay-menu-label" : u"Show auto-play button", # TODO: Translate into German
"autoplay-guipushbuttonlabel" : u"Auto-play when everyone is ready", # TODO: Translate into German
"ready-guipushbuttonlabel" : u"I'm ready to watch!", # TODO: Translate into German
"roomuser-heading-label" : u"Raum / Benutzer",
"size-heading-label" : u"Größe",
@ -816,26 +912,30 @@ de = {
"filename-heading-label" : u"Dateiname",
"notifications-heading-label" : u"Benachrichtigungen",
"userlist-heading-label" : u"Liste der gespielten Dateien",
"othercommands-heading-label" : u"Andere Befehle",
"room-heading-label" : u"Raum",
"seek-heading-label" : u"Spulen",
"browseformedia-label" : u"Nach Mediendateien durchsuchen",
"file-menu-label" : u"&Datei", # & precedes shortcut key
"openmedia-menu-label" : u"&Mediendatei öffnen...",
"openstreamurl-menu-label" : u"Open &media stream URL", # TODO: Translate into German
"exit-menu-label" : u"&Beenden",
"advanced-menu-label" : u"&Erweitert",
"window-menu-label" : u"&Window", # TODO: Translate into German
"setoffset-menu-label" : u"&Offset einstellen",
"createcontrolledroom-menu-label" : u"&Zentral gesteuerten Raum erstellen",
"identifyascontroller-menu-label" : u"Als Raumleiter &identifizieren",
"playback-menu-label" : u"&Playback", # TODO: Translate into German
"help-menu-label" : u"&Hilfe",
"userguide-menu-label" : u"&Benutzerhandbuch öffnen",
"update-menu-label" : u"Check for &update", # TODO: Translate into German
"setoffset-msgbox-label" : u"Offset einstellen",
"offsetinfo-msgbox-label" : u"Offset (siehe http://syncplay.pl/guide/ für eine Anleitung [Englisch]):",
"promptforstreamurl-msgbox-label" : u"Open media stream URL", # TODO: Translate into German
"promptforstreamurlinfo-msgbox-label" : u"Stream URL", # TODO: Translate into German
"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]):",
@ -864,11 +964,13 @@ de = {
"privacy-sendraw-tooltip" : u"Die Information im Klartext übertragen. Dies ist die Standard-Einstellung mit der besten Funktionalität.",
"privacy-sendhashed-tooltip" : u"Die Informationen gehasht übertragen, um sie für andere Clients schwerer lesbar zu machen.",
"privacy-dontsend-tooltip" : u"Diese Information nicht übertragen. Dies garantiert den größtmöglichen Datanschutz.",
"checkforupdatesautomatically-tooltip" : u"Regularly check with the Syncplay website to see whether a new version of Syncplay is available.", # TODO: Translate into German
"slowondesync-tooltip" : u"Reduziert die Abspielgeschwindigkeit zeitweise, um die Synchronität zu den anderen Clients wiederherzustellen.",
"rewindondesync-label" : u"Zurückspulen bei großer Zeitdifferenz (empfohlen)",
"fastforwardondesync-label" : u"Vorspulen wenn das Video lagt (empfohlen)",
"dontslowdownwithme-tooltip" : u"Lässt andere nicht langsamer werden oder zurückspringen, wenn deine Wiedergabe hängt.",
"pauseonleave-tooltip" : u"Wiedergabe anhalten, wenn deine Verbindung verloren geht oder jemand den Raum verlässt.",
"readyatstart-tooltip" : u"Set yourself as 'ready' at start (otherwise you are set as 'not ready' until you change your readiness state)", #TODO: Translate into German
"forceguiprompt-tooltip" : u"Der Konfigurationsdialog wird nicht angezeigt, wenn eine Datei mit Syncplay geöffnet wird.",
"nostore-tooltip" : u"Syncplay mit den angegebenen Einstellungen starten, diese aber nicht dauerhaft speichern.",
"rewindondesync-tooltip" : u"Zum Wiederherstellen der Synchronität in der Zeit zurückspringen (empfohlen)",
@ -879,19 +981,14 @@ de = {
"shownoncontrollerosd-tooltip" : u"OSD-Meldungen bei Ereignissen verursacht durch nicht-Raumleiter in zentral gesteuerten Räumen anzeigen.",
"showdifferentroomosd-tooltip" : u"OSD-Meldungen zu anderen Räumen als dem aktuell betretenen anzeigen.",
"showslowdownosd-tooltip" : u"Meldungen bei Geschwindigkeitsänderung anzeigen.",
"showcontactinfo-tooltip" : u"Kontaktinformationen zu den Syncplay-Entwicklern im Hauptfenster anzeigen.",
"showdurationnotification-tooltip" : u"Nützlich, wenn z.B. ein Teil eines mehrteiligen Videos fehlt, kann jedoch auch fehlerhaft anschlagen.",
"language-tooltip" : u"Die verwendete Sprache von Syncplay",
"help-tooltip" : u"Öffnet Hilfe auf syncplay.pl [Englisch]",
"reset-tooltip" : u"Alle Einstellungen auf Standardwerte zurücksetzen.",
"togglepause-tooltip" : u"Datei starten/pausieren",
"play-tooltip" : u"Fortsetzen",
"pause-tooltip" : u"Pausieren",
"undoseek-tooltip" : u"Den letzten Spulvorgang rückgängig machen.",
"joinroom-tooltip" : u"Den aktuellen Raum verlassen und stattdessen den angegebenen betreten.",
"seektime-tooltip" : u"Springe zur angegebenen Zeit (in Sekunden oder min:sek). Verwende +/- zum relativen Springen.",
"seektime-msgbox-label" : u"Springe zur angegebenen Zeit (in Sekunden oder min:sek). Verwende +/- zum relativen Springen.",
# In-userlist notes (GUI)
"differentsize-note" : u"Verschiedene Größe!",
@ -906,7 +1003,7 @@ de = {
"welcome-server-notification" : u"Willkommen zum Syncplay-Server, v. {0}", # version
"client-connected-room-server-notification" : u"{0}({2}) hat den Raum '{1}' betreten", # username, host, room
"client-left-server-notification" : u"{0} hat den Server verlassen", # name
"no-salt-notification" : "WICHTIGER HINWEIS: Damit von dem Server generierte Passwörter für geführte Räume auch nach einem Serverneustart funktionieren, starte den Server mit dem folgenden Parameter: --salt {}", #Salt
"no-salt-notification" : u"WICHTIGER HINWEIS: Damit von dem Server generierte Passwörter für geführte Räume auch nach einem Serverneustart funktionieren, starte den Server mit dem folgenden Parameter: --salt {}", #Salt
# Server arguments
"server-argument-description" : u'Anwendung, um mehrere MPlayer, MPC-HC und VLC-Instanzen über das Internet zu synchronisieren. Server',
@ -915,6 +1012,7 @@ de = {
"server-password-argument" : u'Server Passwort',
"server-isolate-room-argument" : u'Sollen die Räume isoliert sein?',
"server-salt-argument" : u"zufällige Zeichenkette, die zur Erstellung von Passwörtern verwendet wird",
"server-disable-ready-argument" : u"disable readiness feature", # TODO: Translate into German
"server-motd-argument": u"Pfad zur Datei, von der die Nachricht des Tages geladen wird",
"server-messed-up-motd-unescaped-placeholders": u"Die Nachricht des Tages hat unmaskierte Platzhalter. Alle $-Zeichen sollten verdoppelt werden ($$).",
"server-messed-up-motd-too-long": u"Die Nachricht des Tages ist zu lang - Maximal {} Zeichen, aktuell {}.",
@ -973,9 +1071,6 @@ def isValidLanguage(language):
return messages.has_key(language)
def getMessage(type_, locale=None):
if constants.SHOW_BUTTON_LABELS == False:
if "-guibuttonlabel" in type_:
return ""
if constants.SHOW_TOOLTIPS == False:
if "-tooltip" in type_:
return ""

View File

@ -306,7 +306,9 @@ class MpcHcApi:
class MPCHCAPIPlayer(BasePlayer):
speedSupported = False
secondaryOSDSupported = False
customOpenDialog = False
osdMessageSeparator = "; "
def __init__(self, client):
from twisted.internet import reactor
@ -393,7 +395,7 @@ class MPCHCAPIPlayer(BasePlayer):
def openFile(self, filePath, resetPosition=False):
self._mpcApi.openFile(filePath)
def displayMessage(self, message, duration = (constants.OSD_DURATION*1000)):
def displayMessage(self, message, duration = (constants.OSD_DURATION*1000), secondaryOSD=False):
self._mpcApi.sendOsd(message, constants.MPC_OSD_POSITION, duration)
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)

View File

@ -10,6 +10,9 @@ import os
class MplayerPlayer(BasePlayer):
speedSupported = True
customOpenDialog = False
secondaryOSDSupported = False
osdMessageSeparator = "\\n"
RE_ANSWER = re.compile(constants.MPLAYER_ANSWER_REGEX)
POSITION_QUERY = 'time_pos'
OSD_QUERY = 'osd_show_text'
@ -83,7 +86,7 @@ class MplayerPlayer(BasePlayer):
def _getProperty(self, property_):
self._listener.sendLine("get_property {}".format(property_))
def displayMessage(self, message, duration=(constants.OSD_DURATION * 1000)):
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'))
def setSpeed(self, value):

View File

@ -14,6 +14,9 @@ from syncplay.messages import getMessage
class VlcPlayer(BasePlayer):
speedSupported = True
customOpenDialog = False
secondaryOSDSupported = True
osdMessageSeparator = "; "
RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX)
SLAVE_ARGS = constants.VLC_SLAVE_ARGS
if not sys.platform.startswith('darwin'):
@ -83,16 +86,19 @@ class VlcPlayer(BasePlayer):
self._positionAsk.clear()
self._pausedAsk.clear()
self._listener.sendLine(".")
if not self._filechanged:
if self._filename and not self._filechanged:
self._positionAsk.wait()
self._pausedAsk.wait()
self._client.updatePlayerStatus(self._paused, self._position)
else:
self._client.updatePlayerStatus(self._client.getGlobalPaused(), self._client.getGlobalPosition())
def displayMessage(self, message, duration=constants.OSD_DURATION * 1000):
def displayMessage(self, message, duration=constants.OSD_DURATION * 1000, secondaryOSD=False):
duration /= 1000
self._listener.sendLine('display-osd: {}, {}, {}'.format('top-right', duration, message.encode('utf8')))
if secondaryOSD == False:
self._listener.sendLine('display-osd: {}, {}, {}'.format('top-right', duration, message.encode('utf8')))
else:
self._listener.sendLine('display-secondary-osd: {}, {}, {}'.format('center', duration, message.encode('utf8')))
def setSpeed(self, value):
self._listener.sendLine("set-rate: {:.2n}".format(value))
@ -131,7 +137,10 @@ class VlcPlayer(BasePlayer):
self._listener.sendLine("get-filename")
def lineReceived(self, line):
self._client.ui.showDebugMessage("player >> {}".format(line))
try:
self._client.ui.showDebugMessage("player << {}".format(line))
except:
pass
match, name, value = self.RE_ANSWER.match(line), "", ""
if match:
name, value = match.group('command'), match.group('argument')
@ -340,7 +349,7 @@ class VlcPlayer(BasePlayer):
if self.connected:
try:
self.push(line + "\n")
self._client.ui.showDebugMessage("player >> {}".format(line))
self.__playerController._client.ui.showDebugMessage("player >> {}".format(line))
except:
pass
if line == "close-vlc":

View File

@ -136,6 +136,10 @@ class SyncClientProtocol(JSONCommandProtocol):
controlPassword = values['password']
roomName = values['roomName']
self._client.controlledRoomCreated(roomName, controlPassword)
elif command == "ready":
user, isReady = values["username"], values["isReady"]
manuallyInitiated = values["manuallyInitiated"] if values.has_key("manuallyInitiated") else True
self._client.setReady(user, isReady, manuallyInitiated)
def sendSet(self, setting):
self.sendMessage({"Set": setting})
@ -158,7 +162,8 @@ class SyncClientProtocol(JSONCommandProtocol):
userName = user[0]
file_ = user[1]['file'] if user[1]['file'] <> {} else None
isController = user[1]['controller'] if 'controller' in user[1] else False
self._client.userlist.addUser(userName, roomName, file_, noMessage=True, isController=isController)
isReady = user[1]['isReady'] if 'isReady' in user[1] else None
self._client.userlist.addUser(userName, roomName, file_, noMessage=True, isController=isController, isReady=isReady)
self._client.userlist.showUserList()
def sendList(self):
@ -234,6 +239,14 @@ class SyncClientProtocol(JSONCommandProtocol):
}
})
def setReady(self, isReady, manuallyInitiated=True):
self.sendSet({
"ready": {
"isReady": isReady,
"manuallyInitiated": manuallyInitiated
}
})
def handleError(self, error):
self.dropWithError(error["message"])
@ -342,6 +355,9 @@ class SyncServerProtocol(JSONCommandProtocol):
password = set_[1]["password"] if set_[1].has_key("password") else None
room = set_[1]["room"] if set_[1].has_key("room") else None
self._factory.authRoomController(self._watcher, password, room)
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)
def sendSet(self, setting):
self.sendMessage({"Set": setting})
@ -363,6 +379,16 @@ class SyncServerProtocol(JSONCommandProtocol):
}
})
def sendSetReady(self, username, isReady, manuallyInitiated=True):
self.sendSet({
"ready": {
"username": username,
"isReady": isReady,
"manuallyInitiated": manuallyInitiated
}
})
def sendUserSetting(self, username, room, file_, event):
room = {"name": room.getName()}
user = {username: {}}
@ -381,7 +407,8 @@ class SyncServerProtocol(JSONCommandProtocol):
userFile = {
"position": 0,
"file": watcher.getFile() if watcher.getFile() else {},
"controller": watcher.isController()
"controller": watcher.isController(),
"isReady": watcher.isReady()
}
userlist[room.getName()][watcher.getName()] = userFile

View File

@ -14,7 +14,7 @@ import argparse
from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion
class SyncFactory(Factory):
def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None):
def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False):
print getMessage("welcome-server-notification").format(syncplay.version)
if password:
password = hashlib.md5(password).hexdigest()
@ -24,6 +24,7 @@ class SyncFactory(Factory):
print getMessage("no-salt-notification").format(salt)
self._salt = salt
self._motdFilePath = motdFilePath
self.disableReady = disableReady
if not isolateRooms:
self._roomManager = RoomManager()
else:
@ -78,6 +79,7 @@ class SyncFactory(Factory):
def sendRoomSwitchMessage(self, watcher):
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, None)
self._roomManager.broadcast(watcher, l)
self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False))
def removeWatcher(self, watcher):
if watcher and watcher.getRoom():
@ -91,6 +93,7 @@ class SyncFactory(Factory):
def sendJoinMessage(self, watcher):
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"joined": True}) if w != watcher else None
self._roomManager.broadcast(watcher, l)
self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False))
def sendFileUpdate(self, watcher):
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), watcher.getFile(), None)
@ -125,6 +128,9 @@ class SyncFactory(Factory):
except ValueError:
self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(False, watcher.getName(), room._name))
def setReady(self, watcher, isReady, manuallyInitiated=True):
watcher.setReady(isReady)
self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), manuallyInitiated))
class RoomManager(object):
def __init__(self):
@ -299,6 +305,7 @@ class ControlledRoom(Room):
class Watcher(object):
def __init__(self, server, connector, name):
self._ready = None
self._server = server
self._connector = connector
self._name = name
@ -322,6 +329,14 @@ class Watcher(object):
self._resetStateTimer()
self._askForStateUpdate(True, True)
def setReady(self, ready):
self._ready = ready
def isReady(self):
if self._server.disableReady:
return None
return self._ready
def getRoom(self):
return self._room
@ -352,6 +367,9 @@ class Watcher(object):
def sendControlledRoomAuthStatus(self, success, username, room):
self._connector.sendControlledRoomAuthStatus(success, username, room)
def sendSetReady(self, username, isReady, manuallyInitiated=True):
self._connector.sendSetReady(username, isReady, manuallyInitiated)
def __lt__(self, b):
if self.getPosition() is None or self._file is None:
return False
@ -422,5 +440,6 @@ class ConfigurationGetter(object):
self._argparser.add_argument('--port', metavar='port', type=str, nargs='?', help=getMessage("server-port-argument"))
self._argparser.add_argument('--password', metavar='password', type=str, nargs='?', help=getMessage("server-password-argument"))
self._argparser.add_argument('--isolate-rooms', action='store_true', help=getMessage("server-isolate-room-argument"))
self._argparser.add_argument('--disable-ready', action='store_true', help=getMessage("server-disable-ready-argument"))
self._argparser.add_argument('--salt', metavar='salt', type=str, nargs='?', help=getMessage("server-salt-argument"))
self._argparser.add_argument('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-argument"))

View File

@ -44,8 +44,11 @@ class ConfigurationGetter(object):
"filenamePrivacyMode": constants.PRIVACY_SENDRAW_MODE,
"filesizePrivacyMode": constants.PRIVACY_SENDRAW_MODE,
"pauseOnLeave": False,
"readyAtStart": False,
"clearGUIData": False,
"language" : "",
"checkForUpdatesAutomatically" : None,
"lastCheckedForUpdates" : "",
"resetConfig" : False,
"showOSD" : True,
"showOSDWarnings" : True,
@ -77,6 +80,7 @@ class ConfigurationGetter(object):
"noStore",
"dontSlowDownWithMe",
"pauseOnLeave",
"readyAtStart",
"clearGUIData",
"rewindOnDesync",
"slowOnDesync",
@ -90,9 +94,11 @@ class ConfigurationGetter(object):
"showDifferentRoomOSD",
"showSameRoomOSD",
"showNonControllerOSD",
"showContactInfo" ,
"showDurationNotification"
]
self._tristate = [
"checkForUpdatesAutomatically"
]
self._numeric = [
"slowdownThreshold",
@ -102,9 +108,9 @@ class ConfigurationGetter(object):
self._iniStructure = {
"server_data": ["host", "port", "password"],
"client_settings": ["name", "room", "playerPath", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "pauseOnLeave"],
"gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showContactInfo" , "showDurationNotification"],
"general": ["language"]
"client_settings": ["name", "room", "playerPath", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "pauseOnLeave", "readyAtStart"],
"gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showDurationNotification"],
"general": ["language", "checkForUpdatesAutomatically", "lastCheckedForUpdates"]
}
self._playerFactory = PlayerFactory()
@ -112,8 +118,10 @@ class ConfigurationGetter(object):
def _validateArguments(self):
if self._config['resetConfig']:
language = self._config['language']
checkForUpdatesAutomatically = self._config['checkForUpdatesAutomatically']
self._config = self._defaultConfig
self._config['language'] = language
self._config['checkForUpdatesAutomatically'] = checkForUpdatesAutomatically
raise InvalidConfigValue("*"+getMessage("config-cleared-notification"))
if not isValidLanguage(self._config['language']):
@ -137,6 +145,14 @@ class ConfigurationGetter(object):
elif self._config[key] == "False":
self._config[key] = False
for key in self._tristate:
if self._config[key] == "True":
self._config[key] = True
elif self._config[key] == "False":
self._config[key] = False
elif self._config[key] == "None":
self._config[key] = None
for key in self._numeric:
self._config[key] = float(self._config[key])

View File

@ -1,8 +1,8 @@
from PySide import QtCore, QtGui
from PySide.QtCore import QSettings, Qt, QCoreApplication
from PySide.QtCore import QSettings, Qt, QCoreApplication, QUrl
from PySide.QtGui import QApplication, QLineEdit, QCursor, QLabel, QCheckBox, QDesktopServices, QIcon, QImage, QButtonGroup, QRadioButton, QDoubleSpinBox
from syncplay.players.playerFactory import PlayerFactory
from datetime import datetime
import os
import sys
from syncplay.messages import getMessage, getLanguages, setLanguage, getInitialLanguage
@ -35,6 +35,15 @@ class ConfigDialog(QtGui.QDialog):
pressedclosebutton = False
moreToggling = False
def automaticUpdatePromptCheck(self):
if self.automaticupdatesCheckbox.checkState() == Qt.PartiallyChecked and not self.nostoreCheckbox.isChecked():
reply = QtGui.QMessageBox.question(self, "Syncplay",
getMessage("promptforupdate-label"), QtGui.QMessageBox.StandardButton.Yes | QtGui.QMessageBox.StandardButton.No)
if reply == QtGui.QMessageBox.Yes:
self.automaticupdatesCheckbox.setChecked(True)
else:
self.automaticupdatesCheckbox.setChecked(False)
def moreToggled(self):
if self.moreToggling == False:
self.moreToggling = True
@ -43,7 +52,6 @@ class ConfigDialog(QtGui.QDialog):
self.tabListFrame.show()
self.resetButton.show()
self.nostoreCheckbox.show()
self.alwaysshowCheckbox.show()
self.saveMoreState(True)
self.tabListWidget.setCurrentRow(0)
self.ensureTabListIsVisible()
@ -51,7 +59,6 @@ class ConfigDialog(QtGui.QDialog):
self.tabListFrame.hide()
self.resetButton.hide()
self.nostoreCheckbox.hide()
self.alwaysshowCheckbox.hide()
self.saveMoreState(False)
self.stackedLayout.setCurrentIndex(0)
@ -68,7 +75,7 @@ class ConfigDialog(QtGui.QDialog):
self.runButton.setText(getMessage("storeandrun-label"))
def openHelp(self):
self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/client/")
self.QtGui.QDesktopServices.openUrl(QUrl("http://syncplay.pl/guide/client/"))
def _isURL(self, path):
if path is None:
@ -170,6 +177,17 @@ class ConfigDialog(QtGui.QDialog):
if fileName:
self.executablepathCombobox.setEditText(os.path.normpath(fileName))
def loadLastUpdateCheckDate(self):
settings = QSettings("Syncplay", "Interface")
settings.beginGroup("Update")
self.lastCheckedForUpdates = settings.value("lastChecked", None)
if self.lastCheckedForUpdates:
if self.config["lastCheckedForUpdates"] is not None and self.config["lastCheckedForUpdates"] is not "":
if self.lastCheckedForUpdates > datetime.strptime(self.config["lastCheckedForUpdates"], "%Y-%m-%d %H:%M:%S.%f"):
self.config["lastCheckedForUpdates"] = str(self.lastCheckedForUpdates)
else:
self.config["lastCheckedForUpdates"] = str(self.lastCheckedForUpdates)
def loadMediaBrowseSettings(self):
settings = QSettings("Syncplay", "MediaBrowseDialog")
settings.beginGroup("MediaBrowseDialog")
@ -218,6 +236,9 @@ class ConfigDialog(QtGui.QDialog):
self.saveMediaBrowseSettings()
def _saveDataAndLeave(self):
self.automaticUpdatePromptCheck()
self.loadLastUpdateCheckDate()
self.processWidget(self, lambda w: self.saveValues(w))
if self.hostTextbox.text():
self.config['host'] = self.hostTextbox.text() if ":" in self.hostTextbox.text() else self.hostTextbox.text() + ":" + unicode(constants.DEFAULT_PORT)
@ -293,7 +314,12 @@ class ConfigDialog(QtGui.QDialog):
inverted = True
else:
inverted = False
widget.setChecked(self.config[valueName] != inverted)
if self.config[valueName] is None:
widget.setTristate(True)
widget.setCheckState(Qt.PartiallyChecked)
widget.stateChanged.connect(lambda: widget.setTristate(False))
else:
widget.setChecked(self.config[valueName] != inverted)
elif isinstance(widget, QRadioButton):
radioName, radioValue = valueName.split(constants.CONFIG_NAME_MARKER)[1].split(constants.CONFIG_VALUE_MARKER)
if self.config[radioName] == radioValue:
@ -307,12 +333,15 @@ class ConfigDialog(QtGui.QDialog):
return
if isinstance(widget, QCheckBox) and widget.objectName():
if valueName[:1] == constants.INVERTED_STATE_MARKER:
valueName = valueName[1:]
inverted = True
if widget.checkState() == Qt.PartiallyChecked:
self.config[valueName] = None
else:
inverted = False
self.config[valueName] = widget.isChecked() != inverted
if valueName[:1] == constants.INVERTED_STATE_MARKER:
valueName = valueName[1:]
inverted = True
else:
inverted = False
self.config[valueName] = widget.isChecked() != inverted
elif isinstance(widget, QRadioButton):
radioName, radioValue = valueName.split(constants.CONFIG_NAME_MARKER)[1].split(constants.CONFIG_VALUE_MARKER)
if widget.isChecked():
@ -434,18 +463,104 @@ class ConfigDialog(QtGui.QDialog):
self.connectionSettingsGroup.setMaximumHeight(self.connectionSettingsGroup.minimumSizeHint().height())
self.basicOptionsLayout.setAlignment(Qt.AlignTop)
self.basicOptionsLayout.addWidget(self.connectionSettingsGroup)
self.basicOptionsLayout.addSpacing(17)
self.basicOptionsLayout.addSpacing(5)
self.mediaplayerSettingsGroup.setMaximumHeight(self.mediaplayerSettingsGroup.minimumSizeHint().height())
self.basicOptionsLayout.addWidget(self.mediaplayerSettingsGroup)
self.basicOptionsFrame.setLayout(self.basicOptionsLayout)
self.stackedLayout.addWidget(self.basicOptionsFrame)
def addMiscTab(self):
self.miscFrame = QtGui.QFrame()
self.miscLayout = QtGui.QVBoxLayout()
self.miscFrame.setLayout(self.miscLayout)
self.coreSettingsGroup = QtGui.QGroupBox(getMessage("core-behaviour-title"))
self.coreSettingsLayout = QtGui.QVBoxLayout()
self.coreSettingsGroup.setLayout(self.coreSettingsLayout)
self.pauseonleaveCheckbox = QCheckBox(getMessage("pauseonleave-label"))
self.pauseonleaveCheckbox.setObjectName("pauseOnLeave")
self.coreSettingsLayout.addWidget(self.pauseonleaveCheckbox)
self.readyatstartCheckbox = QCheckBox(getMessage("readyatstart-label"))
self.readyatstartCheckbox.setObjectName("readyAtStart")
self.coreSettingsLayout.addWidget(self.readyatstartCheckbox)
self.internalSettingsGroup = QtGui.QGroupBox(getMessage("syncplay-internals-title"))
self.internalSettingsLayout = QtGui.QVBoxLayout()
self.internalSettingsGroup.setLayout(self.internalSettingsLayout)
self.alwaysshowCheckbox = QCheckBox(getMessage("forceguiprompt-label"))
self.alwaysshowCheckbox.setObjectName(constants.INVERTED_STATE_MARKER + "forceGuiPrompt")
self.internalSettingsLayout.addWidget(self.alwaysshowCheckbox)
self.automaticupdatesCheckbox = QCheckBox(getMessage("checkforupdatesautomatically-label"))
self.automaticupdatesCheckbox.setObjectName("checkForUpdatesAutomatically")
self.internalSettingsLayout.addWidget(self.automaticupdatesCheckbox)
### Privacy:
self.privacySettingsGroup = QtGui.QGroupBox(getMessage("privacy-title"))
self.privacySettingsLayout = QtGui.QVBoxLayout()
self.privacySettingsFrame = QtGui.QFrame()
self.privacyFrame = QtGui.QFrame()
self.privacyLayout = QtGui.QGridLayout()
self.filenameprivacyLabel = QLabel(getMessage("filename-privacy-label"), self)
self.filenameprivacyButtonGroup = QButtonGroup()
self.filenameprivacySendRawOption = QRadioButton(getMessage("privacy-sendraw-option"))
self.filenameprivacySendHashedOption = QRadioButton(getMessage("privacy-sendhashed-option"))
self.filenameprivacyDontSendOption = QRadioButton(getMessage("privacy-dontsend-option"))
self.filenameprivacyButtonGroup.addButton(self.filenameprivacySendRawOption)
self.filenameprivacyButtonGroup.addButton(self.filenameprivacySendHashedOption)
self.filenameprivacyButtonGroup.addButton(self.filenameprivacyDontSendOption)
self.filesizeprivacyLabel = QLabel(getMessage("filesize-privacy-label"), self)
self.filesizeprivacyButtonGroup = QButtonGroup()
self.filesizeprivacySendRawOption = QRadioButton(getMessage("privacy-sendraw-option"))
self.filesizeprivacySendHashedOption = QRadioButton(getMessage("privacy-sendhashed-option"))
self.filesizeprivacyDontSendOption = QRadioButton(getMessage("privacy-dontsend-option"))
self.filesizeprivacyButtonGroup.addButton(self.filesizeprivacySendRawOption)
self.filesizeprivacyButtonGroup.addButton(self.filesizeprivacySendHashedOption)
self.filesizeprivacyButtonGroup.addButton(self.filesizeprivacyDontSendOption)
self.filenameprivacyLabel.setObjectName("filename-privacy")
self.filenameprivacySendRawOption.setObjectName("privacy-sendraw" + constants.CONFIG_NAME_MARKER + "filenamePrivacyMode" + constants.CONFIG_VALUE_MARKER + constants.PRIVACY_SENDRAW_MODE)
self.filenameprivacySendHashedOption.setObjectName("privacy-sendhashed" + constants.CONFIG_NAME_MARKER + "filenamePrivacyMode" + constants.CONFIG_VALUE_MARKER + constants.PRIVACY_SENDHASHED_MODE)
self.filenameprivacyDontSendOption.setObjectName("privacy-dontsend" + constants.CONFIG_NAME_MARKER + "filenamePrivacyMode" + constants.CONFIG_VALUE_MARKER + constants.PRIVACY_DONTSEND_MODE)
self.filesizeprivacyLabel.setObjectName("filesize-privacy")
self.filesizeprivacySendRawOption.setObjectName("privacy-sendraw" + constants.CONFIG_NAME_MARKER + "filesizePrivacyMode" + constants.CONFIG_VALUE_MARKER + constants.PRIVACY_SENDRAW_MODE)
self.filesizeprivacySendHashedOption.setObjectName("privacy-sendhashed" + constants.CONFIG_NAME_MARKER + "filesizePrivacyMode" + constants.CONFIG_VALUE_MARKER + constants.PRIVACY_SENDHASHED_MODE)
self.filesizeprivacyDontSendOption.setObjectName("privacy-dontsend" + constants.CONFIG_NAME_MARKER + "filesizePrivacyMode" + constants.CONFIG_VALUE_MARKER + constants.PRIVACY_DONTSEND_MODE)
self.privacyLayout.addWidget(self.filenameprivacyLabel, 1, 0)
self.privacyLayout.addWidget(self.filenameprivacySendRawOption, 1, 1, Qt.AlignLeft)
self.privacyLayout.addWidget(self.filenameprivacySendHashedOption, 1, 2, Qt.AlignLeft)
self.privacyLayout.addWidget(self.filenameprivacyDontSendOption, 1, 3, Qt.AlignLeft)
self.privacyLayout.addWidget(self.filesizeprivacyLabel, 2, 0)
self.privacyLayout.addWidget(self.filesizeprivacySendRawOption, 2, 1, Qt.AlignLeft)
self.privacyLayout.addWidget(self.filesizeprivacySendHashedOption, 2, 2, Qt.AlignLeft)
self.privacyLayout.addWidget(self.filesizeprivacyDontSendOption, 2, 3, Qt.AlignLeft)
self.privacyFrame.setLayout(self.privacyLayout)
self.privacySettingsGroup.setLayout(self.privacyLayout)
self.privacySettingsGroup.setMaximumHeight(self.privacySettingsGroup.minimumSizeHint().height())
self.privacySettingsLayout.addWidget(self.privacySettingsGroup)
self.privacySettingsLayout.setAlignment(Qt.AlignTop)
self.privacyFrame.setLayout(self.privacySettingsLayout)
self.miscLayout.addWidget(self.coreSettingsGroup)
self.miscLayout.addWidget(self.internalSettingsGroup)
self.miscLayout.addWidget(self.privacySettingsGroup)
self.miscLayout.setAlignment(Qt.AlignTop)
self.stackedLayout.addWidget(self.miscFrame)
def addSyncTab(self):
self.syncSettingsFrame = QtGui.QFrame()
self.syncSettingsLayout = QtGui.QVBoxLayout()
self.desyncSettingsGroup = QtGui.QGroupBox(getMessage("sync-lagging-title"))
self.desyncSettingsGroup = QtGui.QGroupBox(getMessage("sync-otherslagging-title"))
self.desyncOptionsFrame = QtGui.QFrame()
self.desyncSettingsOptionsLayout = QtGui.QHBoxLayout()
config = self.config
@ -534,34 +649,31 @@ class ConfigDialog(QtGui.QDialog):
self.desyncSettingsLayout.setAlignment(Qt.AlignLeft)
self.desyncSettingsGroup.setLayout(self.desyncSettingsLayout)
self.desyncSettingsOptionsLayout.addWidget(self.desyncFrame)
self.syncSettingsLayout.addWidget(self.desyncSettingsGroup)
self.desyncFrame.setLayout(self.syncSettingsLayout)
self.othersyncSettingsGroup = QtGui.QGroupBox(getMessage("sync-other-title"))
self.othersyncSettingsGroup = QtGui.QGroupBox(getMessage("sync-youlaggging-title"))
self.othersyncOptionsFrame = QtGui.QFrame()
self.othersyncSettingsLayout = QtGui.QGridLayout()
self.pauseonleaveCheckbox = QCheckBox(getMessage("pauseonleave-label"))
self.othersyncSettingsLayout.addWidget(self.pauseonleaveCheckbox, 1, 0, 1, 2, Qt.AlignLeft)
self.pauseonleaveCheckbox.setObjectName("pauseOnLeave")
self.dontslowwithmeCheckbox = QCheckBox(getMessage("dontslowdownwithme-label"))
self.dontslowwithmeCheckbox.setObjectName("dontSlowDownWithMe")
self.othersyncSettingsLayout.addWidget(self.dontslowwithmeCheckbox, 2, 0, 1, 2, Qt.AlignLeft)
self.othersyncSettingsLayout.addWidget(self.dontslowwithmeCheckbox, 3, 0, 1, 2, Qt.AlignLeft)
self.fastforwardThresholdLabel.setObjectName("fastforward-threshold")
self.fastforwardThresholdSpinbox.setObjectName("fastforward-threshold")
self.othersyncSettingsLayout.addWidget(self.fastforwardCheckbox, 3, 0,1,2, Qt.AlignLeft)
self.othersyncSettingsLayout.addWidget(self.fastforwardThresholdLabel, 4, 0, 1, 1, Qt.AlignLeft)
self.othersyncSettingsLayout.addWidget(self.fastforwardThresholdSpinbox, 4, 1, Qt.AlignLeft)
self.othersyncSettingsLayout.setAlignment(Qt.AlignLeft)
self.othersyncSettingsLayout.addWidget(self.fastforwardCheckbox, 4, 0,1,2, Qt.AlignLeft)
self.othersyncSettingsLayout.addWidget(self.fastforwardThresholdLabel, 5, 0, 1, 1, Qt.AlignLeft)
self.othersyncSettingsLayout.addWidget(self.fastforwardThresholdSpinbox, 5, 1, Qt.AlignLeft)
self.subitems['fastforwardOnDesync'] = ["fastforward-threshold"]
self.othersyncSettingsGroup.setLayout(self.othersyncSettingsLayout)
self.othersyncSettingsGroup.setMaximumHeight(self.othersyncSettingsGroup.minimumSizeHint().height())
self.syncSettingsLayout.addWidget(self.othersyncSettingsGroup)
self.syncSettingsLayout.addWidget(self.desyncSettingsGroup)
self.syncSettingsFrame.setLayout(self.syncSettingsLayout)
self.desyncSettingsGroup.setMaximumHeight(self.desyncSettingsGroup.minimumSizeHint().height())
self.syncSettingsLayout.setAlignment(Qt.AlignTop)
@ -570,6 +682,7 @@ class ConfigDialog(QtGui.QDialog):
def addMessageTab(self):
self.messageFrame = QtGui.QFrame()
self.messageLayout = QtGui.QVBoxLayout()
self.messageLayout.setAlignment(Qt.AlignTop)
# OSD
self.osdSettingsGroup = QtGui.QGroupBox(getMessage("messages-osd-title"))
@ -615,18 +728,20 @@ class ConfigDialog(QtGui.QDialog):
# Other display
self.displaySettingsGroup = QtGui.QGroupBox(getMessage("messages-other-title"))
self.displaySettingsLayout = QtGui.QGridLayout()
self.displaySettingsLayout.setAlignment(Qt.AlignTop)
self.displaySettingsLayout = QtGui.QVBoxLayout()
self.displaySettingsLayout.setAlignment(Qt.AlignTop & Qt.AlignLeft)
self.displaySettingsFrame = QtGui.QFrame()
self.showDurationNotificationCheckbox = QCheckBox(getMessage("showdurationnotification-label"))
self.showDurationNotificationCheckbox.setObjectName("showDurationNotification")
self.displaySettingsLayout.addWidget(self.showDurationNotificationCheckbox, 0, 0, 1, 2)
self.showcontactinfoCheckbox = QCheckBox(getMessage("showcontactinfo-label"))
self.showcontactinfoCheckbox.setObjectName("showContactInfo")
self.displaySettingsLayout.addWidget(self.showcontactinfoCheckbox, 1, 0, 1, 2)
self.displaySettingsLayout.addWidget(self.showDurationNotificationCheckbox)
self.languageFrame = QtGui.QFrame()
self.languageLayout = QtGui.QHBoxLayout()
self.languageLayout.setContentsMargins(0, 0, 0, 0)
self.languageFrame.setLayout(self.languageLayout)
self.languageFrame.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)
self.languageLayout.setAlignment(Qt.AlignTop & Qt.AlignLeft)
self.languageLabel = QLabel(getMessage("language-label"), self)
self.languageCombobox = QtGui.QComboBox(self)
self.languageCombobox.addItem(getMessage("automatic-language").format(getMessage("LANGUAGE", getInitialLanguage())))
@ -637,73 +752,23 @@ class ConfigDialog(QtGui.QDialog):
if lang == self.config['language']:
self.languageCombobox.setCurrentIndex(self.languageCombobox.count()-1)
self.languageCombobox.currentIndexChanged.connect(self.languageChanged)
self.displaySettingsLayout.addWidget(self.languageLabel, 2, 0, 1, 1)
self.displaySettingsLayout.addWidget(self.languageCombobox, 2, 1, 1, 1)
self.languageLayout.addWidget(self.languageLabel, 1, 0)
self.languageLayout.addWidget(self.languageCombobox, 1, 1)
self.displaySettingsLayout.addWidget(self.languageFrame)
self.languageLabel.setObjectName("language")
self.languageCombobox.setObjectName("language")
self.languageFrame.setMaximumWidth(self.languageFrame.minimumSizeHint().width())
self.displaySettingsGroup.setLayout(self.displaySettingsLayout)
self.displaySettingsGroup.setMaximumHeight(self.displaySettingsGroup.minimumSizeHint().height())
self.displaySettingsLayout.setAlignment(Qt.AlignTop)
self.displaySettingsLayout.setAlignment(Qt.AlignTop & Qt.AlignLeft)
self.messageLayout.addWidget(self.displaySettingsGroup)
# messageFrame
self.messageFrame.setLayout(self.messageLayout)
self.stackedLayout.addWidget(self.messageFrame)
def addPrivacyTab(self):
self.privacySettingsGroup = QtGui.QGroupBox(getMessage("privacy-title"))
self.privacySettingsLayout = QtGui.QVBoxLayout()
self.privacySettingsFrame = QtGui.QFrame()
self.privacyFrame = QtGui.QFrame()
self.privacyLayout = QtGui.QGridLayout()
self.filenameprivacyLabel = QLabel(getMessage("filename-privacy-label"), self)
self.filenameprivacyButtonGroup = QButtonGroup()
self.filenameprivacySendRawOption = QRadioButton(getMessage("privacy-sendraw-option"))
self.filenameprivacySendHashedOption = QRadioButton(getMessage("privacy-sendhashed-option"))
self.filenameprivacyDontSendOption = QRadioButton(getMessage("privacy-dontsend-option"))
self.filenameprivacyButtonGroup.addButton(self.filenameprivacySendRawOption)
self.filenameprivacyButtonGroup.addButton(self.filenameprivacySendHashedOption)
self.filenameprivacyButtonGroup.addButton(self.filenameprivacyDontSendOption)
self.filesizeprivacyLabel = QLabel(getMessage("filesize-privacy-label"), self)
self.filesizeprivacyButtonGroup = QButtonGroup()
self.filesizeprivacySendRawOption = QRadioButton(getMessage("privacy-sendraw-option"))
self.filesizeprivacySendHashedOption = QRadioButton(getMessage("privacy-sendhashed-option"))
self.filesizeprivacyDontSendOption = QRadioButton(getMessage("privacy-dontsend-option"))
self.filesizeprivacyButtonGroup.addButton(self.filesizeprivacySendRawOption)
self.filesizeprivacyButtonGroup.addButton(self.filesizeprivacySendHashedOption)
self.filesizeprivacyButtonGroup.addButton(self.filesizeprivacyDontSendOption)
self.filenameprivacyLabel.setObjectName("filename-privacy")
self.filenameprivacySendRawOption.setObjectName("privacy-sendraw" + constants.CONFIG_NAME_MARKER + "filenamePrivacyMode" + constants.CONFIG_VALUE_MARKER + constants.PRIVACY_SENDRAW_MODE)
self.filenameprivacySendHashedOption.setObjectName("privacy-sendhashed" + constants.CONFIG_NAME_MARKER + "filenamePrivacyMode" + constants.CONFIG_VALUE_MARKER + constants.PRIVACY_SENDHASHED_MODE)
self.filenameprivacyDontSendOption.setObjectName("privacy-dontsend" + constants.CONFIG_NAME_MARKER + "filenamePrivacyMode" + constants.CONFIG_VALUE_MARKER + constants.PRIVACY_DONTSEND_MODE)
self.filesizeprivacyLabel.setObjectName("filesize-privacy")
self.filesizeprivacySendRawOption.setObjectName("privacy-sendraw" + constants.CONFIG_NAME_MARKER + "filesizePrivacyMode" + constants.CONFIG_VALUE_MARKER + constants.PRIVACY_SENDRAW_MODE)
self.filesizeprivacySendHashedOption.setObjectName("privacy-sendhashed" + constants.CONFIG_NAME_MARKER + "filesizePrivacyMode" + constants.CONFIG_VALUE_MARKER + constants.PRIVACY_SENDHASHED_MODE)
self.filesizeprivacyDontSendOption.setObjectName("privacy-dontsend" + constants.CONFIG_NAME_MARKER + "filesizePrivacyMode" + constants.CONFIG_VALUE_MARKER + constants.PRIVACY_DONTSEND_MODE)
self.privacyLayout.addWidget(self.filenameprivacyLabel, 1, 0)
self.privacyLayout.addWidget(self.filenameprivacySendRawOption, 1, 1, Qt.AlignLeft)
self.privacyLayout.addWidget(self.filenameprivacySendHashedOption, 1, 2, Qt.AlignLeft)
self.privacyLayout.addWidget(self.filenameprivacyDontSendOption, 1, 3, Qt.AlignLeft)
self.privacyLayout.addWidget(self.filesizeprivacyLabel, 2, 0)
self.privacyLayout.addWidget(self.filesizeprivacySendRawOption, 2, 1, Qt.AlignLeft)
self.privacyLayout.addWidget(self.filesizeprivacySendHashedOption, 2, 2, Qt.AlignLeft)
self.privacyLayout.addWidget(self.filesizeprivacyDontSendOption, 2, 3, Qt.AlignLeft)
self.privacyFrame.setLayout(self.privacyLayout)
self.privacySettingsGroup.setLayout(self.privacyLayout)
self.privacySettingsGroup.setMaximumHeight(self.privacySettingsGroup.minimumSizeHint().height())
self.privacySettingsLayout.addWidget(self.privacySettingsGroup)
self.privacySettingsLayout.setAlignment(Qt.AlignTop)
self.privacyFrame.setLayout(self.privacySettingsLayout)
self.stackedLayout.addWidget(self.privacyFrame)
def addBottomLayout(self):
config = self.config
resourcespath = self.resourcespath
@ -738,12 +803,12 @@ class ConfigDialog(QtGui.QDialog):
self.nostoreCheckbox = QCheckBox(getMessage("nostore-label"))
self.bottomCheckboxLayout.addWidget(self.showmoreCheckbox)
self.bottomCheckboxLayout.addWidget(self.alwaysshowCheckbox, 0, 1, Qt.AlignLeft)
self.bottomCheckboxLayout.addWidget(self.nostoreCheckbox, 0, 2, Qt.AlignRight)
self.alwaysshowCheckbox.setObjectName(constants.INVERTED_STATE_MARKER + "forceGuiPrompt")
self.nostoreCheckbox.setObjectName("noStore")
self.nostoreCheckbox.toggled.connect(self.runButtonTextUpdate)
self.bottomCheckboxFrame.setLayout(self.bottomCheckboxLayout)
self.mainLayout.addWidget(self.bottomCheckboxFrame, 2, 0, 1, 2)
def tabList(self):
@ -751,9 +816,9 @@ class ConfigDialog(QtGui.QDialog):
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 + "cog.png"),getMessage("misc-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 + "eye.png"),getMessage("privacy-label")))
self.tabListLayout.addWidget(self.tabListWidget)
self.tabListFrame.setLayout(self.tabListLayout)
self.tabListFrame.setFixedWidth(self.tabListFrame.minimumSizeHint().width())
@ -783,7 +848,6 @@ class ConfigDialog(QtGui.QDialog):
self.ensureTabListIsVisible()
self.setFixedWidth(self.minimumSizeHint().width())
self.executablepathCombobox.setFixedWidth(self.mediapathTextbox.width())
self.languageLabel.setFixedWidth(self.languageLabel.width())
def clearGUIData(self, leaveMore=False):
settings = QSettings("Syncplay", "PlayerList")
@ -792,6 +856,10 @@ class ConfigDialog(QtGui.QDialog):
settings.clear()
settings = QSettings("Syncplay", "MainWindow")
settings.clear()
settings = QSettings("Syncplay", "Interface")
settings.beginGroup("Update")
settings.setValue("lastChecked", None)
settings.endGroup()
if not leaveMore:
settings = QSettings("Syncplay", "MoreSettings")
settings.clear()
@ -835,9 +903,9 @@ class ConfigDialog(QtGui.QDialog):
self.mainLayout.setContentsMargins(0,0,0,0)
self.addBasicTab()
self.addMiscTab()
self.addSyncTab()
self.addMessageTab()
self.addPrivacyTab()
self.tabList()
self.mainLayout.addWidget(self.stackedFrame, 0, 1)
@ -847,7 +915,6 @@ class ConfigDialog(QtGui.QDialog):
if self.getMoreState() == False:
self.tabListFrame.hide()
self.nostoreCheckbox.hide()
self.alwaysshowCheckbox.hide()
self.resetButton.hide()
else:
self.showmoreCheckbox.setChecked(True)

View File

@ -39,6 +39,9 @@ class ConsoleUI(threading.Thread):
def updateRoomName(self, room=""):
pass
def updateAutoPlayState(self, newState):
pass
def promptFor(self, prompt=">", message=""):
if message <> "":
print(message)
@ -54,18 +57,21 @@ class ConsoleUI(threading.Thread):
for user in rooms[room]:
userflags = u""
if user.isController():
userflags = userflags + u"(Controller) "
userflags += u"({}) ".format(getMessage("controller-userlist-userflag"))
if user.isReady():
userflags += u"({}) ".format(getMessage("ready-userlist-userflag"))
username = userflags + u"*<{}>*".format(user.username) if user == currentUser else userflags + u"<{}>".format(user.username)
if user.file:
message = u"{} is playing:".format(username)
message = getMessage("userlist-playing-notification").format(username)
self.showMessage(message, True)
message = u" File: '{}' ({})".format(user.file['name'], formatTime(user.file['duration']))
message = u" {}: '{}' ({})".format(getMessage("userlist-file-notification"),user.file['name'], formatTime(user.file['duration']))
if currentUser.file:
if user.file['name'] == currentUser.file['name'] and user.file['size'] != currentUser.file['size']:
message += u" (their file size is different from yours!)"
message += getMessage("different-filesize-notification")
self.showMessage(message, True)
else:
message = u"{} is not playing a file".format(username)
message = getMessage("no-file-played-notification").format(username)
self.showMessage(message, True)
def userListChange(self):
@ -149,6 +155,8 @@ class ConsoleUI(threading.Thread):
elif command.group('command') in constants.COMMANDS_AUTH:
controlpassword = command.group('parameter')
self._syncplayClient.identifyAsController(controlpassword)
elif command.group('command') in constants.COMMANDS_TOGGLE:
self._syncplayClient.toggleReady()
else:
if self._tryAdvancedCommands(data):
return
@ -161,6 +169,7 @@ class ConsoleUI(threading.Thread):
self.showMessage(getMessage("commandlist-notification/pause"), True)
self.showMessage(getMessage("commandlist-notification/seek"), True)
self.showMessage(getMessage("commandlist-notification/help"), True)
self.showMessage(getMessage("commandlist-notification/toggle"), True)
self.showMessage(getMessage("commandlist-notification/create"), True)
self.showMessage(getMessage("commandlist-notification/auth"), True)
self.showMessage(getMessage("syncplay-version-notification").format(syncplay.version), True)

View File

@ -1,12 +1,14 @@
from PySide import QtGui
from PySide.QtCore import Qt, QSettings, QSize, QPoint
from PySide.QtCore import Qt, QSettings, QSize, QPoint, QUrl
from syncplay import utils, constants, version
from syncplay.messages import getMessage
import sys
import time
from datetime import datetime
import re
import os
from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize
lastCheckedForUpdates = None
class UserlistItemDelegate(QtGui.QStyledItemDelegate):
def __init__(self):
@ -74,13 +76,10 @@ class MainWindow(QtGui.QMainWindow):
self.roomInput.setText(self._syncplayClient.getRoom())
self.config = self._syncplayClient.getConfig()
try:
if self.contactLabel and not self.config['showContactInfo']:
self.contactLabel.hide()
if not constants.SHOW_BUTTON_LABELS:
self.hideRoomSeekLabels()
self.hideMiscLabels()
except ():
self.updateReadyState(self.config['readyAtStart'])
except:
pass
self.automaticUpdateCheck()
def promptFor(self, prompt=">", message=""):
# TODO: Prompt user
@ -93,7 +92,7 @@ class MainWindow(QtGui.QMainWindow):
message = message.replace("&gt;", "&gt;</span>")
message = message.replace("\n", "<br />")
if noTimestamp:
self.newMessage(message + "<br />")
self.newMessage("{}<br />".format(message))
else:
self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "<br />")
@ -125,7 +124,13 @@ class MainWindow(QtGui.QMainWindow):
for user in rooms[room]:
useritem = QtGui.QStandardItem(user.username)
isController = user.isController()
sameRoom = room == currentUser.room
if sameRoom:
isReady = user.isReady()
else:
isReady = None
useritem.setData(isController, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE)
useritem.setData(isReady, Qt.UserRole + constants.USERITEM_READY_ROLE)
if user.file:
filesizeitem = QtGui.QStandardItem(formatSize(user.file['size']))
filedurationitem = QtGui.QStandardItem("({})".format(formatTime(user.file['duration'])))
@ -134,7 +139,6 @@ class MainWindow(QtGui.QMainWindow):
sameName = sameFilename(user.file['name'], currentUser.file['name'])
sameSize = sameFilesize(user.file['size'], currentUser.file['size'])
sameDuration = sameFileduration(user.file['duration'], currentUser.file['duration'])
sameRoom = room == currentUser.room
underlinefont = QtGui.QFont()
underlinefont.setUnderline(True)
if sameRoom:
@ -158,13 +162,14 @@ class MainWindow(QtGui.QMainWindow):
font = QtGui.QFont()
if currentUser.username == user.username:
font.setWeight(QtGui.QFont.Bold)
self.updateReadyState(currentUser.isReady())
if isControlledRoom and not isController:
useritem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOTCONTROLLER_COLOR)))
useritem.setFont(font)
useritem.setFlags(useritem.flags() & ~Qt.ItemIsEditable)
filenameitem.setFlags(filenameitem.flags() & ~Qt.ItemIsEditable)
filesizeitem.setFlags(filesizeitem.flags() & ~Qt.ItemIsEditable)
filedurationitem.setFlags(filedurationitem.flags() & ~Qt.ItemIsEditable)
useritem.setFlags(useritem.flags() & ~Qt.ItemIsEditable & ~Qt.ItemIsSelectable)
filenameitem.setFlags(filenameitem.flags() & ~Qt.ItemIsEditable & ~Qt.ItemIsSelectable)
filesizeitem.setFlags(filesizeitem.flags() & ~Qt.ItemIsEditable & ~Qt.ItemIsSelectable)
filedurationitem.setFlags(filedurationitem.flags() & ~Qt.ItemIsEditable & ~Qt.ItemIsSelectable)
roomitem.appendRow((useritem, filesizeitem, filedurationitem, filenameitem))
self.listTreeModel = self._usertreebuffer
self.listTreeView.setModel(self.listTreeModel)
@ -194,6 +199,14 @@ class MainWindow(QtGui.QMainWindow):
except:
pass
def updateReadyState(self, newState):
oldState = self.readyPushButton.isChecked()
if newState != oldState and newState != None:
self.readyPushButton.blockSignals(True)
self.readyPushButton.setChecked(newState)
self.readyPushButton.blockSignals(False)
self.updateReadyIcon()
def roomClicked(self, item):
while item.parent().row() != -1:
item = item.parent()
@ -230,8 +243,18 @@ class MainWindow(QtGui.QMainWindow):
self._syncplayClient.setRoom(room)
self._syncplayClient.sendRoom()
def seekPosition(self):
s = re.match(constants.UI_SEEK_REGEX, self.seekInput.text())
def seekPositionDialog(self):
seekTime, ok = QtGui.QInputDialog.getText(self, getMessage("seektime-menu-label"),
getMessage("seektime-msgbox-label"), QtGui.QLineEdit.Normal,
"0:00")
if ok and seekTime != '':
self.seekPosition(seekTime)
def seekFromButton(self):
self.seekPosition(self.seekInput.text())
def seekPosition(self, seekTime):
s = re.match(constants.UI_SEEK_REGEX, seekTime)
if s:
sign = self._extractSign(s.group('sign'))
t = utils.parseTime(s.group('time'))
@ -240,9 +263,8 @@ class MainWindow(QtGui.QMainWindow):
if sign:
t = self._syncplayClient.getGlobalPosition() + sign * t
self._syncplayClient.setPosition(t)
else:
self.showErrorMessage("Invalid seek value")
self.showErrorMessage(getMessage("invalid-seek-value"))
def undoSeek(self):
tmp_pos = self._syncplayClient.getPlayerPosition()
@ -265,69 +287,6 @@ class MainWindow(QtGui.QMainWindow):
self.exitSyncplay()
self.saveSettings()
def setupSizes(self):
self.hideRoomSeekLabels()
self.miscThreshold = self.seekGroup.sizeHint().width()+self.roomGroup.sizeHint().width()+self.miscGroup.sizeHint().width()+30
self.hideMiscLabels()
self.setMinimumWidth(self.seekGroup.sizeHint().width()+self.roomGroup.sizeHint().width()+self.miscGroup.sizeHint().width()+30)
self.seekGroup.setMinimumWidth(self.seekGroup.sizeHint().width())
self.roomGroup.setMinimumWidth(self.roomGroup.sizeHint().width())
self.miscGroup.setMinimumWidth(self.miscGroup.sizeHint().width())
self.showRoomSeekLabels()
self.showMiscLabels()
windowMaximumWidth = self.maximumWidth()
self.seekGroup.setMaximumWidth(self.seekGroup.sizeHint().width())
self.roomGroup.setMaximumWidth(self.roomGroup.sizeHint().width())
self.miscGroup.setMaximumWidth(self.miscGroup.sizeHint().width())
self.setMaximumWidth(windowMaximumWidth)
self.roomSeekThreshold = self.mainLayout.sizeHint().width()
def hideRoomSeekLabels(self):
self.roomButton.setText("")
self.seekButton.setText("")
def hideMiscLabels(self):
self.unseekButton.setText("")
if constants.MERGE_PLAYPAUSE_BUTTONS:
self.playpauseButton.setText("")
else:
self.playButton.setText("")
self.pauseButton.setText("")
def showRoomSeekLabels(self):
if not constants.SHOW_BUTTON_LABELS:
return
self.roomButton.setText(getMessage("joinroom-guibuttonlabel"))
self.seekButton.setText(getMessage("seektime-guibuttonlabel"))
def showMiscLabels(self):
self.unseekButton.setText(getMessage("undoseek-guibuttonlabel"))
if not constants.SHOW_BUTTON_LABELS:
return
if constants.MERGE_PLAYPAUSE_BUTTONS:
self.playpauseButton.setText(getMessage("togglepause-guibuttonlabel"))
else:
self.playButton.setText(getMessage("play-guibuttonlabel"))
self.pauseButton.setText(getMessage("pause-guibuttonlabel"))
def resizeEvent(self,resizeEvent):
self.updateListGeometry()
if self.roomGroup and self.miscThreshold:
currentWidth = self.mainFrame.size().width()
if currentWidth < self.miscThreshold:
if self.unseekButton.text() != "":
self.hideMiscLabels()
else:
if self.unseekButton.text() == "":
self.showMiscLabels()
if currentWidth < self.roomSeekThreshold:
if self.roomButton.text() != "":
self.hideRoomSeekLabels()
else:
if self.roomButton.text() == "":
self.showRoomSeekLabels()
def loadMediaBrowseSettings(self):
settings = QSettings("Syncplay", "MediaBrowseDialog")
settings.beginGroup("MediaBrowseDialog")
@ -365,6 +324,13 @@ class MainWindow(QtGui.QMainWindow):
self.saveMediaBrowseSettings()
self._syncplayClient._player.openFile(fileName)
def promptForStreamURL(self):
streamURL, ok = QtGui.QInputDialog.getText(self, getMessage("promptforstreamurl-msgbox-label"),
getMessage("promptforstreamurlinfo-msgbox-label"), QtGui.QLineEdit.Normal,
"")
if ok and streamURL != '':
self._syncplayClient._player.openFile(streamURL)
def createControlledRoom(self):
controlroom, ok = QtGui.QInputDialog.getText(self, getMessage("createcontrolledroom-msgbox-label"),
getMessage("controlledroominfo-msgbox-label"), QtGui.QLineEdit.Normal,
@ -405,15 +371,15 @@ class MainWindow(QtGui.QMainWindow):
t = self._syncplayClient.getUserOffset() + sign * t
self._syncplayClient.setUserOffset(t)
else:
self.showErrorMessage("Invalid offset value")
self.showErrorMessage(getMessage("invalid-offset-value"))
def openUserGuide(self):
if sys.platform.startswith('linux'):
self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/linux/")
self.QtGui.QDesktopServices.openUrl(QUrl("http://syncplay.pl/guide/linux/"))
elif sys.platform.startswith('win'):
self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/windows/")
self.QtGui.QDesktopServices.openUrl(QUrl("http://syncplay.pl/guide/windows/"))
else:
self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/")
self.QtGui.QDesktopServices.openUrl(QUrl("http://syncplay.pl/guide/"))
def drop(self):
self.close()
@ -422,8 +388,16 @@ class MainWindow(QtGui.QMainWindow):
window.topSplit = self.topSplitter(Qt.Horizontal, self)
window.outputLayout = QtGui.QVBoxLayout()
window.outputbox = QtGui.QTextEdit()
window.outputbox = QtGui.QTextBrowser()
window.outputbox.setReadOnly(True)
window.outputbox.setTextInteractionFlags(window.outputbox.textInteractionFlags() | Qt.TextSelectableByKeyboard)
window.outputbox.setOpenExternalLinks(True)
window.outputbox.unsetCursor()
window.outputbox.moveCursor(QtGui.QTextCursor.End)
window.outputbox.insertHtml(constants.STYLE_CONTACT_INFO.format(getMessage("contact-label")))
window.outputbox.moveCursor(QtGui.QTextCursor.End)
window.outputbox.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
window.outputlabel = QtGui.QLabel(getMessage("notifications-heading-label"))
window.outputFrame = QtGui.QFrame()
window.outputFrame.setLineWidth(0)
@ -443,19 +417,28 @@ class MainWindow(QtGui.QMainWindow):
window.listFrame = QtGui.QFrame()
window.listFrame.setLineWidth(0)
window.listFrame.setMidLineWidth(0)
window.listFrame.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
window.listLayout.setContentsMargins(0, 0, 0, 0)
window.listLayout.addWidget(window.listlabel)
window.listLayout.addWidget(window.listTreeView)
window.contactLabel = QtGui.QLabel()
window.contactLabel.setWordWrap(True)
window.contactLabel.setFrameStyle(QtGui.QFrame.Box | QtGui.QFrame.Sunken)
window.contactLabel.setLineWidth(1)
window.contactLabel.setMidLineWidth(0)
window.contactLabel.setMargin(2)
window.contactLabel.setText(getMessage("contact-label"))
window.contactLabel.setTextInteractionFlags(Qt.LinksAccessibleByMouse)
window.contactLabel.setOpenExternalLinks(True)
window.listLayout.addWidget(window.contactLabel)
window.roomInput = QtGui.QLineEdit()
window.roomInput.returnPressed.connect(self.joinRoom)
window.roomButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'door_in.png'),
getMessage("joinroom-menu-label"))
window.roomButton.pressed.connect(self.joinRoom)
window.roomLayout = QtGui.QHBoxLayout()
window.roomFrame = QtGui.QFrame()
window.roomFrame.setLayout(self.roomLayout)
window.roomFrame.setContentsMargins(0,0,0,0)
window.roomFrame.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)
window.roomLayout.setContentsMargins(0,0,0,0)
self.roomButton.setToolTip(getMessage("joinroom-tooltip"))
window.roomLayout.addWidget(window.roomInput)
window.roomLayout.addWidget(window.roomButton)
window.roomFrame.setMaximumHeight(window.roomFrame.sizeHint().height())
window.listLayout.addWidget(window.roomFrame, Qt.AlignRight)
window.listFrame.setLayout(window.listLayout)
window.topSplit.addWidget(window.outputFrame)
@ -467,102 +450,106 @@ class MainWindow(QtGui.QMainWindow):
def addBottomLayout(self, window):
window.bottomLayout = QtGui.QHBoxLayout()
window.bottomFrame = QtGui.QFrame()
window.bottomFrame.setLayout(window.bottomLayout)
window.bottomLayout.setContentsMargins(0,0,0,0)
window.addRoomBox(MainWindow)
window.addSeekBox(MainWindow)
window.addMiscBox(MainWindow)
self.addPlaybackLayout(window)
window.bottomLayout.addWidget(window.roomGroup)
window.bottomLayout.addWidget(window.seekGroup)
window.bottomLayout.addWidget(window.miscGroup)
window.mainLayout.addLayout(window.bottomLayout, Qt.AlignLeft)
def addRoomBox(self, window):
window.roomGroup = QtGui.QGroupBox(getMessage("room-heading-label"))
window.roomInput = QtGui.QLineEdit()
window.roomInput.returnPressed.connect(self.joinRoom)
window.roomButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'door_in.png'),
getMessage("joinroom-guibuttonlabel"))
window.roomButton.pressed.connect(self.joinRoom)
window.roomLayout = QtGui.QHBoxLayout()
window.roomInput.setFixedWidth(150)
self.roomButton.setToolTip(getMessage("joinroom-tooltip"))
window.roomLayout.addWidget(window.roomInput)
window.roomLayout.addWidget(window.roomButton)
window.roomGroup.setLayout(window.roomLayout)
window.roomGroup.setFixedHeight(window.roomGroup.sizeHint().height())
def addSeekBox(self, window):
window.seekGroup = QtGui.QGroupBox(getMessage("seek-heading-label"))
window.readyPushButton = QtGui.QPushButton()
readyFont = QtGui.QFont()
readyFont.setWeight(QtGui.QFont.Bold)
window.readyPushButton.setText(getMessage("ready-guipushbuttonlabel"))
window.readyPushButton.setCheckable(True)
window.readyPushButton.setAutoExclusive(False)
window.readyPushButton.toggled.connect(self.changeReadyState)
window.readyPushButton.setFont(readyFont)
window.readyPushButton.setStyleSheet(constants.STYLE_READY_PUSHBUTTON)
window.listLayout.addWidget(window.readyPushButton, Qt.AlignRight)
window.autoPlayPushButton = QtGui.QPushButton()
window.autoPlayPushButton.setVisible(False)
autoPlayFont = QtGui.QFont()
autoPlayFont.setWeight(QtGui.QFont.Bold)
window.autoPlayPushButton.setText(getMessage("autoplay-guipushbuttonlabel"))
window.autoPlayPushButton.setCheckable(True)
window.autoPlayPushButton.setAutoExclusive(False)
window.autoPlayPushButton.toggled.connect(self.changeAutoPlayState)
window.autoPlayPushButton.setFont(autoPlayFont)
window.autoPlayPushButton.setStyleSheet(constants.STYLE_AUTO_PLAY_PUSHBUTTON)
window.listLayout.addWidget(window.autoPlayPushButton, Qt.AlignRight)
self.updateAutoPlayIcon()
window.mainLayout.addWidget(window.bottomFrame, Qt.AlignLeft)
window.bottomFrame.setMaximumHeight(window.bottomFrame.minimumSizeHint().height())
def addPlaybackLayout(self, window):
window.playbackFrame = QtGui.QFrame()
window.playbackFrame.setVisible(False)
window.playbackFrame.setContentsMargins(0,0,0,0)
window.playbackLayout = QtGui.QHBoxLayout()
window.playbackLayout.setAlignment(Qt.AlignLeft)
window.playbackLayout.setContentsMargins(0,0,0,0)
window.playbackFrame.setLayout(window.playbackLayout)
window.seekInput = QtGui.QLineEdit()
window.seekInput.returnPressed.connect(self.seekPosition)
window.seekButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'clock_go.png'),
getMessage("seektime-guibuttonlabel"))
window.seekButton.pressed.connect(self.seekPosition)
self.seekButton.setToolTip(getMessage("seektime-tooltip"))
window.seekLayout = QtGui.QHBoxLayout()
window.seekInput.returnPressed.connect(self.seekFromButton)
window.seekButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'clock_go.png'), "")
window.seekButton.setToolTip(getMessage("seektime-menu-label"))
window.seekButton.pressed.connect(self.seekFromButton)
window.seekInput.setText("0:00")
window.seekInput.setFixedWidth(60)
window.seekLayout.addWidget(window.seekInput)
window.seekLayout.addWidget(window.seekButton)
window.seekGroup.setLayout(window.seekLayout)
window.seekGroup.setFixedHeight(window.seekGroup.sizeHint().height())
def addMiscBox(self, window):
window.miscGroup = QtGui.QGroupBox(getMessage("othercommands-heading-label"))
window.unseekButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'arrow_undo.png'),
getMessage("undoseek-guibuttonlabel"))
window.playbackLayout.addWidget(window.seekInput)
window.playbackLayout.addWidget(window.seekButton)
window.unseekButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'arrow_undo.png'), "")
window.unseekButton.setToolTip(getMessage("undoseek-menu-label"))
window.unseekButton.pressed.connect(self.undoSeek)
self.unseekButton.setToolTip(getMessage("undoseek-tooltip"))
window.miscLayout = QtGui.QHBoxLayout()
window.miscLayout.addWidget(window.unseekButton)
if constants.MERGE_PLAYPAUSE_BUTTONS == True:
window.playpauseButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_pause_blue.png'),
getMessage("togglepause-guibuttonlabel"))
window.playpauseButton.pressed.connect(self.togglePause)
window.miscLayout.addWidget(window.playpauseButton)
self.playpauseButton.setToolTip(getMessage("togglepause-tooltip"))
else:
window.playButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_play_blue.png'),
getMessage("play-guibuttonlabel"))
window.playButton.pressed.connect(self.play)
window.miscLayout.addWidget(window.playButton)
window.pauseButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_pause_blue.png'),
getMessage("pause-guibuttonlabel"))
window.pauseButton.pressed.connect(self.pause)
window.miscLayout.addWidget(window.pauseButton)
self.playButton.setToolTip(getMessage("play-tooltip"))
self.pauseButton.setToolTip(getMessage("pause-tooltip"))
window.miscGroup.setLayout(window.miscLayout)
window.miscGroup.setFixedHeight(window.miscGroup.sizeHint().height())
window.playbackLayout.addWidget(window.unseekButton)
window.playButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_play_blue.png'), "")
window.playButton.setToolTip(getMessage("play-menu-label"))
window.playButton.pressed.connect(self.play)
window.playbackLayout.addWidget(window.playButton)
window.pauseButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_pause_blue.png'), "")
window.pauseButton.setToolTip(getMessage("pause-menu-label"))
window.pauseButton.pressed.connect(self.pause)
window.playbackLayout.addWidget(window.pauseButton)
window.playbackFrame.setMaximumHeight(window.playbackFrame.sizeHint().height())
window.playbackFrame.setMaximumWidth(window.playbackFrame.sizeHint().width())
window.outputLayout.addWidget(window.playbackFrame)
def addMenubar(self, window):
window.menuBar = QtGui.QMenuBar()
# File menu
window.fileMenu = QtGui.QMenu(getMessage("file-menu-label"), self)
window.openAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'folder_explore.png'),
getMessage("openmedia-menu-label"))
window.openAction.triggered.connect(self.browseMediapath)
window.openAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'world_explore.png'),
getMessage("openstreamurl-menu-label"))
window.openAction.triggered.connect(self.promptForStreamURL)
window.exitAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'cross.png'),
getMessage("exit-menu-label"))
window.exitAction.triggered.connect(self.exitSyncplay)
window.menuBar.addMenu(window.fileMenu)
# Playback menu
window.playbackMenu = QtGui.QMenu(getMessage("playback-menu-label"), self)
window.playAction = window.playbackMenu.addAction(QtGui.QIcon(self.resourcespath + 'control_play_blue.png'), getMessage("play-menu-label"))
window.playAction.triggered.connect(self.play)
window.pauseAction = window.playbackMenu.addAction(QtGui.QIcon(self.resourcespath + 'control_pause_blue.png'), getMessage("pause-menu-label"))
window.pauseAction.triggered.connect(self.pause)
window.seekAction = window.playbackMenu.addAction(QtGui.QIcon(self.resourcespath + 'clock_go.png'), getMessage("seektime-menu-label"))
window.seekAction.triggered.connect(self.seekPositionDialog)
window.unseekAction = window.playbackMenu.addAction(QtGui.QIcon(self.resourcespath + 'arrow_undo.png'), getMessage("undoseek-menu-label"))
window.unseekAction.triggered.connect(self.undoSeek)
window.menuBar.addMenu(window.playbackMenu)
# Advanced menu
window.advancedMenu = QtGui.QMenu(getMessage("advanced-menu-label"), self)
window.setoffsetAction = window.advancedMenu.addAction(QtGui.QIcon(self.resourcespath + 'timeline_marker.png'),
getMessage("setoffset-menu-label"))
@ -574,12 +561,32 @@ class MainWindow(QtGui.QMainWindow):
window.identifyascontroller = window.advancedMenu.addAction(QtGui.QIcon(self.resourcespath + 'key_go.png'),
getMessage("identifyascontroller-menu-label"))
window.identifyascontroller.triggered.connect(self.identifyAsController)
window.menuBar.addMenu(window.advancedMenu)
# Window menu
window.windowMenu = QtGui.QMenu(getMessage("window-menu-label"), self)
window.playbackAction = window.windowMenu.addAction(getMessage("playbackbuttons-menu-label"))
window.playbackAction.setCheckable(True)
window.playbackAction.triggered.connect(self.updatePlaybackFrameVisibility)
window.autoplayAction = window.windowMenu.addAction(getMessage("autoplay-menu-label"))
window.autoplayAction.setCheckable(True)
window.autoplayAction.triggered.connect(self.updateAutoplayVisibility)
window.menuBar.addMenu(window.windowMenu)
# Help menu
window.helpMenu = QtGui.QMenu(getMessage("help-menu-label"), self)
window.userguideAction = window.helpMenu.addAction(QtGui.QIcon(self.resourcespath + 'help.png'),
getMessage("userguide-menu-label"))
window.userguideAction.triggered.connect(self.openUserGuide)
window.updateAction = window.helpMenu.addAction(QtGui.QIcon(self.resourcespath + 'application_get.png'),
getMessage("update-menu-label"))
window.updateAction.triggered.connect(self.userCheckForUpdates)
window.menuBar.addMenu(window.helpMenu)
window.mainLayout.setMenuBar(window.menuBar)
@ -606,6 +613,81 @@ class MainWindow(QtGui.QMainWindow):
self.listbox.insertHtml(item)
self.listbox.moveCursor(QtGui.QTextCursor.End)
def updatePlaybackFrameVisibility(self):
self.playbackFrame.setVisible(self.playbackAction.isChecked())
def updateAutoplayVisibility(self):
self.autoPlayPushButton.setVisible(self.autoplayAction.isChecked())
def changeReadyState(self):
self.updateReadyIcon()
self._syncplayClient.changeReadyState(self.readyPushButton.isChecked())
def updateAutoPlayState(self, newState):
oldState = self.autoPlayPushButton.isChecked()
if newState != oldState and newState != None:
self.autoPlayPushButton.setChecked(newState)
self.updateAutoPlayIcon()
def changeAutoPlayState(self):
self.updateAutoPlayIcon()
self._syncplayClient.changeAutoPlayState(self.autoPlayPushButton.isChecked())
def updateReadyIcon(self):
ready = self.readyPushButton.isChecked()
if ready:
self.readyPushButton.setIcon(QtGui.QIcon(self.resourcespath + 'tick_checkbox.png'))
else:
self.readyPushButton.setIcon(QtGui.QIcon(self.resourcespath + 'empty_checkbox.png'))
def updateAutoPlayIcon(self):
ready = self.autoPlayPushButton.isChecked()
if ready:
self.autoPlayPushButton.setIcon(QtGui.QIcon(self.resourcespath + 'tick_checkbox.png'))
else:
self.autoPlayPushButton.setIcon(QtGui.QIcon(self.resourcespath + 'empty_checkbox.png'))
def automaticUpdateCheck(self):
currentDateTime = datetime.utcnow()
if not self.config['checkForUpdatesAutomatically']:
return
if self.config['lastCheckedForUpdates']:
configLastChecked = datetime.strptime(self.config["lastCheckedForUpdates"], "%Y-%m-%d %H:%M:%S.%f")
if self.lastCheckedForUpdates is None or configLastChecked > self.lastCheckedForUpdates:
self.lastCheckedForUpdates = configLastChecked
if self.lastCheckedForUpdates is None:
self.checkForUpdates()
else:
timeDelta = currentDateTime - self.lastCheckedForUpdates
if timeDelta.total_seconds() > constants.AUTOMATIC_UPDATE_CHECK_FREQUENCY:
self.checkForUpdates()
def userCheckForUpdates(self):
self.checkForUpdates(userInitiated=True)
def checkForUpdates(self, userInitiated=False):
self.lastCheckedForUpdates = datetime.utcnow()
updateStatus, updateMessage, updateURL = self._syncplayClient.checkForUpdate(userInitiated)
if updateMessage is None:
if updateStatus == "uptodate":
updateMessage = getMessage("syncplay-uptodate-notification")
elif updateStatus == "updateavailale":
updateMessage = getMessage("syncplay-updateavailable-notification")
else:
import syncplay
updateMessage = getMessage("update-check-failed-notification").format(syncplay.version)
if userInitiated == True:
updateURL = constants.SYNCPLAY_DOWNLOAD_URL
if updateURL is not None:
reply = QtGui.QMessageBox.question(self, "Syncplay",
updateMessage, QtGui.QMessageBox.StandardButton.Yes | QtGui.QMessageBox.StandardButton.No)
if reply == QtGui.QMessageBox.Yes:
self.QtGui.QDesktopServices.openUrl(QUrl(updateURL))
elif userInitiated:
QtGui.QMessageBox.information(self, "Syncplay", updateMessage)
else:
self.showMessage(updateMessage)
def dragEnterEvent(self, event):
data = event.mimeData()
urls = data.urls()
@ -633,6 +715,12 @@ class MainWindow(QtGui.QMainWindow):
settings.beginGroup("MainWindow")
settings.setValue("size", self.size())
settings.setValue("pos", self.pos())
settings.setValue("showPlaybackButtons", self.playbackAction.isChecked())
settings.setValue("showAutoPlayButton", self.autoplayAction.isChecked())
settings.endGroup()
settings = QSettings("Syncplay", "Interface")
settings.beginGroup("Update")
settings.setValue("lastChecked", self.lastCheckedForUpdates)
settings.endGroup()
def loadSettings(self):
@ -640,7 +728,16 @@ class MainWindow(QtGui.QMainWindow):
settings.beginGroup("MainWindow")
self.resize(settings.value("size", QSize(700, 500)))
self.move(settings.value("pos", QPoint(200, 200)))
if settings.value("showPlaybackButtons", "false") == "true":
self.playbackAction.setChecked(True)
self.updatePlaybackFrameVisibility()
if settings.value("showAutoPlayButton", "false") == "true":
self.autoplayAction.setChecked(True)
self.updateAutoplayVisibility()
settings.endGroup()
settings = QSettings("Syncplay", "Interface")
settings.beginGroup("Update")
self.lastCheckedForUpdates = settings.value("lastChecked", None)
def __init__(self):
super(MainWindow, self).__init__()
@ -656,7 +753,6 @@ class MainWindow(QtGui.QMainWindow):
self.addBottomLayout(self)
self.addMenubar(self)
self.addMainFrame(self)
self.setupSizes()
self.loadSettings()
self.setWindowIcon(QtGui.QIcon(self.resourcespath + "syncplay.png"))
self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.AA_DontUseNativeMenuBar & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint)

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2
import site, sys

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2
#coding:utf8
import site, sys
@ -20,5 +20,5 @@ if __name__ == '__main__':
argsGetter = ConfigurationGetter()
args = argsGetter.getConfiguration()
reactor.listenTCP(int(args.port), SyncFactory(args.password, args.motd_file, args.isolate_rooms, args.salt))
reactor.listenTCP(int(args.port), SyncFactory(args.password, args.motd_file, args.isolate_rooms, args.salt, args.disable_ready))
reactor.run()