20
GNUmakefile
@ -4,9 +4,27 @@ ifndef VLC_SUPPORT
|
||||
VLC_SUPPORT = true
|
||||
endif
|
||||
|
||||
ifeq ($(shell uname)),FreeBSD)
|
||||
BSD = true
|
||||
endif
|
||||
ifeq ($(shell uname)),NetBSD)
|
||||
BSD = true
|
||||
endif
|
||||
ifeq ($(shell uname)),OpenBSD)
|
||||
BSD = true
|
||||
endif
|
||||
ifeq ($(shell uname)),DragonFly)
|
||||
BSD = true
|
||||
endif
|
||||
|
||||
ifeq ($(SINGLE_USER),false)
|
||||
ifneq ($(BSD),true)
|
||||
BASE_PATH = /usr
|
||||
VLC_LIB_PATH = ${PREFIX}/usr/lib
|
||||
else
|
||||
BASE_PATH = /usr/local
|
||||
VLC_LIB_PATH = ${PREFIX}/usr/local/lib
|
||||
endif
|
||||
else
|
||||
BASE_PATH = ${HOME}/.local
|
||||
VLC_LIB_PATH = ${HOME}/.local/share
|
||||
@ -18,8 +36,6 @@ endif
|
||||
SHARE_PATH = ${PREFIX}$(BASE_PATH)/share
|
||||
|
||||
common:
|
||||
-mkdir -p $(LIB_PATH)/syncplay/resources/
|
||||
-mkdir -p $(LIB_PATH)/syncplay/resources/lua
|
||||
-mkdir -p $(LIB_PATH)/syncplay/resources/lua/intf
|
||||
-mkdir -p $(APP_SHORTCUT_PATH)
|
||||
-mkdir -p $(SHARE_PATH)/app-install/icons
|
||||
|
||||
@ -16,7 +16,13 @@ import syncplay
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
p = "C:\\Program Files (x86)\\NSIS\\makensis.exe" #TODO: how to move that into proper place, huh
|
||||
from syncplay.messages import getMissingStrings
|
||||
missingStrings = getMissingStrings()
|
||||
if missingStrings is not None and missingStrings is not "":
|
||||
import warnings
|
||||
warnings.warn("MISSING/UNUSED STRINGS DETECTED:\n{}".format(missingStrings))
|
||||
|
||||
p = "C:\\Program Files (x86)\\NSIS\\Unicode\\makensis.exe" #TODO: how to move that into proper place, huh
|
||||
NSIS_COMPILE = p if os.path.isfile(p) else "makensis.exe"
|
||||
OUT_DIR = "syncplay v{}".format(syncplay.version)
|
||||
SETUP_SCRIPT_PATH = "syncplay_setup.nsi"
|
||||
@ -27,7 +33,9 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
|
||||
LoadLanguageFile "$${NSISDIR}\Contrib\Language files\English.nlf"
|
||||
LoadLanguageFile "$${NSISDIR}\Contrib\Language files\Polish.nlf"
|
||||
|
||||
LoadLanguageFile "$${NSISDIR}\Contrib\Language files\Russian.nlf"
|
||||
LoadLanguageFile "$${NSISDIR}\Contrib\Language files\German.nlf"
|
||||
|
||||
Name "Syncplay $version"
|
||||
OutFile "Syncplay $version Setup.exe"
|
||||
InstallDir $$PROGRAMFILES\Syncplay
|
||||
@ -35,18 +43,23 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
XPStyle on
|
||||
Icon resources\icon.ico ;Change DIR
|
||||
SetCompressor /SOLID lzma
|
||||
|
||||
|
||||
VIProductVersion "$version.0"
|
||||
VIAddVersionKey /LANG=$${LANG_ENGLISH} "ProductName" "Syncplay"
|
||||
VIAddVersionKey /LANG=$${LANG_ENGLISH} "FileVersion" "$version.0"
|
||||
VIAddVersionKey /LANG=$${LANG_ENGLISH} "LegalCopyright" "Syncplay"
|
||||
VIAddVersionKey /LANG=$${LANG_ENGLISH} "FileDescription" "Syncplay"
|
||||
|
||||
|
||||
VIAddVersionKey /LANG=$${LANG_POLISH} "ProductName" "Syncplay"
|
||||
VIAddVersionKey /LANG=$${LANG_POLISH} "FileVersion" "$version.0"
|
||||
VIAddVersionKey /LANG=$${LANG_POLISH} "LegalCopyright" "Syncplay"
|
||||
VIAddVersionKey /LANG=$${LANG_POLISH} "FileDescription" "Syncplay"
|
||||
|
||||
|
||||
VIAddVersionKey /LANG=$${LANG_RUSSIAN} "ProductName" "Syncplay"
|
||||
VIAddVersionKey /LANG=$${LANG_RUSSIAN} "FileVersion" "$version.0"
|
||||
VIAddVersionKey /LANG=$${LANG_RUSSIAN} "LegalCopyright" "Syncplay"
|
||||
VIAddVersionKey /LANG=$${LANG_RUSSIAN} "FileDescription" "Syncplay"
|
||||
|
||||
LangString ^SyncplayLanguage $${LANG_ENGLISH} "en"
|
||||
LangString ^Associate $${LANG_ENGLISH} "Associate Syncplay with multimedia files."
|
||||
LangString ^VLC $${LANG_ENGLISH} "Install Syncplay interface for VLC 2 and above"
|
||||
@ -56,7 +69,7 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
LangString ^Desktop $${LANG_ENGLISH} "Desktop"
|
||||
LangString ^QuickLaunchBar $${LANG_ENGLISH} "Quick Launch Bar"
|
||||
LangString ^UninstConfig $${LANG_ENGLISH} "Delete configuration file."
|
||||
|
||||
|
||||
LangString ^SyncplayLanguage $${LANG_POLISH} "pl"
|
||||
LangString ^Associate $${LANG_POLISH} "Skojarz Syncplaya z multimediami"
|
||||
LangString ^VLC $${LANG_POLISH} "Zainstaluj interface Syncplaya dla VLC 2+"
|
||||
@ -66,16 +79,39 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
LangString ^Desktop $${LANG_POLISH} "Pulpit"
|
||||
LangString ^QuickLaunchBar $${LANG_POLISH} "Pasek szybkiego uruchamiania"
|
||||
LangString ^UninstConfig $${LANG_POLISH} "Usun plik konfiguracyjny."
|
||||
|
||||
|
||||
LangString ^SyncplayLanguage $${LANG_RUSSIAN} "ru"
|
||||
LangString ^Associate $${LANG_RUSSIAN} "Ассоциировать Syncplay с видеофайлами"
|
||||
LangString ^VLC $${LANG_RUSSIAN} "Установить интерфейс Syncplay для VLC 2+"
|
||||
LangString ^BrowseVLCBtn $${LANG_RUSSIAN} "Укажите папку VLC"
|
||||
LangString ^Shortcut $${LANG_RUSSIAN} "Создать ярлыки:"
|
||||
LangString ^StartMenu $${LANG_RUSSIAN} "в меню Пуск"
|
||||
LangString ^Desktop $${LANG_RUSSIAN} "на рабочем столе"
|
||||
LangString ^QuickLaunchBar $${LANG_RUSSIAN} "в меню быстрого запуска"
|
||||
LangString ^UninstConfig $${LANG_RUSSIAN} "Удалить файл настроек."
|
||||
|
||||
LangString ^SyncplayLanguage $${LANG_GERMAN} "de"
|
||||
LangString ^Associate $${LANG_GERMAN} "Syncplay mit Multimedia-Dateien assoziieren."
|
||||
LangString ^VLC $${LANG_GERMAN} "Syncplay-Interface für VLC installieren (ab VLC 2+)"
|
||||
LangString ^Shortcut $${LANG_GERMAN} "Erstelle Verknüpfungen an folgenden Orten:"
|
||||
LangString ^BrowseVLCBtn $${LANG_GERMAN} "VLC-Ordner wählen"
|
||||
LangString ^StartMenu $${LANG_GERMAN} "Startmenü"
|
||||
LangString ^Desktop $${LANG_GERMAN} "Desktop"
|
||||
LangString ^QuickLaunchBar $${LANG_GERMAN} "Schnellstartleiste"
|
||||
LangString ^UninstConfig $${LANG_GERMAN} "Konfigurationsdatei löschen."
|
||||
|
||||
; Remove text to save space
|
||||
LangString ^ClickInstall $${LANG_GERMAN} " "
|
||||
|
||||
PageEx license
|
||||
LicenseData resources\license.txt
|
||||
PageExEnd
|
||||
Page custom DirectoryCustom DirectoryCustomLeave
|
||||
Page instFiles
|
||||
|
||||
|
||||
UninstPage custom un.installConfirm un.installConfirmLeave
|
||||
UninstPage instFiles
|
||||
|
||||
|
||||
Var Dialog
|
||||
Var Icon_Syncplay
|
||||
Var Icon_Syncplay_Handle
|
||||
@ -98,7 +134,7 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
Var Label_Size
|
||||
Var Label_Space
|
||||
Var Text_Directory
|
||||
|
||||
|
||||
Var Uninst_Dialog
|
||||
Var Uninst_Icon
|
||||
Var Uninst_Icon_Handle
|
||||
@ -107,14 +143,14 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
Var Uninst_Text_Directory
|
||||
Var Uninst_CheckBox_Config
|
||||
Var Uninst_CheckBox_Config_State
|
||||
|
||||
|
||||
Var Size
|
||||
Var SizeHex
|
||||
Var AvailibleSpace
|
||||
Var AvailibleSpaceGiB
|
||||
Var Drive
|
||||
Var VLC_Directory
|
||||
|
||||
|
||||
!macro APP_ASSOCIATE EXT FileCLASS DESCRIPTION COMMANDTEXT COMMAND
|
||||
WriteRegStr HKCR ".$${EXT}" "" "$${FileCLASS}"
|
||||
WriteRegStr HKCR "$${FileCLASS}" "" `$${DESCRIPTION}`
|
||||
@ -122,23 +158,23 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
WriteRegStr HKCR "$${FileCLASS}\shell\open" "" `$${COMMANDTEXT}`
|
||||
WriteRegStr HKCR "$${FileCLASS}\shell\open\command" "" `$${COMMAND}`
|
||||
!macroend
|
||||
|
||||
|
||||
!macro APP_UNASSOCIATE EXT FileCLASS
|
||||
; Backup the previously associated File class
|
||||
ReadRegStr $$R0 HKCR ".$${EXT}" `$${FileCLASS}_backup`
|
||||
WriteRegStr HKCR ".$${EXT}" "" "$$R0"
|
||||
DeleteRegKey HKCR `$${FileCLASS}`
|
||||
!macroend
|
||||
|
||||
|
||||
!macro ASSOCIATE EXT
|
||||
!insertmacro APP_ASSOCIATE "$${EXT}" "Syncplay.$${EXT}" "$$INSTDIR\Syncplay.exe,%1%" \
|
||||
"Open with Syncplay" "$$INSTDIR\Syncplay.exe $$\"%1$$\""
|
||||
!macroend
|
||||
|
||||
|
||||
!macro UNASSOCIATE EXT
|
||||
!insertmacro APP_UNASSOCIATE "$${EXT}" "Syncplay.$${EXT}"
|
||||
!macroend
|
||||
|
||||
|
||||
;Prevents from running more than one instance of installer and sets default state of checkboxes
|
||||
Function .onInit
|
||||
System::Call 'kernel32::CreateMutexA(i 0, i 0, t "SyncplayMutex") i .r1 ?e'
|
||||
@ -164,6 +200,10 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
Push English
|
||||
Push $${LANG_POLISH}
|
||||
Push Polski
|
||||
Push $${LANG_RUSSIAN}
|
||||
Push Русский
|
||||
Push $${LANG_GERMAN}
|
||||
Push Deutsch
|
||||
Push A ; A means auto count languages
|
||||
LangDLL::LangDialog "Language Selection" "Please select the language of Syncplay and the installer"
|
||||
Pop $$LANGUAGE
|
||||
@ -216,13 +256,13 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
$${NSD_CreateLabel} 8u 85u 187u 10u "$$(^Shortcut)"
|
||||
Pop $$Label_Shortcut
|
||||
|
||||
$${NSD_CreateCheckbox} 8u 98u 50u 10u "$$(^StartMenu)"
|
||||
$${NSD_CreateCheckbox} 8u 98u 60u 10u "$$(^StartMenu)"
|
||||
Pop $$CheckBox_StartMenuShortcut
|
||||
|
||||
$${NSD_CreateCheckbox} 68u 98u 50u 10u "$$(^Desktop)"
|
||||
$${NSD_CreateCheckbox} 78u 98u 70u 10u "$$(^Desktop)"
|
||||
Pop $$CheckBox_DesktopShortcut
|
||||
|
||||
$${NSD_CreateCheckbox} 128u 98u 150u 10u "$$(^QuickLaunchBar)"
|
||||
$${NSD_CreateCheckbox} 158u 98u 130u 10u "$$(^QuickLaunchBar)"
|
||||
Pop $$CheckBox_QuickLaunchShortcut
|
||||
|
||||
$${If} $$CheckBox_Associate_State == $${BST_CHECKED}
|
||||
@ -476,18 +516,18 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
IfFileExists "$$VLC_Directory\lua\intf\syncplay.lua" 0 +2
|
||||
Delete $$VLC_Directory\lua\intf\syncplay.lua
|
||||
FunctionEnd
|
||||
|
||||
|
||||
Section "Install"
|
||||
SetOverwrite on
|
||||
SetOutPath $$INSTDIR
|
||||
WriteUninstaller uninstall.exe
|
||||
|
||||
|
||||
$installFiles
|
||||
|
||||
|
||||
Call InstallOptions
|
||||
Call WriteRegistry
|
||||
SectionEnd
|
||||
|
||||
|
||||
Section "Uninstall"
|
||||
Call un.AssociateDel
|
||||
Call un.InstallOptions
|
||||
@ -582,7 +622,9 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock
|
||||
'resources/timeline_marker.png','resources/control_play_blue.png',
|
||||
'resources/mpc-hc.png','resources/mpc-hc64.png','resources/mplayer.png',
|
||||
'resources/mpv.png','resources/vlc.png', 'resources/house.png', 'resources/film_link.png',
|
||||
'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/bullet_black.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 = ["resources/icon.ico", "resources/syncplay.png"]
|
||||
resources.extend(guiIcons)
|
||||
|
||||
|
Before Width: | Height: | Size: 211 B |
BIN
resources/chevrons_right.png
Normal file
|
After Width: | Height: | Size: 418 B |
BIN
resources/key_go.png
Normal file
|
After Width: | Height: | Size: 744 B |
BIN
resources/lock.png
Normal file
|
After Width: | Height: | Size: 884 B |
BIN
resources/lock_open.png
Normal file
|
After Width: | Height: | Size: 824 B |
@ -5,7 +5,7 @@
|
||||
Principal author: Etoh
|
||||
Other contributors: DerGenaue, jb
|
||||
Project: http://syncplay.pl/
|
||||
Version: 0.2.2
|
||||
Version: 0.2.3
|
||||
|
||||
Note:
|
||||
* This interface module is intended to be used in conjunction with Syncplay.
|
||||
@ -78,16 +78,7 @@ You may also need to re-copy the syncplay.lua file when you update VLC.
|
||||
|
||||
--]==========================================================================]
|
||||
|
||||
local modulepath = config["modulepath"]
|
||||
if(modulepath ~= nil) and (modulepath ~= "") then
|
||||
-- Workaround for when the script is not being run from the usual VLC intf folder.
|
||||
package.path = modulepath
|
||||
pcall(require,"common")
|
||||
else
|
||||
require "common"
|
||||
end
|
||||
|
||||
local connectorversion = "0.2.2"
|
||||
local connectorversion = "0.2.3"
|
||||
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
|
||||
@ -119,9 +110,32 @@ local newtitle = 0
|
||||
|
||||
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]", ".")
|
||||
local s, i, d = string.match(str, "^([+-]?)(%d*)%.?(%d*)$")
|
||||
if not s or not i or not d then
|
||||
return nil
|
||||
end
|
||||
|
||||
if s == "-" then
|
||||
s = -1
|
||||
else
|
||||
s = 1
|
||||
end
|
||||
if i == "" then
|
||||
i = "0"
|
||||
end
|
||||
if d == nil or d == "" then
|
||||
d = "0"
|
||||
end
|
||||
return s * (tonumber(i) + tonumber(d)/(10^string.len(d)))
|
||||
end
|
||||
|
||||
-- Start hosting Syncplay interface.
|
||||
|
||||
port = tonumber(config["port"])
|
||||
port = radixsafe_tonumber(config["port"])
|
||||
if (port == nil or port < 1) then port = 4123 end
|
||||
|
||||
function quit_vlc()
|
||||
@ -256,12 +270,12 @@ function set_time ( timetoset)
|
||||
if input then
|
||||
local response, errormsg, realtime, titletrack
|
||||
realtime = timetoset % titlemultiplier
|
||||
oldtitle = tonumber(get_var("title", 0))
|
||||
oldtitle = radixsafe_tonumber(get_var("title", 0))
|
||||
newtitle = (timetoset - realtime) / titlemultiplier
|
||||
if oldtitle ~= newtitle and newtitle > -1 then
|
||||
set_var("title", tonumber(newtitle))
|
||||
set_var("title", radixsafe_tonumber(newtitle))
|
||||
end
|
||||
errormsg = set_var("time", tonumber(realtime))
|
||||
errormsg = set_var("time", radixsafe_tonumber(realtime))
|
||||
return errormsg
|
||||
else
|
||||
return noinput
|
||||
@ -393,7 +407,7 @@ function display_osd ( argument )
|
||||
if input then
|
||||
osdarray = get_args(argument,3)
|
||||
--position, duration, message -> message, , position, duration (converted from seconds to microseconds)
|
||||
local osdduration = tonumber(osdarray[2]) * 1000 * 1000
|
||||
local osdduration = radixsafe_tonumber(osdarray[2]) * 1000 * 1000
|
||||
vlc.osd.message(osdarray[3],channel1,osdarray[1],osdduration)
|
||||
else
|
||||
errormsg = noinput
|
||||
@ -425,11 +439,11 @@ function do_command ( command, argument)
|
||||
elseif command == "get-filepath" then response = "filepath"..msgseperator..errormerge(get_filepath())..msgterminator
|
||||
elseif command == "get-filename" then response = "filename"..msgseperator..errormerge(get_filename())..msgterminator
|
||||
elseif command == "get-title" then response = "title"..msgseperator..errormerge(get_var("title", 0))..msgterminator
|
||||
elseif command == "set-position" then errormsg = set_time(tonumber(argument))
|
||||
elseif command == "seek-within-title" then errormsg = set_var("time", tonumber(argument))
|
||||
elseif command == "set-position" then errormsg = set_time(radixsafe_tonumber(argument))
|
||||
elseif command == "seek-within-title" then errormsg = set_var("time", radixsafe_tonumber(argument))
|
||||
elseif command == "set-playstate" then errormsg = set_playstate(argument)
|
||||
elseif command == "set-rate" then errormsg = set_var("rate", tonumber(argument))
|
||||
elseif command == "set-title" then errormsg = set_var("title", tonumber(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 == "load-file" then response = load_file(argument)
|
||||
elseif command == "close-vlc" then quit_vlc()
|
||||
@ -471,7 +485,7 @@ function set_playstate(argument)
|
||||
end
|
||||
|
||||
if string.sub(vlc.misc.version(),1,2) == "1." then
|
||||
vlc.msg.err("This version of VLC is not known to support version " .. connectorversion .. " of the Syncplay interface module on Windows. Please use VLC 2+.")
|
||||
vlc.msg.err("This version of VLC is not known to support the Syncplay interface module. Please use VLC 2+.")
|
||||
quit_vlc()
|
||||
else
|
||||
l = vlc.net.listen_tcp(host, port)
|
||||
|
||||
BIN
resources/page_white_key.png
Normal file
|
After Width: | Height: | Size: 616 B |
BIN
resources/tick.png
Normal file
|
After Width: | Height: | Size: 537 B |
BIN
resources/user_key.png
Normal file
|
After Width: | Height: | Size: 834 B |
@ -1,3 +1,3 @@
|
||||
version = '1.2.9'
|
||||
milestone = 'Pineapple, Pulverize and Destroy!'
|
||||
version = '1.3.0'
|
||||
milestone = 'Akki'
|
||||
projectURL = 'http://syncplay.pl/'
|
||||
|
||||
@ -4,12 +4,12 @@ import time
|
||||
import re
|
||||
from twisted.internet.protocol import ClientFactory
|
||||
from twisted.internet import reactor, task
|
||||
from functools import wraps
|
||||
from syncplay.protocols import SyncClientProtocol
|
||||
from syncplay import utils, constants
|
||||
from syncplay.messages import getMessage
|
||||
import threading
|
||||
from syncplay.messages import getMissingStrings, getMessage
|
||||
from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \
|
||||
PRIVACY_HIDDENFILENAME, FILENAME_STRIP_REGEX
|
||||
PRIVACY_HIDDENFILENAME
|
||||
import collections
|
||||
|
||||
class SyncClientFactory(ClientFactory):
|
||||
@ -61,14 +61,21 @@ class SyncplayClient(object):
|
||||
constants.SHOW_DIFFERENT_ROOM_OSD = config['showDifferentRoomOSD']
|
||||
constants.SHOW_SAME_ROOM_OSD = config['showSameRoomOSD']
|
||||
constants.SHOW_DURATION_NOTIFICATION = config['showDurationNotification']
|
||||
constants.DEBUG_MODE = config['debug']
|
||||
|
||||
self.controlpasswords = {}
|
||||
self.lastControlPasswordAttempt = None
|
||||
self.serverVersion = "0.0.0"
|
||||
|
||||
self.lastLeftTime = 0
|
||||
self.lastLeftUser = u""
|
||||
self.protocolFactory = SyncClientFactory(self)
|
||||
self.ui = UiManager(self, ui)
|
||||
self.userlist = SyncplayUserlist(self.ui, self)
|
||||
self._protocol = None
|
||||
""":type : SyncClientProtocol|None"""
|
||||
self._player = None
|
||||
if config['room'] == None or config['room'] == '':
|
||||
if config['room'] is None or config['room'] == '':
|
||||
config['room'] = config['name'] # ticket #58
|
||||
self.defaultRoom = config['room']
|
||||
self.playerPositionBeforeLastSeek = 0.0
|
||||
@ -99,7 +106,13 @@ class SyncplayClient(object):
|
||||
|
||||
self._warnings = self._WarningManager(self._player, self.userlist, self.ui)
|
||||
if constants.LIST_RELATIVE_CONFIGS and self._config.has_key('loadedRelativePaths') and self._config['loadedRelativePaths']:
|
||||
self.ui.showMessage(getMessage("relative-config-notification").format("; ".join(self._config['loadedRelativePaths'])), noPlayer=True, noTimestamp=True)
|
||||
paths = "; ".join(self._config['loadedRelativePaths'])
|
||||
self.ui.showMessage(getMessage("relative-config-notification").format(paths), noPlayer=True, noTimestamp=True)
|
||||
|
||||
if constants.DEBUG_MODE and constants.WARN_ABOUT_MISSING_STRINGS:
|
||||
missingStrings = getMissingStrings()
|
||||
if missingStrings is not None and missingStrings is not "":
|
||||
self.ui.showDebugMessage("MISSING/UNUSED STRINGS DETECTED:\n{}".format(missingStrings))
|
||||
|
||||
def initProtocol(self, protocol):
|
||||
self._protocol = protocol
|
||||
@ -177,6 +190,13 @@ class SyncplayClient(object):
|
||||
madeChangeOnPlayer = True
|
||||
return madeChangeOnPlayer
|
||||
|
||||
def _fastforwardPlayerDueToTimeDifference(self, position, setBy):
|
||||
hideFromOSD = not constants.SHOW_SAME_ROOM_OSD
|
||||
self.setPosition(position + constants.FASTFORWARD_EXTRA_TIME)
|
||||
self.ui.showMessage(getMessage("fastforward-notification").format(setBy), hideFromOSD)
|
||||
madeChangeOnPlayer = True
|
||||
return madeChangeOnPlayer
|
||||
|
||||
def _serverUnpaused(self, setBy):
|
||||
hideFromOSD = not constants.SHOW_SAME_ROOM_OSD
|
||||
self._player.setPaused(False)
|
||||
@ -223,7 +243,7 @@ class SyncplayClient(object):
|
||||
|
||||
def _changePlayerStateAccordingToGlobalState(self, position, paused, doSeek, setBy):
|
||||
madeChangeOnPlayer = False
|
||||
pauseChanged = paused != self.getGlobalPaused()
|
||||
pauseChanged = paused != self.getGlobalPaused() or paused != self.getPlayerPaused()
|
||||
diff = self.getPlayerPosition() - position
|
||||
if self._lastGlobalUpdate is None:
|
||||
madeChangeOnPlayer = self._initPlayerState(position, paused)
|
||||
@ -234,7 +254,9 @@ class SyncplayClient(object):
|
||||
madeChangeOnPlayer = self._serverSeeked(position, setBy)
|
||||
if diff > self._config['rewindThreshold'] and not doSeek and not self._config['rewindOnDesync'] == False:
|
||||
madeChangeOnPlayer = self._rewindPlayerDueToTimeDifference(position, setBy)
|
||||
if (self._player.speedSupported and not doSeek and not paused and not self._config['slowOnDesync'] == False):
|
||||
if diff < (self._config['fastforwardThreshold'] * -1) and not doSeek and not self._config['fastforwardOnDesync'] == False:
|
||||
madeChangeOnPlayer = self._fastforwardPlayerDueToTimeDifference(position, setBy)
|
||||
if self._player.speedSupported and not doSeek and not paused and not self._config['slowOnDesync'] == False:
|
||||
madeChangeOnPlayer = self._slowDownToCoverTimeDifference(diff, setBy)
|
||||
if paused == False and pauseChanged:
|
||||
madeChangeOnPlayer = self._serverUnpaused(setBy)
|
||||
@ -317,7 +339,6 @@ class SyncplayClient(object):
|
||||
size = os.path.getsize(path)
|
||||
except OSError: # file not accessible (stream?)
|
||||
size = 0
|
||||
rawfilename = filename
|
||||
filename, size = self.__executePrivacySettings(filename, size)
|
||||
self.userlist.currentUser.setFile(filename, duration, size)
|
||||
self.sendFile()
|
||||
@ -333,6 +354,9 @@ class SyncplayClient(object):
|
||||
size = 0
|
||||
return filename, size
|
||||
|
||||
def setServerVersion(self, version):
|
||||
self.serverVersion = version
|
||||
|
||||
def sendFile(self):
|
||||
file_ = self.userlist.currentUser.file
|
||||
if self._protocol and self._protocol.logged and file_:
|
||||
@ -358,6 +382,17 @@ class SyncplayClient(object):
|
||||
if self._protocol and self._protocol.logged and room:
|
||||
self._protocol.sendRoomSetting(room)
|
||||
self.getUserList()
|
||||
self.reIdentifyAsController()
|
||||
|
||||
def reIdentifyAsController(self):
|
||||
room = self.userlist.currentUser.room
|
||||
if utils.RoomPasswordProvider.isControlledRoom(room):
|
||||
storedRoomPassword = self.getControlledRoomPassword(room)
|
||||
if storedRoomPassword:
|
||||
self.identifyAsController(storedRoomPassword)
|
||||
|
||||
def connected(self):
|
||||
self.reIdentifyAsController()
|
||||
|
||||
def getRoom(self):
|
||||
return self.userlist.currentUser.room
|
||||
@ -414,6 +449,64 @@ class SyncplayClient(object):
|
||||
if promptForAction:
|
||||
self.ui.promptFor(getMessage("enter-to-exit-prompt"))
|
||||
|
||||
def requireMinServerVersion(minVersion):
|
||||
def requireMinVersionDecorator(f):
|
||||
@wraps(f)
|
||||
def wrapper(self, *args, **kwds):
|
||||
if not utils.meetsMinVersion(self.serverVersion,minVersion):
|
||||
self.ui.showErrorMessage(u"This feature is not supported by the server. The feature requires a server running Syncplay {}+, but the server is running Syncplay {}.".format(minVersion, self.serverVersion))
|
||||
return
|
||||
return f(self, *args, **kwds)
|
||||
return wrapper
|
||||
return requireMinVersionDecorator
|
||||
|
||||
@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.setRoom(roomName)
|
||||
self.sendRoom()
|
||||
self._protocol.requestControlledRoom(roomName, controlPassword)
|
||||
self.ui.updateRoomName(roomName)
|
||||
|
||||
def stripControlPassword(self, controlPassword):
|
||||
if controlPassword:
|
||||
return re.sub(constants.CONTROL_PASSWORD_STRIP_REGEX, "", controlPassword).upper()
|
||||
else:
|
||||
return ""
|
||||
|
||||
@requireMinServerVersion(constants.CONTROLLED_ROOMS_MIN_VERSION)
|
||||
def identifyAsController(self, controlPassword):
|
||||
controlPassword = self.stripControlPassword(controlPassword)
|
||||
self.ui.showMessage(getMessage("identifying-as-controller-notification").format(controlPassword))
|
||||
self.lastControlPasswordAttempt = controlPassword
|
||||
self._protocol.requestControlledRoom(self.getRoom(), controlPassword)
|
||||
|
||||
def controllerIdentificationError(self, username, room):
|
||||
self.ui.showErrorMessage(getMessage("failed-to-identify-as-controller-notification").format(username))
|
||||
|
||||
def controllerIdentificationSuccess(self, username, roomname):
|
||||
self.userlist.setUserAsController(username)
|
||||
if self.userlist.isRoomSame(roomname):
|
||||
hideFromOSD = not constants.SHOW_SAME_ROOM_OSD
|
||||
self.ui.showMessage(getMessage("authenticated-as-controller-notification").format(username), hideFromOSD)
|
||||
if username == self.userlist.currentUser.username:
|
||||
self.storeControlPassword(roomname, self.lastControlPasswordAttempt)
|
||||
self.ui.userListChange()
|
||||
|
||||
def storeControlPassword(self, room, password):
|
||||
if password:
|
||||
self.controlpasswords[room] = password
|
||||
|
||||
def getControlledRoomPassword(self, room):
|
||||
if self.controlpasswords.has_key(room):
|
||||
return self.controlpasswords[room]
|
||||
|
||||
class _WarningManager(object):
|
||||
def __init__(self, player, userlist, ui):
|
||||
self._player = player
|
||||
@ -466,6 +559,7 @@ class SyncplayUser(object):
|
||||
self.username = username
|
||||
self.room = room
|
||||
self.file = file_
|
||||
self._controller = False
|
||||
|
||||
def setFile(self, filename, duration, size):
|
||||
file_ = {
|
||||
@ -484,7 +578,10 @@ class SyncplayUser(object):
|
||||
return sameName and sameSize and sameDuration
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.username.lower() < other.username.lower()
|
||||
if self.isController() == other.isController():
|
||||
return self.username.lower() < other.username.lower()
|
||||
else:
|
||||
return self.isController() > other.isController()
|
||||
|
||||
def __repr__(self, *args, **kwargs):
|
||||
if self.file:
|
||||
@ -492,6 +589,16 @@ class SyncplayUser(object):
|
||||
else:
|
||||
return "{}".format(self.username)
|
||||
|
||||
def setControllerStatus(self, isController):
|
||||
self._controller = isController
|
||||
|
||||
def isController(self):
|
||||
return self._controller
|
||||
|
||||
def canControl(self):
|
||||
if self.isController() or not utils.RoomPasswordProvider.isControlledRoom(self.room):
|
||||
return True
|
||||
|
||||
class SyncplayUserlist(object):
|
||||
def __init__(self, ui, client):
|
||||
self.currentUser = SyncplayUser()
|
||||
@ -512,40 +619,46 @@ class SyncplayUserlist(object):
|
||||
showOnOSD = constants.SHOW_OSD_WARNINGS
|
||||
else:
|
||||
showOnOSD = constants.SHOW_DIFFERENT_ROOM_OSD
|
||||
if constants.SHOW_NONCONTROLLER_OSD == False and self.canControl(username) == False:
|
||||
showOnOSD = False
|
||||
hideFromOSD = not showOnOSD
|
||||
if room and not file_:
|
||||
message = getMessage("room-join-notification").format(username, room)
|
||||
self.ui.showMessage(message, hideFromOSD)
|
||||
elif room and file_:
|
||||
duration = utils.formatTime(file_['duration'])
|
||||
message = getMessage("playing-notification").format(username, file_['name'], duration)
|
||||
if self.currentUser.room <> room or self.currentUser.username == username:
|
||||
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, not constants.SHOW_OSD_WARNINGS)
|
||||
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, not constants.SHOW_OSD_WARNINGS)
|
||||
if not file_:
|
||||
message = getMessage("room-join-notification").format(username, room)
|
||||
self.ui.showMessage(message, hideFromOSD)
|
||||
else:
|
||||
duration = utils.formatTime(file_['duration'])
|
||||
message = getMessage("playing-notification").format(username, file_['name'], duration)
|
||||
if self.currentUser.room <> room or self.currentUser.username == username:
|
||||
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)
|
||||
|
||||
def addUser(self, username, room, file_, noMessage=False):
|
||||
def addUser(self, username, room, file_, noMessage=False, isController=None):
|
||||
if username == self.currentUser.username:
|
||||
if isController is not None:
|
||||
self.currentUser.setControllerStatus(isController)
|
||||
return
|
||||
user = SyncplayUser(username, room, file_)
|
||||
if isController is not None:
|
||||
user.setControllerStatus(isController)
|
||||
self._users[username] = user
|
||||
if not noMessage:
|
||||
self.__showUserChangeMessage(username, room, file_)
|
||||
self.userListChange()
|
||||
self.userListChange(room)
|
||||
|
||||
def removeUser(self, username):
|
||||
hideFromOSD = not constants.SHOW_DIFFERENT_ROOM_OSD
|
||||
@ -572,6 +685,8 @@ class SyncplayUserlist(object):
|
||||
if self._users.has_key(username):
|
||||
user = self._users[username]
|
||||
oldRoom = user.room if user.room else None
|
||||
if user.room != room:
|
||||
user.setControllerStatus(isController=False)
|
||||
self.__displayModUserMessage(username, room, file_, user, oldRoom)
|
||||
user.room = room
|
||||
if file_:
|
||||
@ -580,12 +695,20 @@ class SyncplayUserlist(object):
|
||||
self.__showUserChangeMessage(username, room, file_)
|
||||
else:
|
||||
self.addUser(username, room, file_)
|
||||
self.userListChange()
|
||||
self.userListChange(room)
|
||||
|
||||
def setUserAsController(self, username):
|
||||
if self.currentUser.username == username:
|
||||
self.currentUser.setControllerStatus(True)
|
||||
elif self._users.has_key(username):
|
||||
user = self._users[username]
|
||||
user.setControllerStatus(True)
|
||||
|
||||
def areAllFilesInRoomSame(self):
|
||||
for user in self._users.itervalues():
|
||||
if user.room == self.currentUser.room and user.file and not self.currentUser.isFileSame(user.file):
|
||||
return False
|
||||
if user.canControl():
|
||||
return False
|
||||
return True
|
||||
|
||||
def areYouAloneInRoom(self):
|
||||
@ -600,8 +723,18 @@ class SyncplayUserlist(object):
|
||||
return True
|
||||
return False
|
||||
|
||||
def userListChange(self):
|
||||
self._roomUsersChanged = True
|
||||
def canControl(self, username):
|
||||
if self.currentUser.username == username and self.currentUser.canControl():
|
||||
return True
|
||||
|
||||
for user in self._users.itervalues():
|
||||
if user.username == username and user.canControl():
|
||||
return True
|
||||
return False
|
||||
|
||||
def userListChange(self, room = None):
|
||||
if room is not None and self.isRoomSame(room):
|
||||
self._roomUsersChanged = True
|
||||
self.ui.userListChange()
|
||||
|
||||
def roomStateConfirmed(self):
|
||||
@ -637,6 +770,10 @@ class UiManager(object):
|
||||
self.__ui = ui
|
||||
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)
|
||||
self.__ui.showMessage(message, noTimestamp)
|
||||
@ -648,6 +785,9 @@ class UiManager(object):
|
||||
if constants.SHOW_OSD and self._client._player:
|
||||
self._client._player.displayMessage(message, duration * 1000)
|
||||
|
||||
def setControllerStatus(self, username, isController):
|
||||
self.__ui.setControllerStatus(username, isController)
|
||||
|
||||
def showErrorMessage(self, message, criticalerror=False):
|
||||
if message <> self.lastError: # Avoid double call bug
|
||||
self.lastError = message
|
||||
@ -662,5 +802,10 @@ class UiManager(object):
|
||||
def markEndOfUserlist(self):
|
||||
self.__ui.markEndOfUserlist()
|
||||
|
||||
def updateRoomName(self, room=""):
|
||||
self.__ui.updateRoomName(room)
|
||||
|
||||
def drop(self):
|
||||
self.__ui.drop()
|
||||
|
||||
|
||||
|
||||
@ -14,18 +14,24 @@ 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)
|
||||
|
||||
#Overriden by config
|
||||
SHOW_OSD = True # Sends Syncplay messages to media player OSD
|
||||
SHOW_OSD_WARNINGS = True # Show warnings if playing different file, alone in room
|
||||
SHOW_SLOWDOWN_OSD = True # Show notifications of slowing down / reverting on time difference
|
||||
SHOW_SAME_ROOM_OSD = True # Show OSD notifications for events relating to room user is in
|
||||
SHOW_NONCONTROLLER_OSD = False # Show OSD notifications for non-controllers in controlled rooms
|
||||
SHOW_DIFFERENT_ROOM_OSD = False # Show OSD notifications for events relating to room user is not in
|
||||
SHOW_DURATION_NOTIFICATION = True
|
||||
DEBUG_MODE = False
|
||||
|
||||
#Changing these might be ok
|
||||
DEFAULT_REWIND_THRESHOLD = 4
|
||||
MINIMUM_REWIND_THRESHOLD = 3
|
||||
DEFAULT_FASTFORWARD_THRESHOLD = 5
|
||||
MINIMUM_FASTFORWARD_THRESHOLD = 2
|
||||
FASTFORWARD_EXTRA_TIME = 0.25
|
||||
SEEK_THRESHOLD = 1
|
||||
SLOWDOWN_RATE = 0.95
|
||||
DEFAULT_SLOWDOWN_KICKIN_THRESHOLD = 1.5
|
||||
@ -40,34 +46,39 @@ 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
|
||||
FILENAME_STRIP_REGEX = u"[-~_\.\[\](): ]"
|
||||
CONTROL_PASSWORD_STRIP_REGEX = u"[^a-zA-Z0-9\-]"
|
||||
ROOM_NAME_STRIP_REGEX = u"^(\+)(?P<roomnamebase>.*)(:)(\w{12})$"
|
||||
COMMANDS_UNDO = ["u", "undo", "revert"]
|
||||
COMMANDS_LIST = ["l", "list", "users"]
|
||||
COMMANDS_PAUSE = ["p", "play", "pause"]
|
||||
COMMANDS_ROOM = ["r", "room"]
|
||||
COMMANDS_HELP = ['help', 'h', '?', '/?', r'\?']
|
||||
COMMANDS_CREATE = ['c','create']
|
||||
COMMANDS_AUTH = ['a','auth']
|
||||
MPC_MIN_VER = "1.6.4"
|
||||
VLC_MIN_VERSION = "2.0.0"
|
||||
VLC_INTERFACE_MIN_VERSION = "0.2.1"
|
||||
CONTROLLED_ROOMS_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",
|
||||
r"C:\Program Files\MPC-HC\mpc-hc64.exe",
|
||||
r"C:\Program Files\Media Player Classic - Home Cinema\mpc-hc.exe",
|
||||
r"C:\Program Files\Media Player Classic - Home Cinema\mpc-hc64.exe",
|
||||
r"C:\Program Files (x86)\Media Player Classic - Home Cinema\mpc-hc.exe",
|
||||
r"C:\Program Files (x86)\K-Lite Codec Pack\Media Player Classic\mpc-hc.exe",
|
||||
r"C:\Program Files\K-Lite Codec Pack\Media Player Classic\mpc-hc.exe",
|
||||
r"C:\Program Files (x86)\Combined Community Codec Pack\MPC\mpc-hc.exe",
|
||||
r"C:\Program Files\Combined Community Codec Pack\MPC\mpc-hc.exe",
|
||||
r"C:\Program Files\MPC HomeCinema (x64)\mpc-hc64.exe",
|
||||
r"c:\program files (x86)\mpc-hc\mpc-hc.exe",
|
||||
r"c:\program files\mpc-hc\mpc-hc.exe",
|
||||
r"c:\program files\mpc-hc\mpc-hc64.exe",
|
||||
r"c:\program files\media player classic - home cinema\mpc-hc.exe",
|
||||
r"c:\program files\media player classic - home cinema\mpc-hc64.exe",
|
||||
r"c:\program files (x86)\media player classic - home cinema\mpc-hc.exe",
|
||||
r"c:\program files (x86)\k-lite codec pack\media player classic\mpc-hc.exe",
|
||||
r"c:\program files\k-lite codec pack\media Player classic\mpc-hc.exe",
|
||||
r"c:\program files (x86)\combined community codec pack\mpc\mpc-hc.exe",
|
||||
r"c:\program files\combined community codec pack\mpc\mpc-hc.exe",
|
||||
r"c:\program files\mpc homecinema (x64)\mpc-hc64.exe",
|
||||
]
|
||||
MPLAYER_PATHS = ["mplayer2", "mplayer"]
|
||||
MPV_PATHS = ["mpv", "/opt/mpv/mpv", r"C:\Program Files\mpv\mpv.exe", r"C:\Program Files\mpv-player\mpv.exe",
|
||||
r"C:\Program Files (x86)\mpv\mpv.exe", r"C:\Program Files (x86)\mpv-player\mpv.exe",
|
||||
MPV_PATHS = ["mpv", "/opt/mpv/mpv", r"c:\program files\mpv\mpv.exe", r"c:\program files\mpv-player\mpv.exe",
|
||||
r"c:\program Files (x86)\mpv\mpv.exe", r"c:\program Files (x86)\mpv-player\mpv.exe",
|
||||
"/Applications/mpv.app/Contents/MacOS/mpv"]
|
||||
VLC_PATHS = [
|
||||
r"C:\Program Files (x86)\VideoLAN\VLC\vlc.exe",
|
||||
r"C:\Program Files\VideoLAN\VLC\vlc.exe",
|
||||
r"c:\program files (x86)\videolan\vlc\vlc.exe",
|
||||
r"c:\program files\videolan\vlc\vlc.exe",
|
||||
"/usr/bin/vlc",
|
||||
"/usr/bin/vlc-wrapper",
|
||||
"/Applications/VLC.app/Contents/MacOS/VLC",
|
||||
@ -89,14 +100,16 @@ MPC_LOCK_WAIT_TIME = 0.2
|
||||
MPC_RETRY_WAIT_TIME = 0.01
|
||||
MPC_MAX_RETRIES = 30
|
||||
MPC_PAUSE_TOGGLE_DELAY = 0.05
|
||||
MPV_NEWFILE_IGNORE_TIME = 1
|
||||
MPV_LOCK_WAIT_TIME = 0.2
|
||||
VLC_OPEN_MAX_WAIT_TIME = 15
|
||||
VLC_MIN_PORT = 10000
|
||||
VLC_MAX_PORT = 55000
|
||||
|
||||
#These are not changes you're looking for
|
||||
STYLE_TABLIST = "QListWidget::item { color: black; border-style: solid; border-width: 1px; border-radius: 2px; } QListWidget::item:selected { background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(242, 248, 255, 255), stop:1 rgba(208, 229, 255, 255)); border-color: #84ACDD; } QListWidget::item:!selected { border-color: transparent; } QListWidget::item:!selected:hover { background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(248, 248, 248, 255), stop:1 rgba(229, 229, 229, 255)); border-color: silver; }"
|
||||
STYLE_SUBCHECKBOX = "QCheckBox, QLabel {{ margin-left: 8px; padding-left: 18px; background:url('{}') left no-repeat }}" #Graphic path
|
||||
STYLE_SUBLABEL = "QCheckBox, QLabel {{ margin-left: 8px; padding-left: 14px; background:url('{}') left no-repeat }}" #Graphic path
|
||||
STYLE_SUBCHECKBOX = "QCheckBox, QLabel {{ margin-left: 6px; padding-left: 21px; background:url('{}') left no-repeat }}" #Graphic path
|
||||
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_NOTIFICATIONBOX = "Username { color: #367AA9; font-weight:bold; }"
|
||||
@ -104,12 +117,19 @@ STYLE_USERNAME = "color: #367AA9; font-weight:bold;"
|
||||
STYLE_ERRORNOTIFICATION = "color: red;"
|
||||
STYLE_DIFFERENTITEM_COLOR = 'red'
|
||||
STYLE_NOFILEITEM_COLOR = 'blue'
|
||||
STYLE_NOTCONTROLLER_COLOR = 'grey'
|
||||
|
||||
MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4']
|
||||
USERLIST_GUI_USERNAME_OFFSET = 21 # Pixels
|
||||
|
||||
MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4', '-af', 'scaletempo']
|
||||
# --quiet works with both mpv 0.2 and 0.3
|
||||
MPV_SLAVE_ARGS = ['--slave-broken', '--hr-seek=always', '--quiet', '--keep-open']
|
||||
MPV_SLAVE_ARGS = ['--hr-seek=always', '--quiet', '--keep-open', '-af', 'scaletempo']
|
||||
MPV_SLAVE_ARGS_WINDOWS = ['--slave-broken']
|
||||
MPV_SLAVE_ARGS_NONWINDOWS = ['--input-terminal=no','--input-file=/dev/stdin']
|
||||
MPV_SLAVE_ARGS_NEW = ['--term-playing-msg=<SyncplayUpdateFile>\nANS_filename=${filename}\nANS_length=${=length}\nANS_path=${path}\n</SyncplayUpdateFile>']
|
||||
MPV_NEW_VERSION = False
|
||||
VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek',
|
||||
'--play-and-pause']
|
||||
'--play-and-pause', '--start-time=0']
|
||||
VLC_SLAVE_NONOSX_ARGS = ['--no-one-instance', '--no-one-instance-when-started-from-file']
|
||||
MPLAYER_ANSWER_REGEX = "^ANS_([a-zA-Z_-]+)=(.+)$|^(Exiting)\.\.\. \((.+)\)$"
|
||||
VLC_ANSWER_REGEX = r"(?:^(?P<command>[a-zA-Z_]+)(?:\: )?(?P<argument>.*))"
|
||||
@ -126,4 +146,6 @@ INVERTED_STATE_MARKER = "*"
|
||||
ERROR_MESSAGE_MARKER = "*"
|
||||
LOAD_SAVE_MANUALLY_MARKER = "!"
|
||||
CONFIG_NAME_MARKER = ":"
|
||||
CONFIG_VALUE_MARKER = "="
|
||||
CONFIG_VALUE_MARKER = "="
|
||||
USERITEM_CONTROLLER_ROLE = 0
|
||||
USERITEM_READY_ROLE = 1
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
from syncplay import constants
|
||||
|
||||
en = {
|
||||
|
||||
"LANGUAGE" : "English",
|
||||
|
||||
# Client notifications
|
||||
"config-cleared-notification" : "Settings cleared. Changes will be saved when you store a valid configuration.",
|
||||
|
||||
@ -16,6 +17,7 @@ en = {
|
||||
"retrying-notification" : "%s, Retrying in %d seconds...", # Seconds
|
||||
|
||||
"rewind-notification" : "Rewinded due to time difference with <{}>", # User
|
||||
"fastforward-notification" : "Fast-forwarded due to time difference with <{}>", # User
|
||||
"slowdown-notification" : "Slowing down due to time difference with <{}>", # User
|
||||
"revert-notification" : "Reverting speed back to normal",
|
||||
|
||||
@ -31,8 +33,12 @@ en = {
|
||||
"playing-notification" : "<{}> is playing '{}' ({})", # User, file, duration
|
||||
"playing-notification/room-addendum" : " 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",
|
||||
|
||||
"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): ",
|
||||
"file-differences-notification" : "Your file differs in the following way(s): ", # controlPassword
|
||||
"room-files-not-same" : "Not all files played in the room are the same",
|
||||
"alone-in-the-room": "You're alone in the room",
|
||||
|
||||
@ -53,10 +59,13 @@ 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",
|
||||
"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.",
|
||||
|
||||
"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
|
||||
@ -71,6 +80,7 @@ en = {
|
||||
"server-timeout-error" : "Connection with server timed out",
|
||||
"mpc-slave-error" : "Unable to start MPC in slave mode!",
|
||||
"mpc-version-insufficient-error" : "MPC version not sufficient, please use `mpc-hc` >= `{}`",
|
||||
"mpv-version-error" : "Syncplay is not compatible with this version of mpv. Please use a different version of mpv (e.g. Git HEAD).",
|
||||
"player-file-open-error" : "Player failed opening file",
|
||||
"player-path-error" : "Player path is not set properly",
|
||||
"hostname-empty-error" : "Hostname can't be empty",
|
||||
@ -82,6 +92,12 @@ en = {
|
||||
|
||||
"unable-to-start-client-error" : "Unable to start client",
|
||||
|
||||
"player-path-config-error": "Player path is not set properly",
|
||||
"no-file-path-config-error" :"File must be selected before starting your player",
|
||||
"no-hostname-config-error": "Hostname can't be empty",
|
||||
"invalid-port-config-error" : "Port must be valid",
|
||||
"empty-value-config-error" : "{} can't be empty", # Config option
|
||||
|
||||
"not-json-error" : "Not a json encoded string\n",
|
||||
"hello-arguments-error" : "Not enough Hello arguments\n",
|
||||
"version-mismatch-error" : "Mismatch between versions of client and server\n",
|
||||
@ -105,7 +121,7 @@ en = {
|
||||
"file-argument" : 'file to play',
|
||||
"args-argument" : 'player options, if you need to pass options starting with - prepend them with single \'--\' argument',
|
||||
"clear-gui-data-argument" : 'resets path and window state GUI data stored as QSettings',
|
||||
"language-argument" :'language for Syncplay messages (en/pl)',
|
||||
"language-argument" :'language for Syncplay messages (de/en/pl/ru)',
|
||||
|
||||
"version-argument" : 'prints your version',
|
||||
"version-message" : "You're using Syncplay version {} ({})",
|
||||
@ -127,6 +143,7 @@ en = {
|
||||
"more-title" : "Show more settings",
|
||||
"slowdown-threshold-label" : "Slow down threshold:",
|
||||
"rewind-threshold-label" : "Rewind threshold:",
|
||||
"fastforward-threshold-label" : "Fast-forward threshold:",
|
||||
"never-rewind-value" : "Never",
|
||||
"seconds-suffix" : " secs",
|
||||
"privacy-sendraw-option" : "Send raw",
|
||||
@ -135,7 +152,7 @@ en = {
|
||||
"filename-privacy-label" : "Filename information:",
|
||||
"filesize-privacy-label" : "File size information:",
|
||||
"slowondesync-label" : "Slow down on minor desync (not supported on MPC-HC)",
|
||||
"dontslowdownwithme-label" : "Never slow down or rewind others",
|
||||
"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)
|
||||
"nostore-label" : "Don't store this configuration", # (Inverted)
|
||||
@ -143,16 +160,21 @@ en = {
|
||||
|
||||
"showosdwarnings-label" : "Include warnings (e.g. when files are different)",
|
||||
"showsameroomosd-label" : "Include events in your room",
|
||||
"shownoncontrollerosd-label" : "Include events from non-controllers in controlled rooms",
|
||||
"showdifferentroomosd-label" : "Include events in other rooms",
|
||||
"showslowdownosd-label" :"Include slowing down / reverting notification",
|
||||
"showcontactinfo-label" : "Show contact info box",
|
||||
"showbuttonlabels-label" : "Show labels on buttons",
|
||||
"showtooltips-label" : "Show tooltips",
|
||||
"language-label" : "Language",
|
||||
"showdurationnotification-label" : "Warn about media duration mismatches",
|
||||
"basics-label" : "Basics",
|
||||
"sync-label" : "Sync",
|
||||
"sync-lagging-title" : "If others are lagging behind...",
|
||||
"sync-other-title" : "Other sync options",
|
||||
"messages-label" : "Messages",
|
||||
"messages-osd-title" : "On-screen Display settings",
|
||||
"messages-other-title" : "Other display settings",
|
||||
"privacy-label" : "Privacy",
|
||||
"privacy-title" : "Privacy settings",
|
||||
|
||||
"help-label" : "Help",
|
||||
"reset-label" : "Restore defaults",
|
||||
@ -166,10 +188,12 @@ en = {
|
||||
"undoseek-guibuttonlabel" : "Undo seek",
|
||||
"togglepause-guibuttonlabel" : "Toggle pause",
|
||||
"play-guibuttonlabel" : "Play",
|
||||
"pause-guibuttonlabel" : "Play",
|
||||
"pause-guibuttonlabel" : "Pause",
|
||||
|
||||
"roomuser-heading-label" : "Room / User",
|
||||
"fileplayed-heading-label" : "File being played",
|
||||
"size-heading-label" : "Size",
|
||||
"duration-heading-label" : "Length",
|
||||
"filename-heading-label" : "Filename",
|
||||
"notifications-heading-label" : "Notifications",
|
||||
"userlist-heading-label" : "List of who is playing what",
|
||||
"othercommands-heading-label" : "Other commands",
|
||||
@ -183,12 +207,24 @@ en = {
|
||||
"exit-menu-label" : "E&xit",
|
||||
"advanced-menu-label" : "&Advanced",
|
||||
"setoffset-menu-label" : "Set &offset",
|
||||
"createcontrolledroom-menu-label" : "&Create controlled room",
|
||||
"identifyascontroller-menu-label" : "&Identify as room controller",
|
||||
|
||||
"help-menu-label" : "&Help",
|
||||
"userguide-menu-label" : "Open user &guide",
|
||||
|
||||
"setoffset-msgbox-label" : "Set offset",
|
||||
"offsetinfo-msgbox-label" : "Offset (see http://syncplay.pl/guide/ for usage instructions):",
|
||||
|
||||
|
||||
"createcontrolledroom-msgbox-label" : "Create controlled room",
|
||||
"controlledroominfo-msgbox-label" : "Enter name of controlled room\r\n(see http://syncplay.pl/guide/ for usage instructions):",
|
||||
|
||||
"identifyascontroller-msgbox-label" : "Identify as Room Controller",
|
||||
"identifyinfo-msgbox-label" : "Enter controller password for this room\r\n(see http://syncplay.pl/guide/ for usage instructions):",
|
||||
|
||||
"megabyte-suffix" : " MB",
|
||||
|
||||
# Tooltips
|
||||
|
||||
"host-tooltip" : "Hostname or IP to connect to, optionally including port (e.g. syncplay.pl:8999). Only synchronised with people on same server/port.",
|
||||
@ -202,27 +238,30 @@ 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),
|
||||
"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.",
|
||||
"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.",
|
||||
"dontslowdownwithme-tooltip" : "Means others do not get slowed down or rewinded if your playback is lagging. Useful for room controllers.",
|
||||
"pauseonleave-tooltip" : "Pause playback if you get disconnected or someone leaves from your room.",
|
||||
"rewindondesync-label" : "Rewind on major desync (highly recommended)",
|
||||
"fastforwardondesync-label" : "Fast-forward if lagging behind (recommended)",
|
||||
"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).",
|
||||
"showosd-tooltip" : "Sends Syncplay messages to media player OSD.",
|
||||
"showosdwarnings-tooltip" : "Show warnings if playing different file, alone in room.",
|
||||
"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 controllerd 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.",
|
||||
"showbuttonlabels-tooltip" : "Show the text alongside the icons for buttons in the main UI.",
|
||||
"showtooltips-tooltip" : "Show tooltip help messages when you mouseover an input element in Syncplay.",
|
||||
"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.",
|
||||
@ -247,6 +286,7 @@ en = {
|
||||
"welcome-server-notification" : "Welcome to Syncplay server, ver. {0}", # version
|
||||
"client-connected-room-server-notification" : "{0}({2}) connected to room '{1}'", # username, host, room
|
||||
"client-left-server-notification" : "{0} left server", # name
|
||||
"no-salt-notification" : "PLEASE NOTE: To allow room control passwords generated by this server instance to still work when the server is restarted, please add the following command line argument when running the Syncplay server in the future: --salt {}", #Salt
|
||||
|
||||
|
||||
# Server arguments
|
||||
@ -255,11 +295,10 @@ 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-motd-argument": "path to file from which motd will be fetched",
|
||||
"server-messed-up-motd-unescaped-placeholders": "Message of the Day has unescaped placeholders. All $ signs should be doubled ($$).",
|
||||
"server-messed-up-motd-too-long": "Message of the Day is too long - maximum of {} chars, {} given.",
|
||||
"server-irc-verbose": "Should server actively report changes in rooms",
|
||||
"server-irc-config": "Path to irc bot config files",
|
||||
|
||||
# Server errors
|
||||
"unknown-command-server-error" : "Unknown command {}", # message
|
||||
@ -269,13 +308,12 @@ en = {
|
||||
"password-required-server-error" : "Password required",
|
||||
"wrong-password-server-error" : "Wrong password supplied",
|
||||
"hello-server-error" : "Not enough Hello arguments",
|
||||
"version-mismatch-server-error" : "Mismatch between versions of client and server"
|
||||
|
||||
|
||||
}
|
||||
|
||||
pl = {
|
||||
|
||||
"LANGUAGE" : "Polski", # (Polish)
|
||||
|
||||
# Client notifications
|
||||
"connection-attempt-notification" : u"Próba połączenia z {}:{}", # Port, IP
|
||||
"reconnection-attempt-notification" : u"Połączenie z serwerem zostało przerwane, ponowne łączenie",
|
||||
@ -311,14 +349,604 @@ pl = {
|
||||
"server-timeout-error" : u"Przekroczono czas oczekiwania na odpowiedź serwera"
|
||||
}
|
||||
|
||||
ru = {
|
||||
"LANGUAGE" : u"Русский", # (Russian)
|
||||
|
||||
# Client notifications
|
||||
"config-cleared-notification" : u"Настройки сброшены. Изменения вступят в силу при сохранении корректной конфигурации.",
|
||||
|
||||
"relative-config-notification" : u"Загружен(ы) файл(ы) относительной конфигурации: {}",
|
||||
|
||||
"connection-attempt-notification" : u"Подключение к {}:{}", # Port, IP
|
||||
"reconnection-attempt-notification" : u"Соединение с сервером потеряно, переподключение",
|
||||
"disconnection-notification" : u"Отключились от сервера",
|
||||
"connection-failed-notification" : u"Не удалось подключиться к серверу",
|
||||
"connected-successful-notification" : u"Соединение с сервером установлено",
|
||||
"retrying-notification" : u"%s, следующая попытка через %d секунд(ы)...", # Seconds
|
||||
|
||||
"rewind-notification" : u"Перемотано из-за разницы во времени с <{}>", # User
|
||||
"slowdown-notification" : u"Воспроизведение замедлено из-за разницы во времени с <{}>", # User
|
||||
"revert-notification" : u"Возвращаемся к нормальной скорости воспроизведения",
|
||||
|
||||
"pause-notification" : u"<{}> приостановил(а) воспроизведение", # User
|
||||
"unpause-notification" : u"<{}> возобновил(а) воспроизведение", # User
|
||||
"seek-notification" : u"<{}> перемотал с {} на {}", # User, from time, to time
|
||||
|
||||
"current-offset-notification" : u"Текущее смещение: {} секунд(ы)", # Offset
|
||||
|
||||
"room-join-notification" : u"<{}> зашел(зашла) в комнату: '{}'", # User
|
||||
"left-notification" : u"<{}> покинул(а) комнату", # User
|
||||
"left-paused-notification" : u"<{}> покинул(а) комнату, <{}> приостановил(а) воспроизведение", # User who left, User who paused
|
||||
"playing-notification" : u"<{}> включил '{}' ({})", # User, file, duration
|
||||
"playing-notification/room-addendum" : u" в комнате: '{}'", # Room
|
||||
|
||||
"file-different-notification" : u"Вероятно, файл, который Вы смотрите, отличается от того, который смотрит <{}>.", # User
|
||||
"file-differences-notification" : u"Ваш файл отличается: ",
|
||||
"room-files-not-same" : u"Не все пользователи в этой комнате смотрят один и тот же файл.",
|
||||
"alone-in-the-room" : u"В этой комнате кроме Вас никого нет.",
|
||||
|
||||
"different-filesize-notification" : u" (размер Вашего файла не совпадает с размером их файла!)",
|
||||
"file-played-by-notification" : u"Файл: {} просматривают:", # File
|
||||
"notplaying-notification" : u"Люди, которые не смотрят ничего:",
|
||||
"userlist-room-notification" : u"В комнате '{}':", # Room
|
||||
|
||||
"mplayer-file-required-notification" : u"Для использования Syncplay с mplayer необходимо передать файл в качестве параметра",
|
||||
"mplayer-file-required-notification/example" : u"Пример использования: syncplay [options] [url|path/]filename",
|
||||
"mplayer2-required" : u"Syncplay не совместим с MPlayer 1.x, пожалуйста, используйте mplayer2 или mpv",
|
||||
|
||||
"unrecognized-command-notification" : u"Неизвестная команда.",
|
||||
"commandlist-notification" : u"Доступные команды:",
|
||||
"commandlist-notification/room" : u"\tr [name] - сменить комнату",
|
||||
"commandlist-notification/list" : u"\tl - показать список пользователей",
|
||||
"commandlist-notification/undo" : u"\tu - отменить последнюю перемотку",
|
||||
"commandlist-notification/pause" : u"\tp - вкл./выкл. паузу",
|
||||
"commandlist-notification/seek" : u"\t[s][+-]time - перемотать к заданному моменту времени, если не указан + или -, то время считается абсолютным (от начала файла) в секундах или мин:сек",
|
||||
"commandlist-notification/help" : u"\th - помощь",
|
||||
"syncplay-version-notification" : u"Версия Syncplay: {}", # syncplay.version
|
||||
"more-info-notification" : u"Больше информации на {}", # projectURL
|
||||
|
||||
"gui-data-cleared-notification" : u"Syncplay очистил путь и информацию о состоянии окна, использованного GUI.",
|
||||
|
||||
"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
|
||||
"vlc-interface-oldversion-ignored" : u"Внимание: Syncplay обнаружил, что старая версия модуля интерфейса Syncplay для VLC уже установлена в директорию VLC. По существу, если Вы используете VLC 2.0, то предпочтение будет отдано файлу syncplay.lua, содержащемуся в директории Syncplay, но в таком случае другие пользовательские скрипты и расширения интерфейса не будут работать. Пожалуйста, обратитесь к Руководству Пользователя Syncplay (http://syncplay.pl/guide/) за инструкциями о том, как установить syncplay.lua.",
|
||||
"vlc-interface-not-installed" : u"Внимание: Модуль интерфейса Syncplay для VLC не обнаружен в директории VLC. По существу, если Вы используете VLC 2.0, то VLC будет использовать модуль syncplay.lua из директории Syncplay, но в таком случае другие пользовательские скрипты и расширения интерфейса не будут работать. Пожалуйста, обратитесь к Руководству Пользователя Syncplay (http://syncplay.pl/guide/) за инструкциями о том, как установить syncplay.lua.",
|
||||
|
||||
# Client prompts
|
||||
"enter-to-exit-prompt" : u"Для выхода нажмите Enter\n",
|
||||
|
||||
# Client errors
|
||||
"missing-arguments-error" : u"Некоторые необходимые аргументы отсутствуют, обратитесь к --help",
|
||||
"server-timeout-error" : u"Подключение к серверу превысило лимит времени",
|
||||
"mpc-slave-error" : u"Невозможно запустить MPC в slave режиме!",
|
||||
"mpc-version-insufficient-error" : u"Версия MPC слишком старая, пожалуйста, используйте `mpc-hc` >= `{}`",
|
||||
"mpv-version-error" : u"Syncplay не совместим с данной версией mpv. Пожалуйста, используйте другую версию mpv (лучше свежайшую).",
|
||||
"player-file-open-error" : u"Проигрыватель не может открыть файл.",
|
||||
"player-path-error" : u"Путь к проигрывателю задан неверно.",
|
||||
"hostname-empty-error" : u"Имя пользователя не может быть пустым.",
|
||||
"empty-error" : u"{} не может быть пустым.", # Configuration
|
||||
"media-player-error" : u"Ошибка Media player: \"{}\"", # Error line
|
||||
"unable-import-gui-error" : u"Невозможно импортировать библиотеки GUI (графического интерфейса). Необходимо установить PySide, иначе графический интерфейс не будет работать.",
|
||||
|
||||
"arguments-missing-error" : u"Некоторые необходимые аргументы отсутствуют, обратитесь к --help",
|
||||
|
||||
"unable-to-start-client-error" : u"Невозможно запустить клиент",
|
||||
|
||||
"not-json-error" : u"Не является закодированной json-строкой\n",
|
||||
"hello-arguments-error" : u"Не хватает аргументов Hello\n",
|
||||
"version-mismatch-error" : u"Конфликт версий между клиентом и сервером\n",
|
||||
"vlc-failed-connection" : u"Ошибка подключения к VLC. Если у Вас не установлен syncplay.lua, то обратитесь к http://syncplay.pl/LUA/ за инструкциями.",
|
||||
"vlc-failed-noscript" : u"VLC сообщает, что скрипт интерфейса syncplay.lua не установлен. Пожалуйста, обратитесь к http://syncplay.pl/LUA/ за инструкциями.",
|
||||
"vlc-failed-versioncheck" : u"Данная версия VLC не поддерживается Syncplay. Пожалуйста, используйте VLC версии 2 или выше.",
|
||||
"vlc-failed-other" : u"Во время загрузки скрипта интерфейса syncplay.lua в VLC произошла следующая ошибка: {}", # Syncplay Error
|
||||
|
||||
# Client arguments
|
||||
"argument-description" : u'Решение для синхронного воспроизведения в VLC, MPlayer или MPC-HC через Интернет.',
|
||||
"argument-epilog" : u'Если параметр не будет передан, то будет использоваться значение, указанное в _config.',
|
||||
"nogui-argument" : u'не использовать GUI',
|
||||
"host-argument" : u'адрес сервера',
|
||||
"name-argument" : u'желательное имя пользователя',
|
||||
"debug-argument" : u'режим отладки',
|
||||
"force-gui-prompt-argument" : u'показать окно настройки',
|
||||
"no-store-argument" : u'не сохранять данные в .syncplay',
|
||||
"room-argument" : u'начальная комната',
|
||||
"password-argument" : u'пароль для доступа к серверу',
|
||||
"player-path-argument" : u'путь к исполняемому файлу Вашего проигрывателя',
|
||||
"file-argument" : u'воспроизводимый файл',
|
||||
"args-argument" : u'параметры проигрывателя; если нужно передать параметры, начинающиеся с - , то сначала пишите \'--\'',
|
||||
"clear-gui-data-argument" : u'сбрасывает путь и данные о состоянии окна GUI, хранимые как QSettings',
|
||||
"language-argument" : u'язык сообщений Syncplay (en/pl/ru)',
|
||||
|
||||
"version-argument" : u'выводит номер версии',
|
||||
"version-message" : u"Вы используете Syncplay версии {} ({})",
|
||||
|
||||
# Client labels
|
||||
"config-window-title" : u"Настройка Syncplay",
|
||||
|
||||
"connection-group-title" : u"Параметры подключения",
|
||||
"host-label" : u"Адрес сервера: ",
|
||||
"name-label" : u"Имя пользователя (не обязательно):",
|
||||
"password-label" : u"Пароль к серверу (если требуется):",
|
||||
"room-label" : u"Начальная комната: ",
|
||||
|
||||
"media-setting-title" : u"Параметры проигрывателя",
|
||||
"executable-path-label" : u"Путь к проигрывателю:",
|
||||
"media-path-label" : u"Путь к видеофайлу:",
|
||||
"browse-label" : u"Выбрать",
|
||||
|
||||
"more-title" : u"Больше настроек",
|
||||
"slowdown-threshold-label" : u"Предел для замедления:",
|
||||
"rewind-threshold-label" : u"Предел для перемотки:",
|
||||
"never-rewind-value" : u"Никогда",
|
||||
"seconds-suffix" : u" секунд(ы)",
|
||||
"privacy-sendraw-option" : u"отпр. как есть",
|
||||
"privacy-sendhashed-option" : u"отпр. хэш",
|
||||
"privacy-dontsend-option" : u"не отпр.",
|
||||
"filename-privacy-label" : u"Имя файла:",
|
||||
"filesize-privacy-label" : u"Размер файла:",
|
||||
"slowondesync-label" : u"Замедлять при небольших рассинхронизациях (не поддерживаетя в MPC-HC)",
|
||||
"rewindondesync-label" : u"Перемотка при больших рассинхронизациях (настоятельно рекомендуется)",
|
||||
"dontslowdownwithme-label" : u"Никогда не замедлять или перематывать видео другим",
|
||||
"pauseonleave-label" : u"Приостанавливать, когда кто-то уходит (например, отключился)",
|
||||
"forceguiprompt-label" : u"Не показывать больше этот диалог", # (Inverted)
|
||||
"nostore-label" : u"Не сохранять текущую конфигурацию", # (Inverted)
|
||||
"showosd-label" : u"Включить экранные сообщения (поверх видео)",
|
||||
|
||||
"showosdwarnings-label" : u"Показывать предупреждения (напр., когда файлы не совпадают)",
|
||||
"showsameroomosd-label" : u"Показывать события Вашей комнаты",
|
||||
"showdifferentroomosd-label" : u"Показывать события других комнат",
|
||||
"showslowdownosd-label" : u"Показывать уведомления о замедлении/перемотке",
|
||||
"showcontactinfo-label" : u"Отображать контактную информацию разработчиков",
|
||||
"showdurationnotification-label" : u"Предупреждать о несовпадении продолжительности видео",
|
||||
"basics-label" : u"Основное",
|
||||
"sync-label" : u"Синхронизация",
|
||||
"messages-label" : u"Сообщения",
|
||||
"privacy-label" : u"Приватность",
|
||||
|
||||
"help-label" : u"Помощь",
|
||||
"reset-label" : u"Сброс настроек",
|
||||
"run-label" : u"Запустить Syncplay",
|
||||
"storeandrun-label" : u"Сохранить настройки и зап. Syncplay",
|
||||
|
||||
"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"Пауза",
|
||||
|
||||
"roomuser-heading-label" : u"Комната / Пользователь",
|
||||
"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"&Открыть видеофайл",
|
||||
"exit-menu-label" : u"&Выход",
|
||||
"advanced-menu-label" : u"&Дополнительно",
|
||||
"setoffset-menu-label" : u"Установить &смещение",
|
||||
"help-menu-label" : u"&Помощь",
|
||||
"userguide-menu-label" : u"&Руководство Пользователя",
|
||||
|
||||
"setoffset-msgbox-label" : u"Установить смещение",
|
||||
"offsetinfo-msgbox-label" : u"Смещение (см. как использовать на http://syncplay.pl/guide/):",
|
||||
|
||||
# Tooltips
|
||||
|
||||
"host-tooltip" : u"Имя или IP-адрес, к которому будет произведено подключение, может содержать номер порта (напр., syncplay.pl:8999). Синхронизация возможна только в рамках одного сервера/порта.",
|
||||
"name-tooltip" : u"Имя, под которым Вы будете известны. Регистриция не требуется, так что имя пользователя можно легко сменить в любой момент. Будет сгенерировано случайным образом, если не указать.",
|
||||
"password-tooltip" : u"Пароли нужны для подключения к приватным серверам.",
|
||||
"room-tooltip" : u"Комната, в которую Вы попадете сразу после подключения. Можно не указывать. Синхронизация возможна только между людьми в одной и той же комнате.",
|
||||
|
||||
"executable-path-tooltip" : u"Расположение Вашего видеопроигрывателя (MPC-HC, VLC, mplayer2 или mpv).",
|
||||
"media-path-tooltip" : u"Расположение видеофайла или потока для просмотра. Обязательно для mpv и mplayer2.",
|
||||
|
||||
"more-tooltip" : u"Показать дополнительные настройки.",
|
||||
"slowdown-threshold-tooltip" : u"Отставание самого медленного клиента, необходимое для временного уменьшения скорости видео (по умолчанию: {} сек.).".format(constants.DEFAULT_SLOWDOWN_KICKIN_THRESHOLD),
|
||||
"rewind-threshold-tooltip" : u"Отставание самого медленного клиента, необходимое для перемотки назад в целях синхронизации (по умолчанию: {} сек.).".format(constants.DEFAULT_REWIND_THRESHOLD),
|
||||
"filename-privacy-tooltip" : u"Режим приватности для передачи имени воспроизводимого файла на сервер.",
|
||||
"filesize-privacy-tooltip" : u"Режим приватности для передачи размера воспроизводимого файла на сервер.",
|
||||
"privacy-sendraw-tooltip" : u"Отправляет эту информацию без шифрования. Рекомендуемая опция с наибольшей функциональностью.",
|
||||
"privacy-sendhashed-tooltip" : u"Отправляет хэш-сумму этой информации, делая ее невидимой для других пользователей.",
|
||||
"privacy-dontsend-tooltip" : u"Не отправлять эту информацию на сервер. Предоставляет наибольшую приватность.",
|
||||
"slowondesync-tooltip" : u"Временно уменьшить скорость воспроизведения в целях синхронизации с другими зрителями. Не поддерживается в MPC-HC.",
|
||||
"dontslowdownwithme-tooltip" : u"Ваши лаги не будут влиять на других зрителей.",
|
||||
"pauseonleave-tooltip" : u"Приостановить воспроизведение, если Вы покинули комнату или кто-то из зрителей отключился от сервера.",
|
||||
"forceguiprompt-tooltip" : u"Окно настройки не будет отображаться при открытии файла в Syncplay.", # (Inverted)
|
||||
"nostore-tooltip" : u"Запустить Syncplay с данной конфигурацией, но не сохранять изменения навсегда.", # (Inverted)
|
||||
"rewindondesync-tooltip" : u"Перематывать назад, когда это необходимо для синхронизации. Отключение этой опции может привести к большим рассинхронизациям!",
|
||||
"showosd-tooltip" : u"Отправлять сообщения Syncplay в видеопроигрыватель и отображать их поверх видео (OSD - On Screen Display).",
|
||||
"showosdwarnings-tooltip" : u"Показывать OSC-предупреждения, если проигрываются разные файлы или если Вы в комнате больше никого нет.",
|
||||
"showsameroomosd-tooltip" : u"Показывать OSD-уведомления о событиях, относящихся к комнате, в которой Вы находитесь.",
|
||||
"showdifferentroomosd-tooltip" : u"Показывать OSD-уведомления о событиях, относящихся к любым другим комнатам.",
|
||||
"showslowdownosd-tooltip" : u"Показывать уведомления о замедлении или перемотке в целях синхронизации.",
|
||||
"showcontactinfo-tooltip" : u"Показывать информационных блок с контактной информацией разработчиков Syncplay на главном окне Syncplay.",
|
||||
"showdurationnotification-tooltip" : u"Полезно, когда сегмент составного файла отсутствует. Возможны ложные срабатывания.",
|
||||
|
||||
"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"Перемотать к определенному моменту времени (указывать в секундах или мин:сек). Используйте +/-, чтобы перемотать вперед/назад относительно настоящего момента.",
|
||||
|
||||
# In-userlist notes (GUI)
|
||||
"differentsize-note" : u"Размер файла не совпадает!",
|
||||
"differentsizeandduration-note" : u"Размер и продолжительность файла не совпадают!",
|
||||
"differentduration-note" : u"Продолжительность файла не совпадает!",
|
||||
"nofile-note" : u"(Ничего не воспроизводим)",
|
||||
|
||||
# Server messages to client
|
||||
"new-syncplay-available-motd-message" : u"<NOTICE> Вы используете Syncplay версии {}. Доступна более новая версия на http://syncplay.pl/ . </NOTICE>", # ClientVersion
|
||||
|
||||
# Server arguments
|
||||
"server-argument-description" : u'Решение для синхронного воспроизведения в VLC, MPlayer или MPC-HC через Интернет. Серверная часть',
|
||||
"server-argument-epilog" : u'Если параметр не будет передан, то будет использоваться значение, указанное в _config.',
|
||||
"server-port-argument" : u'номер TCP порта сервера',
|
||||
"server-password-argument" : u'пароль к серверу',
|
||||
"server-isolate-room-argument" : u'должны ли комнаты быть изолированными?',
|
||||
"server-motd-argument" : u"путь к файлу, из которого будет извлекаться MOTD-сообщение",
|
||||
"server-messed-up-motd-unescaped-placeholders" : u"MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).",
|
||||
"server-messed-up-motd-too-long" : u"MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).",
|
||||
|
||||
# Server errors
|
||||
"unknown-command-server-error" : u"Неизвестная команда: {}", # message
|
||||
"not-json-server-error" : u"Не является закодированной json-строкой: {}", # message
|
||||
"not-known-server-error" : u"Данную команду могут выполнять только авторизованные пользователи.",
|
||||
"client-drop-server-error" : u"Клиент отключен с ошибкой: {} -- {}", # host, error
|
||||
"password-required-server-error" : u"Необходимо указать пароль.",
|
||||
"wrong-password-server-error" : u"Указан неверный пароль.",
|
||||
"hello-server-error" : u"Не хватает аргументов Hello.",
|
||||
|
||||
}
|
||||
|
||||
de = {
|
||||
"LANGUAGE" : u"Deutsch", # (German)
|
||||
|
||||
# Client notifications
|
||||
"config-cleared-notification" : u"Settings cleared. Changes will be saved when you store a valid configuration.", # TODO: Translate into German
|
||||
|
||||
"relative-config-notification" : u"Relative Konfigurationsdatei(en) geladen: {}",
|
||||
|
||||
"connection-attempt-notification" : u"Versuche zu verbinden nach {}:{}", # Port, IP
|
||||
"reconnection-attempt-notification" : u"Verbindung zum Server verloren, versuche erneut",
|
||||
"disconnection-notification" : u"Verbindung zum Server beendet",
|
||||
"connection-failed-notification" : u"Verbindung zum Server fehlgeschlagen",
|
||||
"connected-successful-notification" : u"Erfolgreich mit Server verbunden",
|
||||
"retrying-notification" : u"%s, versuche erneut in %d Sekunden...", # Seconds
|
||||
|
||||
"rewind-notification" : u"Zurückgespult wegen Zeitdifferenz mit <{}>", # User
|
||||
"fastforward-notification" : u"Fast-forwarded due to time difference with <{}>", # User # TODO: Translate into German
|
||||
"slowdown-notification" : u"Verlangsamt wegen Zeitdifferenz mit <{}>", # User
|
||||
"revert-notification" : u"Normalgeschwindigkeit",
|
||||
|
||||
"pause-notification" : u"<{}> pausierte", # User
|
||||
"unpause-notification" : u"<{}> startete", # User
|
||||
"seek-notification" : u"<{}> sprang von {} nach {}", # User, from time, to time
|
||||
|
||||
"current-offset-notification" : u"Aktueller Offset: {} Sekunden", # Offset
|
||||
|
||||
"room-join-notification" : u"<{}> hat den Raum '{}' betreten", # User
|
||||
"left-notification" : u"<{}> ist gegangen", # User
|
||||
"left-paused-notification" : u"<{}> left, <{}> paused", # User who left, User who paused # TODO: Translate into German
|
||||
"playing-notification" : u"<{}> spielt '{}' ({})", # User, file, duration
|
||||
"playing-notification/room-addendum" : u" in Raum: '{}'", # Room
|
||||
|
||||
"identifying-as-controller-notification" : u"Identifying as room controller with password '{}'...", # TODO: Translate into German
|
||||
"failed-to-identify-as-controller-notification" : u"<{}> failed to identify as a room controller.", # TODO: Translate into German
|
||||
"authenticated-as-controller-notification" : u"<{}> authenticated as a room controller", # 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: ", # controlPassword
|
||||
"room-files-not-same" : u"Nicht alle Dateien im Raum sind gleich",
|
||||
"alone-in-the-room": u"Du bist alleine im Raum",
|
||||
|
||||
"different-filesize-notification" : u" (ihre Dateigröße ist anders als deine!)",
|
||||
"file-played-by-notification" : u"Datei: {} wird gespielt von:", # File
|
||||
"notplaying-notification" : u"Personen im Raum, die keine Dateien spielen:",
|
||||
"userlist-room-notification" : u"In Raum '{}':", # Room
|
||||
|
||||
"mplayer-file-required-notification" : u"Syncplay für mplayer benötigt eine Datei-Angabe beim Start",
|
||||
"mplayer-file-required-notification/example" : u"Nutzungsbeispiel: syncplay [optionen] [url|pfad/]dateiname",
|
||||
"mplayer2-required" : u"Syncplay ist inkompatibel zu MPlayer 1.x, bitte nutze MPlayer2 oder mpv",
|
||||
|
||||
"unrecognized-command-notification" : u"Unbekannter Befehl",
|
||||
"commandlist-notification" : u"Verfügbare Befehle:",
|
||||
"commandlist-notification/room" : u"\tr [Name] - Raum ändern",
|
||||
"commandlist-notification/list" : u"\tl - Nutzerliste anzeigen",
|
||||
"commandlist-notification/undo" : u"\tu - Letzter Zeitsprung rückgängig",
|
||||
"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 Sekungen oder Minuten:Sekunden",
|
||||
"commandlist-notification/help" : u"\th - Diese Hilfe",
|
||||
"commandlist-notification/create" : u"\tc [name] - create controlled room using name of current room", # TODO: Translate into German
|
||||
"commandlist-notification/auth" : u"\ta [password] - authenticate as room controller with controller password", # TODO: Translate into German
|
||||
"syncplay-version-notification" : u"Syncplay Version: {}", # syncplay.version
|
||||
"more-info-notification" : u"Weitere Informationen auf: {}", # projectURL
|
||||
|
||||
"gui-data-cleared-notification" : u"Syncplay hat die Pfad und Fensterdaten der Syncplay-GUI zurückgesetzt.",
|
||||
"language-changed-msgbox-label" : u"Language will be changed when you run Syncplay.", # 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
|
||||
"vlc-interface-oldversion-ignored": u"Warnung: Syncplay hat bemerkt, dass eine alte Version des Syncplay Interface-Moduls für VLC im VLC-Verzeichnis installiert ist. Daher wird, wenn du VLC 2.0 nutzt, die syncplay.lua die mit Syncplay mitgeliefert wurde, verwendet. Dies bedeutet allerdings, dass keine anderen Interface-Skripts und Erweiterungen geladen werden. In der Syncplay-Anleitung unter http://syncplay.pl/guide/ [Englisch] findest du Details zur Installation des syncplay.lua-Skripts.",
|
||||
"vlc-interface-not-installed": u"Warnung: Es wurde kein Syncplay Interface-Modul für VLC im VLC-Verzeichnis gefunden. Daher wird, wenn du VLC 2.0 nutzt, die syncplay.lua die mit Syncplay mitgeliefert wurde, verwendet. Dies bedeutet allerdings, dass keine anderen Interface-Skripts und Erweiterungen geladen werden. In der Syncplay-Anleitung unter http://syncplay.pl/guide/ [Englisch] findest du Details zur Installation des syncplay.lua-Skripts.",
|
||||
|
||||
# Client prompts
|
||||
"enter-to-exit-prompt" : u"Enter drücken zum Beenden\n",
|
||||
|
||||
# Client errors
|
||||
"missing-arguments-error" : u"Notwendige Argumente fehlen, siehe --help",
|
||||
"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 is not compatible with this version of mpv. Please use a different version of mpv (e.g. Git HEAD).", # TODO: Translate into German
|
||||
"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",
|
||||
"empty-error" : u"{} darf nicht leer sein", # Configuration
|
||||
"media-player-error": u"Media player error: \"{}\"", # Error line # TODO: Translate into German
|
||||
"unable-import-gui-error": u"Could not import GUI libraries. If you do not have PySide installed then you will need to install it for the GUI to work.", # TODO: Translate into German
|
||||
|
||||
"arguments-missing-error" : u"Notwendige Argumente fehlen, siehe --help",
|
||||
|
||||
"unable-to-start-client-error" : u"Client kann nicht gestartet werden",
|
||||
|
||||
"player-path-config-error": u"Player path is not set properly", # TODO: Translate into German
|
||||
"no-file-path-config-error": u"File must be selected before starting your player", # TODO: Translate into German
|
||||
"no-hostname-config-error": u"Hostname can't be empty", # TODO: Translate into German
|
||||
"invalid-port-config-error" : u"Port must be valid", # TODO: Translate into German
|
||||
"empty-value-config-error" : u"{} can't be empty", # Config option # TODO: Translate into German
|
||||
|
||||
"not-json-error" : u"Kein JSON-String\n",
|
||||
"hello-arguments-error" : u"Zu wenige Hello-Argumente\n",
|
||||
"version-mismatch-error" : u"Verschiedene Versionen auf Client und Server\n",
|
||||
"vlc-failed-connection": u"Kann nicht zu VLC verbinden. Wenn du syncplay.lua nicht installiert hast, findest du auf http://syncplay.pl/LUA/ für eine Anleitung.",
|
||||
"vlc-failed-noscript": u"Laut VLC ist das syncplay.lua Interface-Skript nicht installiert. Auf http://syncplay.pl/LUA/ findest du eine Anleitung.",
|
||||
"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
|
||||
|
||||
# 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',
|
||||
"nogui-argument" : u'Keine GUI anzeigen',
|
||||
"host-argument" : u'Server\'-Addresse',
|
||||
"name-argument" : u'Gewünschter Nutzername',
|
||||
"debug-argument" : u'Debug-Modus',
|
||||
"force-gui-prompt-argument" : u'Einstellungsfenster anzeigen',
|
||||
"no-store-argument" : u'keine Werte in .syncplay speichern',
|
||||
"room-argument" : u'Standard-Raum',
|
||||
"password-argument" : u'Server-Passwort',
|
||||
"player-path-argument" : u'Pfad zum Player',
|
||||
"file-argument" : u'Zu spielende Datei',
|
||||
"args-argument" : u'Player-Einstellungen; Wenn du Einstellungen, die mit - beginnen, nutzen willst, stelle ein einzelnes \'--\'-Argument davor',
|
||||
"clear-gui-data-argument" : u'Setzt die Pfad- und GUI-Fenster-Daten, die in den QSettings gespeichert sind, zurück',
|
||||
"language-argument" : u'language for Syncplay messages (de/en/pl/ru)', # TODO: Translate into German
|
||||
|
||||
"version-argument" : u'prints your version', # TODO: Translate into German
|
||||
"version-message" : u"You're using Syncplay version {} ({})", # TODO: Translate into German
|
||||
|
||||
# Client labels
|
||||
"config-window-title" : u"Syncplay Konfiguration",
|
||||
|
||||
"connection-group-title" : u"Verbindungseinstellungen",
|
||||
"host-label" : u"Server-Adresse: ",
|
||||
"name-label" : u"Benutzername (optional):",
|
||||
"password-label" : u"Server-Passwort (falls nötig):",
|
||||
"room-label" : u"Standard-Raum: ",
|
||||
|
||||
"media-setting-title" : u"Media-Player Einstellungen",
|
||||
"executable-path-label" : u"Pfad zum Media-Player:",
|
||||
"media-path-label" : u"Pfad zur Datei:",
|
||||
"browse-label" : u"Durchsuchen",
|
||||
|
||||
"more-title" : u"Mehr Einstellungen zeigen",
|
||||
"slowdown-threshold-label" : u"Slow down threshold:", # TODO: Translate into German
|
||||
"rewind-threshold-label" : u"Rewind threshold:", # TODO: Translate into German
|
||||
"fastforward-threshold-label" : u"Fast-forward threshold:", # TODO: Translate into German
|
||||
"never-rewind-value" : u"Never", # TODO: Translate into German
|
||||
"seconds-suffix" : u" secs", # TODO: Translate into German (if needed)
|
||||
"privacy-sendraw-option" : u"Klartext senden",
|
||||
"privacy-sendhashed-option" : u"Hash senden",
|
||||
"privacy-dontsend-option" : u"Nicht senden",
|
||||
"filename-privacy-label" : u"Dateiname:",
|
||||
"filesize-privacy-label" : u"Dateigröße:",
|
||||
"slowondesync-label" : u"Verlangsamen wenn nicht synchron", # TODO: Update new wording into German (should translate to "Slow down on minor desync (not supported on MPC-HC)")
|
||||
"dontslowdownwithme-label" : u"Nie verlangsamen oder andere zurückspulen", # TODO: Update new wording into German (should state "Experimental" in brackets at the end)
|
||||
"pauseonleave-label" : u"Pausieren wenn ein Benutzer austritt",
|
||||
"forceguiprompt-label" : u"Don't always show this dialog", # TODO: Translate into German (previous was "Diesen Dialog immer anzeigen" but the logic is now inverted)
|
||||
"nostore-label" : u"Diese Konfiguration nicht speichern",
|
||||
"showosd-label" : u"Enable OSD Messages", # TODO: Translate into German
|
||||
|
||||
"showosdwarnings-label" : u"Include warnings (e.g. when files are different)", # TODO: Translate into German
|
||||
"showsameroomosd-label" : u"Include events in your room", # TODO: Translate into German
|
||||
"shownoncontrollerosd-label" : u"Include events from non-controllers in controlled rooms", # TODO: Translate into German
|
||||
"showdifferentroomosd-label" : u"Include events in other rooms", # TODO: Translate into German
|
||||
"showslowdownosd-label" : u"Include slowing down / reverting notification", # TODO: Translate into German
|
||||
"showcontactinfo-label" : u"Show contact info box", # TODO: Translate into German
|
||||
"language-label" : u"Language", # TODO: Translate into German
|
||||
"showdurationnotification-label" : u"Warn about media duration mismatches", # TODO: Translate into German
|
||||
"basics-label" : u"Basics", # TODO: Translate into German
|
||||
"sync-label" : u"Sync", # TODO: Translate into German
|
||||
"sync-lagging-title" : u"If others are lagging behind...", # TODO: Translate into German
|
||||
"sync-other-title" : u"Other sync options", # TODO: Translate into German
|
||||
"messages-label" : u"Messages", # TODO: Translate into German
|
||||
"messages-osd-title" : u"On-screen Display settings", # TODO: Translate into German
|
||||
"messages-other-title" : u"Other display settings", # TODO: Translate into German
|
||||
"privacy-label" : u"Privacy", # TODO: Translate into German
|
||||
"privacy-title" : u"Privacy settings", # TODO: Translate into German
|
||||
|
||||
"help-label" : u"Hilfe",
|
||||
"reset-label" : u"Restore defaults", # TODO: Translate into German
|
||||
"run-label" : u"Syncplay starten",
|
||||
"storeandrun-label" : u"Konfiguration speichern und Syncplay starten",
|
||||
|
||||
"contact-label" : u"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.", #TODO: Translate into German
|
||||
|
||||
"joinroom-guibuttonlabel" : u"Join room", # TODO: Translate into German
|
||||
"seektime-guibuttonlabel" : u"Seek to time", # TODO: Translate into German
|
||||
"undoseek-guibuttonlabel" : u"Undo seek", # TODO: Translate into German
|
||||
"togglepause-guibuttonlabel" : u"Toggle pause", # TODO: Translate into German
|
||||
"play-guibuttonlabel" : u"Play", # TODO: Translate into German
|
||||
"pause-guibuttonlabel" : u"Pause", # TODO: Translate into German
|
||||
|
||||
"roomuser-heading-label" : u"Raum / Benutzer",
|
||||
"size-heading-label" : u"Size", # TODO: Translate into German (Note: This is size as in filesize)
|
||||
"duration-heading-label" : u"Length", # TODO: Translate into German (Note: This is length as in file duration)
|
||||
"filename-heading-label" : u"Filename", # TODO: Translate into German
|
||||
"notifications-heading-label" : u"Notifications", # TODO: Translate into German
|
||||
"userlist-heading-label" : u"List of who is playing what", # TODO: Translate into German
|
||||
"othercommands-heading-label" : u"Other commands", # TODO: Translate into German
|
||||
"room-heading-label" : u"Room", # TODO: Translate into German
|
||||
"seek-heading-label" : u"Seek", # TODO: Translate into German
|
||||
|
||||
"browseformedia-label" : u"Browse for media files", # TODO: Translate into German
|
||||
|
||||
"file-menu-label" : u"&File", # & precedes shortcut key # TODO: Translate into German
|
||||
"openmedia-menu-label" : u"&Open media file", # TODO: Translate into German
|
||||
"exit-menu-label" : u"E&xit", # TODO: Translate into German
|
||||
"advanced-menu-label" : u"&Advanced", # TODO: Translate into German
|
||||
"setoffset-menu-label" : u"Set &offset", # TODO: Translate into German
|
||||
"createcontrolledroom-menu-label" : u"&Create controlled room", # TODO: Translate into German
|
||||
"identifyascontroller-menu-label" : u"&Identify as room controller", # TODO: Translate into German
|
||||
|
||||
"help-menu-label" : u"&Help", # TODO: Translate into German
|
||||
"userguide-menu-label" : u"Open user &guide", # TODO: Translate into German
|
||||
|
||||
"setoffset-msgbox-label" : u"Set offset", # TODO: Translate into German
|
||||
"offsetinfo-msgbox-label" : u"Offset (see http://syncplay.pl/guide/ for usage instructions):", # TODO: Translate into German
|
||||
|
||||
|
||||
"createcontrolledroom-msgbox-label" : u"Create controlled room", # TODO: Translate into German
|
||||
"controlledroominfo-msgbox-label" : u"Enter name of controlled room\r\n(see http://syncplay.pl/guide/ for usage instructions):", # TODO: Translate into German
|
||||
|
||||
"identifyascontroller-msgbox-label" : u"Identify as Room Controller", # TODO: Translate into German
|
||||
"identifyinfo-msgbox-label" : u"Enter controller password for this room\r\n(see http://syncplay.pl/guide/ for usage instructions):", # TODO: Translate into German
|
||||
|
||||
"megabyte-suffix" : u" MB", # TODO: Translate into German (or is MB fine?)
|
||||
|
||||
# Tooltips
|
||||
|
||||
"host-tooltip" : u"Hostname oder IP zu der verbunden werden soll. Ptional mit Port (z.B.. syncplay.pl:8999). Synchronisation findet nur mit Personen auf dem selben Server und Port statt.",
|
||||
"name-tooltip" : u"Dein Benutzername. Keine Registrierung, kann einfach geändert werden. Bei fehlender Angabe wird ein zufälliger Name generiert.",
|
||||
"password-tooltip" : u"Passwörter sind nur bei Verbindung zu privaten Servern nötig.",
|
||||
"room-tooltip" : u"Der Raum, der betreten werden soll, kann ein x-beliebiger sein. Allerdings werden nur Clients im selben Raum synchronisiert.",
|
||||
|
||||
"executable-path-tooltip" : u"Pfad zum ausgewählten, unterstützten Mediaplayer (MPC-HC, VLC, mplayer2 or mpv).",
|
||||
"media-path-tooltip" : u"Pfad zum wiederzugebenden Video oder Stream. Notwendig für mpv und mplayer2.",
|
||||
|
||||
"more-tooltip" : u"Weitere Einstellungen anzeigen.",
|
||||
"slowdown-threshold-tooltip" : u"Time ahead of slowest client before temporarily reducing playback speed (default: {} secs).".format(constants.DEFAULT_SLOWDOWN_KICKIN_THRESHOLD), # Todo: Translate into German
|
||||
"rewind-threshold-tooltip" : u"Time ahead slowest client before seeking to get back in sync (default: {} secs).".format(constants.DEFAULT_REWIND_THRESHOLD), # Todo: Translate into German
|
||||
"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 German
|
||||
"filename-privacy-tooltip" : u"Privat-Modus beim senden des Namens der aktuellen Datei zum Server.",
|
||||
"filesize-privacy-tooltip" : u"Privat-Modus beim senden der Größe der aktuellen Datei zum Server.",
|
||||
"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.",
|
||||
"slowondesync-tooltip" : u"Reduziert die Abspielgeschwindigkeit zeitweise, um dich wieder synchron zu den anderen Clients zu machen.",
|
||||
"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.",
|
||||
"rewindondesync-label" : u"Zurückspulen bei großer Zeitdifferent (empfohlen)",
|
||||
"fastforwardondesync-label" : u"Fast-forward if lagging behind (recommended)", # TODO: Translate into German
|
||||
"forceguiprompt-tooltip" : u"Configuration dialogue is not shown when opening a file with Syncplay.", # TODO: Translate into German (was "Der Konfigurations-Dislog wird immer angezeigt. Sogar, wenn du eine Datei mit Syncplay öffnest." but logic is now inverted)
|
||||
"nostore-tooltip" : u"Syncplay mit den angegebenen Einstellungen starten, diese aber nicht fauerhaft speichern.",
|
||||
"rewindondesync-tooltip" : u"In der Zeit zurückspringen zum wiederherstellen der Synchronität. Empfohlen.",
|
||||
"fastforwardondesync-tooltip" : u"Jump forward when out of sync with room controller (or your pretend position if 'Never slow down or rewind others' enabled).", # Translate into German (if you want)
|
||||
"showosd-tooltip" : u"Sends Syncplay messages to media player OSD.", # Translate into German (if you want)
|
||||
"showosdwarnings-tooltip" : u"Show warnings if playing different file, alone in room.", # TODO: Translate into German (if you want)
|
||||
"showsameroomosd-tooltip" : u"Show OSD notifications for events relating to room user is in.", # TODO: Translate into German (if you want)
|
||||
"shownoncontrollerosd-tooltip" : u"Show OSD notifications for events relating to non-controllers who are in controllerd rooms.", # Translate into German (if you want)
|
||||
"showdifferentroomosd-tooltip" : u"Show OSD notifications for events relating to room user is not in.", # TODO: Translate into German (if you want)
|
||||
"showslowdownosd-tooltip" : u"Show notifications of slowing down / reverting on time difference.", # TODO: Translate into German (if you want)
|
||||
"showcontactinfo-tooltip" : u"Show information box about contacting Syncplay developers in main Syncplay window.", # TODO: Translate into German (if you want)
|
||||
"showdurationnotification-tooltip" : u"Useful for when a segment in a multi-part file is missing, but can result in false positives.", # TODO: Translate into German (if you want)
|
||||
"language-tooltip" : u"Language to be used by Syncplay.", # TODO: Translate into German (if you want)
|
||||
|
||||
"help-tooltip" : u"Öffnet Hilfe auf syncplay.pl [Englisch]",
|
||||
"reset-tooltip" : u"Reset all settings to the default configuration.", # TODO: Translate into German
|
||||
|
||||
"togglepause-tooltip" : u"Pause/unpause media.", # TODO: Translate into German (if you want)
|
||||
"play-tooltip" : u"Unpause media.", # TODO: Translate into German (if you want)
|
||||
"pause-tooltip" : u"Pause media.", # TODO: Translate into German (if you want)
|
||||
"undoseek-tooltip" : u"Seek to where you were before the most recent seek.", # TODO: Translate into German(if you want)
|
||||
"joinroom-tooltip" : u"Leave current room and joins specified room.", # TODO: Translate into German(if you want)
|
||||
"seektime-tooltip" : u"Jump to specified time (in seconds / min:sec). Use +/- for relative seek.", # TODO: Translate into German
|
||||
|
||||
# In-userlist notes (GUI)
|
||||
"differentsize-note" : u"Different size!", # TODO: Translate into German
|
||||
"differentsizeandduration-note" : u"Different size and duration!", # TODO: Translate into German
|
||||
"differentduration-note" : u"Different duration!", # TODO: Translate into German
|
||||
"nofile-note" : u"(No file being played)", # TODO: Translate into German
|
||||
|
||||
# Server messages to client
|
||||
"new-syncplay-available-motd-message" : u"<NOTICE> Du nutzt Syncplay Version {}, aber es gibt eine neuere Version auf http://syncplay.pl</NOTICE>", # ClientVersion
|
||||
|
||||
# Server notifications
|
||||
"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" : "PLEASE NOTE: To allow room control passwords generated by this server instance to still work when the server is restarted, please add the following command line argument when running the Syncplay server in the future: --salt {}", #Salt # TODO: Translate into German
|
||||
|
||||
# Server arguments
|
||||
"server-argument-description" : u'Anwendung, um mehrere MPlayer, MPC-HC und VLC-Instanzen über das Internet zu synchronisieren. Server',
|
||||
"server-argument-epilog" : u'Wenn keine Optionen angegeben sind, werden die _config-Werte verwendet',
|
||||
"server-port-argument" : u'Server TCP-Port',
|
||||
"server-password-argument" : u'Server Passwort',
|
||||
"server-isolate-room-argument" : u'Sollen die Räume isoliert sein?',
|
||||
"server-salt-argument" : u"random string used to generate controlled room passwords", # 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 {}.",
|
||||
|
||||
# Server errors
|
||||
"unknown-command-server-error" : u"Unbekannter Befehl {}", # message
|
||||
"not-json-server-error" : u"Kein JSON-String {}", # message
|
||||
"not-known-server-error" : u"Der Server muss dich kennen, bevor du diesen Befehl nutzen kannst",
|
||||
"client-drop-server-error" : u"Client verloren: {} -- {}", # host, error
|
||||
"password-required-server-error" : u"Passwort nötig",
|
||||
"wrong-password-server-error" : u"Ungültiges Passwort",
|
||||
"hello-server-error" : u"Zu wenige Hello-Argumente"
|
||||
}
|
||||
|
||||
messages = {
|
||||
"en": en,
|
||||
"pl": pl,
|
||||
"current": None
|
||||
"ru": ru,
|
||||
"de": de,
|
||||
"CURRENT": None
|
||||
}
|
||||
|
||||
def getLanguages():
|
||||
langList = {}
|
||||
for lang in messages:
|
||||
if lang != "CURRENT":
|
||||
langList[lang] = getMessage("LANGUAGE", lang)
|
||||
return langList
|
||||
|
||||
def setLanguage(lang):
|
||||
messages["current"] = lang
|
||||
messages["CURRENT"] = lang
|
||||
|
||||
def getMissingStrings():
|
||||
missingStrings = ""
|
||||
for lang in messages:
|
||||
if lang != "en" and lang != "CURRENT":
|
||||
for message in messages["en"]:
|
||||
if not messages[lang].has_key(message):
|
||||
missingStrings = missingStrings + "({}) Missing: {}\n".format(lang, message)
|
||||
for message in messages[lang]:
|
||||
if not messages["en"].has_key(message):
|
||||
missingStrings = missingStrings + "({}) Unused: {}\n".format(lang, message)
|
||||
|
||||
return missingStrings
|
||||
|
||||
def getMessage(type_, locale=None):
|
||||
if constants.SHOW_BUTTON_LABELS == False:
|
||||
@ -327,7 +955,7 @@ def getMessage(type_, locale=None):
|
||||
if constants.SHOW_TOOLTIPS == False:
|
||||
if "-tooltip" in type_:
|
||||
return ""
|
||||
lang = messages["current"]
|
||||
lang = messages["CURRENT"]
|
||||
if locale and messages.has_key(locale):
|
||||
if messages[locale].has_key(type_):
|
||||
return unicode(messages[locale][type_])
|
||||
|
||||
@ -49,7 +49,7 @@ class BasePlayer(object):
|
||||
'''
|
||||
@type filePath: string
|
||||
'''
|
||||
def openFile(self, filePath):
|
||||
def openFile(self, filePath, resetPosition=False):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@ -82,8 +82,25 @@ class BasePlayer(object):
|
||||
@staticmethod
|
||||
def getExpandedPath(path):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
|
||||
'''
|
||||
Opens a custom media browse dialog, and then changes to that media if appropriate
|
||||
'''
|
||||
@staticmethod
|
||||
def openCustomOpenDialog(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
'''
|
||||
@type playerPath: string
|
||||
@type filePath: string
|
||||
@return errorMessage: string
|
||||
|
||||
Checks if the player has any problems with the given player/file path
|
||||
'''
|
||||
@staticmethod
|
||||
def getPlayerPathErrors(playerPath, filePath):
|
||||
raise NotImplementedError()
|
||||
|
||||
class DummyPlayer(BasePlayer):
|
||||
|
||||
@staticmethod
|
||||
@ -101,3 +118,7 @@ class DummyPlayer(BasePlayer):
|
||||
@staticmethod
|
||||
def getExpandedPath(path):
|
||||
return path
|
||||
|
||||
@staticmethod
|
||||
def getPlayerPathErrors(playerPath, filePath):
|
||||
return None
|
||||
@ -306,6 +306,7 @@ class MpcHcApi:
|
||||
|
||||
class MPCHCAPIPlayer(BasePlayer):
|
||||
speedSupported = False
|
||||
customOpenDialog = False
|
||||
|
||||
def __init__(self, client):
|
||||
from twisted.internet import reactor
|
||||
@ -331,6 +332,10 @@ class MPCHCAPIPlayer(BasePlayer):
|
||||
self.__versionUpdate.set()
|
||||
self._mpcApi.sendRawCommand(MpcHcApi.CMD_CLOSEAPP, "")
|
||||
|
||||
@staticmethod
|
||||
def getPlayerPathErrors(playerPath, filePath):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def run(client, playerPath, filePath, args):
|
||||
args.extend(['/open', '/new'])
|
||||
@ -385,7 +390,7 @@ class MPCHCAPIPlayer(BasePlayer):
|
||||
if filePath:
|
||||
self.openFile(filePath)
|
||||
|
||||
def openFile(self, filePath):
|
||||
def openFile(self, filePath, resetPosition=False):
|
||||
self._mpcApi.openFile(filePath)
|
||||
|
||||
def displayMessage(self, message, duration = (constants.OSD_DURATION*1000)):
|
||||
|
||||
@ -9,8 +9,8 @@ import os
|
||||
|
||||
class MplayerPlayer(BasePlayer):
|
||||
speedSupported = True
|
||||
customOpenDialog = False
|
||||
RE_ANSWER = re.compile(constants.MPLAYER_ANSWER_REGEX)
|
||||
SLAVE_ARGS = constants.MPLAYER_SLAVE_ARGS
|
||||
POSITION_QUERY = 'time_pos'
|
||||
OSD_QUERY = 'osd_show_text'
|
||||
|
||||
@ -23,6 +23,9 @@ class MplayerPlayer(BasePlayer):
|
||||
self._duration = None
|
||||
self._filename = None
|
||||
self._filepath = None
|
||||
self.quitReason = None
|
||||
self.lastLoadedTime = None
|
||||
self.fileLoaded = False
|
||||
try:
|
||||
self._listener = self.__Listener(self, playerPath, filePath, args)
|
||||
except ValueError:
|
||||
@ -86,8 +89,12 @@ class MplayerPlayer(BasePlayer):
|
||||
def setSpeed(self, value):
|
||||
self._setProperty('speed', "{:.2f}".format(value))
|
||||
|
||||
def openFile(self, filePath):
|
||||
def _loadFile(self, filePath):
|
||||
self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath)))
|
||||
|
||||
def openFile(self, filePath, resetPosition=False):
|
||||
self._filepath = filePath
|
||||
self._loadFile(filePath)
|
||||
self._onFileUpdate()
|
||||
if self._paused != self._client.getGlobalPaused():
|
||||
self.setPaused(self._client.getGlobalPaused())
|
||||
@ -124,19 +131,42 @@ class MplayerPlayer(BasePlayer):
|
||||
arg = arg.replace('"', '\\"')
|
||||
return u'"{}"'.format(arg)
|
||||
|
||||
def _fileIsLoaded(self):
|
||||
return True
|
||||
|
||||
def _handleUnknownLine(self, line):
|
||||
pass
|
||||
|
||||
def _storePosition(self, value):
|
||||
self._position = value
|
||||
|
||||
def _storePauseState(self, value):
|
||||
self._paused = value
|
||||
|
||||
def lineReceived(self, line):
|
||||
if line:
|
||||
self._client.ui.showDebugMessage("player << {}".format(line))
|
||||
if "Failed to get value of property" in line:
|
||||
if "filename" in line:
|
||||
self._getFilename()
|
||||
elif "length" in line:
|
||||
self._getLength()
|
||||
elif "path" in line:
|
||||
self._getFilepath()
|
||||
return
|
||||
match = self.RE_ANSWER.match(line)
|
||||
if not match:
|
||||
self._handleUnknownLine(line)
|
||||
return
|
||||
|
||||
name, value =[m for m in match.groups() if m]
|
||||
name = name.lower()
|
||||
|
||||
if name == self.POSITION_QUERY:
|
||||
self._position = float(value)
|
||||
self._storePosition(float(value))
|
||||
self._positionAsk.set()
|
||||
elif name == "pause":
|
||||
self._paused = bool(value == 'yes')
|
||||
self._storePauseState(bool(value == 'yes'))
|
||||
self._pausedAsk.set()
|
||||
elif name == "length":
|
||||
self._duration = float(value)
|
||||
@ -149,7 +179,9 @@ class MplayerPlayer(BasePlayer):
|
||||
self._filenameAsk.set()
|
||||
elif name == "exiting":
|
||||
if value != 'Quit':
|
||||
self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("media-player-error").format(value), True)
|
||||
if self.quitReason is None:
|
||||
self.quitReason = getMessage("media-player-error").format(value)
|
||||
self.reactor.callFromThread(self._client.ui.showErrorMessage, self.quitReason, True)
|
||||
self.drop()
|
||||
|
||||
@staticmethod
|
||||
@ -170,12 +202,21 @@ class MplayerPlayer(BasePlayer):
|
||||
def getIconPath(path):
|
||||
return constants.MPLAYER_ICONPATH
|
||||
|
||||
@staticmethod
|
||||
def getStartupArgs(path):
|
||||
return constants.MPLAYER_SLAVE_ARGS
|
||||
|
||||
@staticmethod
|
||||
def isValidPlayerPath(path):
|
||||
if "mplayer" in path and MplayerPlayer.getExpandedPath(path):
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def getPlayerPathErrors(playerPath, filePath):
|
||||
if not filePath:
|
||||
return getMessage("no-file-path-config-error")
|
||||
|
||||
@staticmethod
|
||||
def getExpandedPath(playerPath):
|
||||
if not os.path.isfile(playerPath):
|
||||
@ -219,7 +260,7 @@ class MplayerPlayer(BasePlayer):
|
||||
filePath = os.path.realpath(filePath)
|
||||
|
||||
call = [playerPath, filePath]
|
||||
call.extend(playerController.SLAVE_ARGS)
|
||||
call.extend(playerController.getStartupArgs(playerPath))
|
||||
if args:
|
||||
call.extend(args)
|
||||
# At least mpv may output escape sequences which result in syncplay
|
||||
@ -259,7 +300,10 @@ class MplayerPlayer(BasePlayer):
|
||||
|
||||
def sendLine(self, line):
|
||||
try:
|
||||
line = (line.decode('utf8') + u"\n").encode('utf8')
|
||||
if not isinstance(line, unicode):
|
||||
line = line.decode('utf8')
|
||||
line = (line + u"\n").encode('utf8')
|
||||
self.__playerController._client.ui.showDebugMessage("player >> {}".format(line))
|
||||
self.__process.stdin.write(line)
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
@ -1,23 +1,35 @@
|
||||
import re
|
||||
import subprocess
|
||||
from syncplay.players.mplayer import MplayerPlayer
|
||||
from syncplay.messages import getMessage
|
||||
from syncplay import constants
|
||||
import os
|
||||
import os, sys, time
|
||||
|
||||
class MpvPlayer(MplayerPlayer):
|
||||
SLAVE_ARGS = constants.MPV_SLAVE_ARGS
|
||||
POSITION_QUERY = 'time-pos'
|
||||
OSD_QUERY = 'show_text'
|
||||
|
||||
def _setProperty(self, property_, value):
|
||||
self._listener.sendLine("no-osd set {} {}".format(property_, value))
|
||||
|
||||
def setPaused(self, value):
|
||||
if self._paused <> value:
|
||||
self._paused = not self._paused
|
||||
self._listener.sendLine('cycle pause')
|
||||
RE_VERSION = re.compile('.*mpv (\d)\.(\d)\.\d.*')
|
||||
|
||||
@staticmethod
|
||||
def run(client, playerPath, filePath, args):
|
||||
return MpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args)
|
||||
try:
|
||||
ver = MpvPlayer.RE_VERSION.search(subprocess.check_output([playerPath, '--version']))
|
||||
except:
|
||||
ver = None
|
||||
constants.MPV_NEW_VERSION = ver is None or int(ver.group(1)) > 0 or int(ver.group(2)) >= 5
|
||||
if constants.MPV_NEW_VERSION:
|
||||
return NewMpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args)
|
||||
else:
|
||||
return OldMpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args)
|
||||
|
||||
@staticmethod
|
||||
def getStartupArgs(path):
|
||||
args = constants.MPV_SLAVE_ARGS
|
||||
if constants.MPV_NEW_VERSION or sys.platform.startswith('win'):
|
||||
args.extend(constants.MPV_SLAVE_ARGS_NEW)
|
||||
if sys.platform.startswith('win') or not constants.MPV_NEW_VERSION:
|
||||
args.extend(constants.MPV_SLAVE_ARGS_WINDOWS)
|
||||
else:
|
||||
args.extend(constants.MPV_SLAVE_ARGS_NONWINDOWS)
|
||||
return args
|
||||
|
||||
@staticmethod
|
||||
def getDefaultPlayerPathsList():
|
||||
@ -53,3 +65,112 @@ class MpvPlayer(MplayerPlayer):
|
||||
@staticmethod
|
||||
def getIconPath(path):
|
||||
return constants.MPV_ICONPATH
|
||||
|
||||
class OldMpvPlayer(MpvPlayer):
|
||||
POSITION_QUERY = 'time-pos'
|
||||
OSD_QUERY = 'show_text'
|
||||
|
||||
def _setProperty(self, property_, value):
|
||||
self._listener.sendLine("no-osd set {} {}".format(property_, value))
|
||||
|
||||
def setPaused(self, value):
|
||||
if self._paused <> value:
|
||||
self._paused = not self._paused
|
||||
self._listener.sendLine('cycle pause')
|
||||
|
||||
def mpvVersionErrorCheck(self, line):
|
||||
if "Error parsing option" in line or "Error parsing commandline option" in line:
|
||||
self.quitReason = getMessage("mpv-version-error")
|
||||
|
||||
elif "Could not open pipe at '/dev/stdin'" in line:
|
||||
self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("mpv-version-error"), True)
|
||||
self.drop()
|
||||
|
||||
def _handleUnknownLine(self, line):
|
||||
self.mpvVersionErrorCheck(line)
|
||||
if "Playing: " in line:
|
||||
newpath = line[9:]
|
||||
oldpath = self._filepath
|
||||
if newpath != oldpath and oldpath is not None:
|
||||
self.reactor.callFromThread(self._onFileUpdate)
|
||||
if self._paused != self._client.getGlobalPaused():
|
||||
self.setPaused(self._client.getGlobalPaused())
|
||||
self.setPosition(self._client.getGlobalPosition())
|
||||
|
||||
class NewMpvPlayer(OldMpvPlayer):
|
||||
lastResetTime = None
|
||||
|
||||
def _storePosition(self, value):
|
||||
if self._recentlyReset():
|
||||
self._position = 0
|
||||
elif self._fileIsLoaded():
|
||||
self._position = value
|
||||
else:
|
||||
self._position = self._client.getGlobalPosition()
|
||||
|
||||
def _storePauseState(self, value):
|
||||
if self._fileIsLoaded():
|
||||
self._paused = value
|
||||
else:
|
||||
self._paused = self._client.getGlobalPaused()
|
||||
|
||||
def askForStatus(self):
|
||||
self._positionAsk.clear()
|
||||
self._pausedAsk.clear()
|
||||
self._getPaused()
|
||||
self._getPosition()
|
||||
self._positionAsk.wait(constants.MPV_LOCK_WAIT_TIME)
|
||||
self._pausedAsk.wait(constants.MPV_LOCK_WAIT_TIME)
|
||||
self._client.updatePlayerStatus(self._paused, self._position)
|
||||
|
||||
def _preparePlayer(self):
|
||||
self.setPaused(True)
|
||||
self.reactor.callLater(0, self._client.initPlayer, self)
|
||||
|
||||
def _clearFileLoaded(self):
|
||||
self.fileLoaded = False
|
||||
self.lastLoadedTime = None
|
||||
|
||||
def _loadFile(self, filePath):
|
||||
self._clearFileLoaded()
|
||||
self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath)))
|
||||
|
||||
def openFile(self, filePath, resetPosition=False):
|
||||
if resetPosition:
|
||||
self.lastResetTime = time.time()
|
||||
self._loadFile(filePath)
|
||||
if self._paused != self._client.getGlobalPaused():
|
||||
self.setPaused(self._client.getGlobalPaused())
|
||||
self.setPosition(self._client.getGlobalPosition())
|
||||
|
||||
def _handleUnknownLine(self, line):
|
||||
self.mpvVersionErrorCheck(line)
|
||||
|
||||
if line == "<SyncplayUpdateFile>" or "Playing:" in line:
|
||||
self._clearFileLoaded()
|
||||
|
||||
elif line == "</SyncplayUpdateFile>":
|
||||
self._onFileUpdate()
|
||||
|
||||
def _recentlyReset(self):
|
||||
if not self.lastResetTime:
|
||||
return False
|
||||
elif time.time() < self.lastResetTime + constants.MPV_NEWFILE_IGNORE_TIME:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _onFileUpdate(self):
|
||||
self.fileLoaded = True
|
||||
self.lastLoadedTime = time.time()
|
||||
self.reactor.callFromThread(self._client.updateFile, self._filename, self._duration, self._filepath)
|
||||
if not (self._recentlyReset()):
|
||||
self.reactor.callFromThread(self.setPosition, self._client.getGlobalPosition())
|
||||
if self._paused != self._client.getGlobalPaused():
|
||||
self.reactor.callFromThread(self._client.getGlobalPaused)
|
||||
|
||||
def _fileIsLoaded(self):
|
||||
if self.fileLoaded == True and self.lastLoadedTime != None and time.time() > (self.lastLoadedTime + constants.MPV_NEWFILE_IGNORE_TIME):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@ -10,10 +10,10 @@ import socket
|
||||
import asynchat, asyncore
|
||||
import urllib
|
||||
from syncplay.messages import getMessage
|
||||
import time
|
||||
|
||||
class VlcPlayer(BasePlayer):
|
||||
speedSupported = True
|
||||
customOpenDialog = False
|
||||
RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX)
|
||||
SLAVE_ARGS = constants.VLC_SLAVE_ARGS
|
||||
if not sys.platform.startswith('darwin'):
|
||||
@ -30,6 +30,13 @@ class VlcPlayer(BasePlayer):
|
||||
self._filename = None
|
||||
self._filepath = None
|
||||
self._filechanged = False
|
||||
try: # Hack to fix locale issue without importing locale library
|
||||
self.radixChar = "{:n}".format(1.5)[1:2]
|
||||
if self.radixChar == "" or self.radixChar == "1" or self.radixChar == "5":
|
||||
raise ValueError
|
||||
except:
|
||||
self._client.ui.showErrorMessage("Failed to determine locale. As a fallback Syncplay is using the following radix character: \".\".")
|
||||
self.radixChar = "."
|
||||
|
||||
self._durationAsk = threading.Event()
|
||||
self._filenameAsk = threading.Event()
|
||||
@ -91,8 +98,7 @@ class VlcPlayer(BasePlayer):
|
||||
self._listener.sendLine("set-rate: {:.2n}".format(value))
|
||||
|
||||
def setPosition(self, value):
|
||||
self._position = value
|
||||
self._listener.sendLine("set-position: {}".format(value))
|
||||
self._listener.sendLine("set-position: {}".format(value).replace(".",self.radixChar))
|
||||
|
||||
def setPaused(self, value):
|
||||
self._paused = value
|
||||
@ -112,7 +118,7 @@ class VlcPlayer(BasePlayer):
|
||||
def _isASCII (self, s):
|
||||
return all(ord(c) < 128 for c in s)
|
||||
|
||||
def openFile(self, filePath):
|
||||
def openFile(self, filePath, resetPosition=False):
|
||||
if self._isASCII(filePath):
|
||||
self._listener.sendLine('load-file: {}'.format(filePath.encode('ascii', 'ignore')))
|
||||
else:
|
||||
@ -125,6 +131,7 @@ class VlcPlayer(BasePlayer):
|
||||
self._listener.sendLine("get-filename")
|
||||
|
||||
def lineReceived(self, line):
|
||||
self._client.ui.showDebugMessage("player >> {}".format(line))
|
||||
match, name, value = self.RE_ANSWER.match(line), "", ""
|
||||
if match:
|
||||
name, value = match.group('command'), match.group('argument')
|
||||
@ -193,6 +200,10 @@ class VlcPlayer(BasePlayer):
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def getPlayerPathErrors(playerPath, filePath):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def getIconPath(path):
|
||||
return constants.VLC_ICONPATH
|
||||
@ -322,15 +333,14 @@ class VlcPlayer(BasePlayer):
|
||||
self.__playerController.drop()
|
||||
|
||||
def found_terminator(self):
|
||||
# print "received: {}".format("".join(self._ibuffer))
|
||||
self.__playerController.lineReceived("".join(self._ibuffer))
|
||||
self._ibuffer = []
|
||||
|
||||
def sendLine(self, line):
|
||||
if self.connected:
|
||||
# print "send: {}".format(line)
|
||||
try:
|
||||
self.push(line + "\n")
|
||||
self._client.ui.showDebugMessage("player >> {}".format(line))
|
||||
except:
|
||||
pass
|
||||
if line == "close-vlc":
|
||||
|
||||
@ -30,6 +30,7 @@ class JSONCommandProtocol(LineReceiver):
|
||||
if not line:
|
||||
return
|
||||
try:
|
||||
self.showDebugMessage("client/server << {}".format(line))
|
||||
messages = json.loads(line)
|
||||
except:
|
||||
self.dropWithError(getMessage("not-json-server-error").format(line))
|
||||
@ -39,6 +40,7 @@ class JSONCommandProtocol(LineReceiver):
|
||||
def sendMessage(self, dict_):
|
||||
line = json.dumps(dict_)
|
||||
self.sendLine(line)
|
||||
self.showDebugMessage("client/server >> {}".format(line))
|
||||
|
||||
def drop(self):
|
||||
self.transport.loseConnection()
|
||||
@ -55,6 +57,9 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
self.logged = False
|
||||
self._pingService = PingService()
|
||||
|
||||
def showDebugMessage(self, line):
|
||||
self._client.ui.showDebugMessage(line)
|
||||
|
||||
def connectionMade(self):
|
||||
self._client.initProtocol(self)
|
||||
self.sendHello()
|
||||
@ -71,6 +76,7 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
username = hello["username"] if hello.has_key("username") else None
|
||||
roomName = hello["room"]["name"] if hello.has_key("room") else None
|
||||
version = hello["version"] if hello.has_key("version") else None
|
||||
version = hello["realversion"] if hello.has_key("realversion") else version # Used for 1.2.X compatibility
|
||||
motd = hello["motd"] if hello.has_key("motd") else None
|
||||
return username, roomName, version, motd
|
||||
|
||||
@ -78,8 +84,6 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
username, roomName, version, motd = self._extractHelloArguments(hello)
|
||||
if not username or not roomName or not version:
|
||||
self.dropWithError(getMessage("hello-server-error").format(hello))
|
||||
elif version.split(".")[0:2] != syncplay.version.split(".")[0:2]:
|
||||
self.dropWithError(getMessage("version-mismatch-server-error".format(hello)))
|
||||
else:
|
||||
self._client.setUsername(username)
|
||||
self._client.setRoom(roomName)
|
||||
@ -87,7 +91,9 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
if motd:
|
||||
self._client.ui.showMessage(motd, True, True)
|
||||
self._client.ui.showMessage(getMessage("connected-successful-notification"))
|
||||
self._client.connected()
|
||||
self._client.sendFile()
|
||||
self._client.setServerVersion(version)
|
||||
|
||||
def sendHello(self):
|
||||
hello = {}
|
||||
@ -96,7 +102,8 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
if password: hello["password"] = password
|
||||
room = self._client.getRoom()
|
||||
if room: hello["room"] = {"name" :room}
|
||||
hello["version"] = syncplay.version
|
||||
hello["version"] = "1.2.255" # Used so newer clients work on 1.2.X server
|
||||
hello["realversion"] = syncplay.version
|
||||
self.sendMessage({"Hello": hello})
|
||||
|
||||
def _SetUser(self, users):
|
||||
@ -114,13 +121,21 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
self._client.userlist.modUser(username, room, file_)
|
||||
|
||||
def handleSet(self, settings):
|
||||
for set_ in settings.iteritems():
|
||||
command = set_[0]
|
||||
for (command, values) in settings.iteritems():
|
||||
if command == "room":
|
||||
roomName = set_[1]["name"] if set_[1].has_key("name") else None
|
||||
roomName = values["name"] if values.has_key("name") else None
|
||||
self._client.setRoom(roomName)
|
||||
elif command == "user":
|
||||
self._SetUser(set_[1])
|
||||
self._SetUser(values)
|
||||
elif command == "controllerAuth":
|
||||
if values['success']:
|
||||
self._client.controllerIdentificationSuccess(values["user"], values["room"])
|
||||
else:
|
||||
self._client.controllerIdentificationError(values["user"], values["room"])
|
||||
elif command == "newControlledRoom":
|
||||
controlPassword = values['password']
|
||||
roomName = values['roomName']
|
||||
self._client.controlledRoomCreated(roomName, controlPassword)
|
||||
|
||||
def sendSet(self, setting):
|
||||
self.sendMessage({"Set": setting})
|
||||
@ -142,7 +157,8 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
for user in room[1].iteritems():
|
||||
userName = user[0]
|
||||
file_ = user[1]['file'] if user[1]['file'] <> {} else None
|
||||
self._client.userlist.addUser(userName, roomName, file_, noMessage=True)
|
||||
isController = user[1]['controller'] if 'controller' in user[1] else False
|
||||
self._client.userlist.addUser(userName, roomName, file_, noMessage=True, isController=isController)
|
||||
self._client.userlist.showUserList()
|
||||
|
||||
def sendList(self):
|
||||
@ -210,8 +226,16 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
state["ignoringOnTheFly"]["client"] = self.clientIgnoringOnTheFly
|
||||
self.sendMessage({"State": state})
|
||||
|
||||
def requestControlledRoom(self, room, password):
|
||||
self.sendSet({
|
||||
"controllerAuth": {
|
||||
"room": room,
|
||||
"password": password
|
||||
}
|
||||
})
|
||||
|
||||
def handleError(self, error):
|
||||
self.dropWithError(error["message"]) # TODO: more processing and fallbacking
|
||||
self.dropWithError(error["message"])
|
||||
|
||||
def sendError(self, message):
|
||||
self.sendMessage({"Error": {"message": message}})
|
||||
@ -241,6 +265,9 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
return f(self, *args, **kwds)
|
||||
return wrapper
|
||||
|
||||
def showDebugMessage(self, line):
|
||||
pass
|
||||
|
||||
def dropWithError(self, error):
|
||||
print getMessage("client-drop-server-error").format(self.transport.getPeer().host, error)
|
||||
self.sendError(error)
|
||||
@ -253,7 +280,7 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
return self._logged
|
||||
|
||||
def _extractHelloArguments(self, hello):
|
||||
roomName, roomPassword = None, None
|
||||
roomName = None
|
||||
username = hello["username"] if hello.has_key("username") else None
|
||||
username = username.strip()
|
||||
serverPassword = hello["password"] if hello.has_key("password") else None
|
||||
@ -261,9 +288,9 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
if room:
|
||||
roomName = room["name"] if room.has_key("name") else None
|
||||
roomName = roomName.strip()
|
||||
roomPassword = room["password"] if room.has_key("password") else None
|
||||
version = hello["version"] if hello.has_key("version") else None
|
||||
return username, serverPassword, roomName, roomPassword, version
|
||||
version = hello["realversion"] if hello.has_key("realversion") else version
|
||||
return username, serverPassword, roomName, version
|
||||
|
||||
def _checkPassword(self, serverPassword):
|
||||
if self._factory.password:
|
||||
@ -276,15 +303,14 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
return True
|
||||
|
||||
def handleHello(self, hello):
|
||||
username, serverPassword, roomName, roomPassword, version = self._extractHelloArguments(hello)
|
||||
username, serverPassword, roomName, version = self._extractHelloArguments(hello)
|
||||
if not username or not roomName or not version:
|
||||
self.dropWithError(getMessage("hello-server-error"))
|
||||
elif version.split(".")[0:2] != syncplay.version.split(".")[0:2]:
|
||||
self.dropWithError(getMessage("version-mismatch-server-error"))
|
||||
return
|
||||
else:
|
||||
if not self._checkPassword(serverPassword):
|
||||
return
|
||||
self._factory.addWatcher(self, username, roomName, roomPassword)
|
||||
self._factory.addWatcher(self, username, roomName)
|
||||
self._logged = True
|
||||
self.sendHello(version)
|
||||
|
||||
@ -298,7 +324,8 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
userIp = self.transport.getPeer().host
|
||||
room = self._watcher.getRoom()
|
||||
if room: hello["room"] = {"name": room.getName()}
|
||||
hello["version"] = syncplay.version
|
||||
hello["version"] = clientVersion # Used so 1.2.X client works on newer server
|
||||
hello["realversion"] = syncplay.version
|
||||
hello["motd"] = self._factory.getMotd(userIp, username, room, clientVersion)
|
||||
self.sendMessage({"Hello": hello})
|
||||
|
||||
@ -311,14 +338,34 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
self._factory.setWatcherRoom(self._watcher, roomName)
|
||||
elif command == "file":
|
||||
self._watcher.setFile(set_[1])
|
||||
elif command == "controllerAuth":
|
||||
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)
|
||||
|
||||
def sendSet(self, setting):
|
||||
self.sendMessage({"Set": setting})
|
||||
|
||||
def sendNewControlledRoom(self, roomName, password):
|
||||
self.sendSet({
|
||||
"newControlledRoom": {
|
||||
"password": password,
|
||||
"roomName": roomName
|
||||
}
|
||||
})
|
||||
|
||||
def sendControlledRoomAuthStatus(self, success, username, roomname):
|
||||
self.sendSet({
|
||||
"controllerAuth": {
|
||||
"user": username,
|
||||
"room": roomname,
|
||||
"success": success
|
||||
}
|
||||
})
|
||||
|
||||
def sendUserSetting(self, username, room, file_, event):
|
||||
room = {"name": room.getName()}
|
||||
user = {}
|
||||
user[username] = {}
|
||||
user = {username: {}}
|
||||
user[username]["room"] = room
|
||||
if file_:
|
||||
user[username]["file"] = file_
|
||||
@ -331,7 +378,11 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
if room:
|
||||
if room.getName() not in userlist:
|
||||
userlist[room.getName()] = {}
|
||||
userFile = { "position": 0, "file": watcher.getFile() if watcher.getFile() else {} }
|
||||
userFile = {
|
||||
"position": 0,
|
||||
"file": watcher.getFile() if watcher.getFile() else {},
|
||||
"controller": watcher.isController()
|
||||
}
|
||||
userlist[room.getName()][watcher.getName()] = userFile
|
||||
|
||||
def sendList(self):
|
||||
@ -354,8 +405,8 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
"position": position if position else 0,
|
||||
"paused": paused,
|
||||
"doSeek": doSeek,
|
||||
"setBy": setBy.getName()
|
||||
}
|
||||
"setBy": setBy.getName() if setBy else None
|
||||
}
|
||||
ping = {
|
||||
"latencyCalculation": self._pingService.newTimestamp(),
|
||||
"serverRtt": self._pingService.getRtt()
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import hashlib
|
||||
import random
|
||||
from twisted.internet import task, reactor
|
||||
from twisted.internet.protocol import Factory
|
||||
import syncplay
|
||||
@ -10,13 +11,18 @@ import codecs
|
||||
import os
|
||||
from string import Template
|
||||
import argparse
|
||||
from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion
|
||||
|
||||
class SyncFactory(Factory):
|
||||
def __init__(self, password='', motdFilePath=None, isolateRooms=False):
|
||||
def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None):
|
||||
print getMessage("welcome-server-notification").format(syncplay.version)
|
||||
if password:
|
||||
password = hashlib.md5(password).hexdigest()
|
||||
self.password = password
|
||||
if salt is None:
|
||||
salt = RandomStringGenerator.generate_server_salt()
|
||||
print getMessage("no-salt-notification").format(salt)
|
||||
self._salt = salt
|
||||
self._motdFilePath = motdFilePath
|
||||
if not isolateRooms:
|
||||
self._roomManager = RoomManager()
|
||||
@ -36,7 +42,7 @@ class SyncFactory(Factory):
|
||||
def getMotd(self, userIp, username, room, clientVersion):
|
||||
oldClient = False
|
||||
if constants.WARN_OLD_CLIENTS:
|
||||
if int(clientVersion.replace(".", "")) < int(constants.RECENT_CLIENT_THRESHOLD.replace(".", "")):
|
||||
if not meetsMinVersion(clientVersion, constants.RECENT_CLIENT_THRESHOLD):
|
||||
oldClient = True
|
||||
if self._motdFilePath and os.path.isfile(self._motdFilePath):
|
||||
tmpl = codecs.open(self._motdFilePath, "r", "utf-8-sig").read()
|
||||
@ -54,7 +60,7 @@ class SyncFactory(Factory):
|
||||
else:
|
||||
return ""
|
||||
|
||||
def addWatcher(self, watcherProtocol, username, roomName, roomPassword):
|
||||
def addWatcher(self, watcherProtocol, username, roomName):
|
||||
username = self._roomManager.findFreeUsername(username)
|
||||
watcher = Watcher(self, watcherProtocol, username)
|
||||
self.setWatcherRoom(watcher, roomName, asJoin=True)
|
||||
@ -65,13 +71,16 @@ class SyncFactory(Factory):
|
||||
self.sendJoinMessage(watcher)
|
||||
else:
|
||||
self.sendRoomSwitchMessage(watcher)
|
||||
if RoomPasswordProvider.isControlledRoom(roomName):
|
||||
for controller in watcher.getRoom().getControllers():
|
||||
watcher.sendControlledRoomAuthStatus(True, controller, roomName)
|
||||
|
||||
def sendRoomSwitchMessage(self, watcher):
|
||||
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, None)
|
||||
self._roomManager.broadcast(watcher, l)
|
||||
|
||||
def removeWatcher(self, watcher):
|
||||
if watcher.getRoom():
|
||||
if watcher and watcher.getRoom():
|
||||
self.sendLeftMessage(watcher)
|
||||
self._roomManager.removeWatcher(watcher)
|
||||
|
||||
@ -83,21 +92,40 @@ class SyncFactory(Factory):
|
||||
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"joined": True}) if w != watcher else None
|
||||
self._roomManager.broadcast(watcher, l)
|
||||
|
||||
def sendFileUpdate(self, watcher, file_):
|
||||
def sendFileUpdate(self, watcher):
|
||||
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), watcher.getFile(), None)
|
||||
self._roomManager.broadcast(watcher, l)
|
||||
|
||||
def forcePositionUpdate(self, room, watcher, doSeek):
|
||||
def forcePositionUpdate(self, watcher, doSeek, watcherPauseState):
|
||||
room = watcher.getRoom()
|
||||
paused, position = room.isPaused(), watcher.getPosition()
|
||||
setBy = watcher
|
||||
room.setPosition(watcher.getPosition(), setBy)
|
||||
l = lambda w: w.sendState(position, paused, doSeek, setBy, True)
|
||||
self._roomManager.broadcastRoom(watcher, l)
|
||||
if room.canControl(watcher):
|
||||
paused, position = room.isPaused(), watcher.getPosition()
|
||||
setBy = watcher
|
||||
l = lambda w: w.sendState(position, paused, doSeek, setBy, True)
|
||||
room.setPosition(watcher.getPosition(), setBy)
|
||||
self._roomManager.broadcastRoom(watcher, l)
|
||||
else:
|
||||
watcher.sendState(room.getPosition(), watcherPauseState, False, watcher, True) # Fixes BC break with 1.2.x
|
||||
watcher.sendState(room.getPosition(), room.isPaused(), True, room.getSetBy(), True)
|
||||
|
||||
def getAllWatchersForUser(self, forUser):
|
||||
return self._roomManager.getAllWatchersForUser(forUser)
|
||||
|
||||
def authRoomController(self, watcher, password, roomBaseName=None):
|
||||
room = watcher.getRoom()
|
||||
roomName = roomBaseName if roomBaseName else room.getName()
|
||||
try:
|
||||
success = RoomPasswordProvider.check(roomName, password, self._salt)
|
||||
if success:
|
||||
watcher.getRoom().addController(watcher)
|
||||
self._roomManager.broadcast(watcher, lambda w: w.sendControlledRoomAuthStatus(success, watcher.getName(), room._name))
|
||||
except NotControlledRoom:
|
||||
newName = RoomPasswordProvider.getControlledRoomName(roomName, password, self._salt)
|
||||
watcher.sendNewControlledRoom(newName, password)
|
||||
except ValueError:
|
||||
self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(False, watcher.getName(), room._name))
|
||||
|
||||
|
||||
class RoomManager(object):
|
||||
def __init__(self):
|
||||
self._rooms = {}
|
||||
@ -135,7 +163,10 @@ class RoomManager(object):
|
||||
if roomName in self._rooms:
|
||||
return self._rooms[roomName]
|
||||
else:
|
||||
room = Room(roomName)
|
||||
if RoomPasswordProvider.isControlledRoom(roomName):
|
||||
room = ControlledRoom(roomName)
|
||||
else:
|
||||
room = Room(roomName)
|
||||
self._rooms[roomName] = room
|
||||
return room
|
||||
|
||||
@ -228,6 +259,44 @@ class Room(object):
|
||||
def getSetBy(self):
|
||||
return self._setBy
|
||||
|
||||
def canControl(self, watcher):
|
||||
return True
|
||||
|
||||
class ControlledRoom(Room):
|
||||
def __init__(self, name):
|
||||
Room.__init__(self, name)
|
||||
self._controllers = {}
|
||||
|
||||
def getPosition(self):
|
||||
if self._controllers:
|
||||
watcher = min(self._controllers.values())
|
||||
self._setBy = watcher
|
||||
return watcher.getPosition()
|
||||
else:
|
||||
return 0
|
||||
|
||||
def addController(self, watcher):
|
||||
self._controllers[watcher.getName()] = watcher
|
||||
|
||||
def removeWatcher(self, watcher):
|
||||
Room.removeWatcher(self, watcher)
|
||||
if watcher.getName() in self._controllers:
|
||||
del self._controllers[watcher.getName()]
|
||||
|
||||
def setPaused(self, paused=Room.STATE_PAUSED, setBy=None):
|
||||
if self.canControl(setBy):
|
||||
Room.setPaused(self, paused, setBy)
|
||||
|
||||
def setPosition(self, position, setBy=None):
|
||||
if self.canControl(setBy):
|
||||
Room.setPosition(self, position, setBy)
|
||||
|
||||
def canControl(self, watcher):
|
||||
return watcher.getName() in self._controllers
|
||||
|
||||
def getControllers(self):
|
||||
return self._controllers
|
||||
|
||||
class Watcher(object):
|
||||
def __init__(self, server, connector, name):
|
||||
self._server = server
|
||||
@ -243,7 +312,7 @@ class Watcher(object):
|
||||
|
||||
def setFile(self, file_):
|
||||
self._file = file_
|
||||
self._server.sendFileUpdate(self, file_)
|
||||
self._server.sendFileUpdate(self)
|
||||
|
||||
def setRoom(self, room):
|
||||
self._room = room
|
||||
@ -277,16 +346,22 @@ class Watcher(object):
|
||||
def sendSetting(self, user, room, file_, event):
|
||||
self._connector.sendUserSetting(user, room, file_, event)
|
||||
|
||||
def sendNewControlledRoom(self, roomBaseName, password):
|
||||
self._connector.sendNewControlledRoom(roomBaseName, password)
|
||||
|
||||
def sendControlledRoomAuthStatus(self, success, username, room):
|
||||
self._connector.sendControlledRoomAuthStatus(success, username, room)
|
||||
|
||||
def __lt__(self, b):
|
||||
if self.getPosition() is None or self._file is None:
|
||||
return False
|
||||
if b.getPosition is None or b._file is None:
|
||||
if b.getPosition() is None or b.getFile() is None:
|
||||
return True
|
||||
return self.getPosition() < b.getPosition()
|
||||
|
||||
def _scheduleSendState(self):
|
||||
self._sendStateTimer = task.LoopingCall(self._askForStateUpdate)
|
||||
self._sendStateTimer.start(constants.SERVER_STATE_INTERVAL, True)
|
||||
self._sendStateTimer.start(constants.SERVER_STATE_INTERVAL)
|
||||
|
||||
def _askForStateUpdate(self, doSeek=False, forcedUpdate=False):
|
||||
self._server.sendState(self, doSeek, forcedUpdate)
|
||||
@ -313,18 +388,25 @@ class Watcher(object):
|
||||
return False
|
||||
return self._room.isPaused() and not paused or not self._room.isPaused() and paused
|
||||
|
||||
def _updatePositionByAge(self, messageAge, paused, position):
|
||||
if not paused:
|
||||
position += messageAge
|
||||
return position
|
||||
|
||||
def updateState(self, position, paused, doSeek, messageAge):
|
||||
pauseChanged = self.__hasPauseChanged(paused)
|
||||
self._lastUpdatedOn = time.time()
|
||||
if pauseChanged:
|
||||
self.getRoom().setPaused(Room.STATE_PAUSED if paused else Room.STATE_PLAYING, self)
|
||||
if position is not None:
|
||||
if not paused:
|
||||
position += messageAge
|
||||
position = self._updatePositionByAge(messageAge, paused, position)
|
||||
self.setPosition(position)
|
||||
if doSeek or pauseChanged:
|
||||
self._server.forcePositionUpdate(self._room, self, doSeek)
|
||||
self._server.forcePositionUpdate(self, doSeek, paused)
|
||||
|
||||
def isController(self):
|
||||
return RoomPasswordProvider.isControlledRoom(self._room.getName()) \
|
||||
and self._room.canControl(self)
|
||||
|
||||
class ConfigurationGetter(object):
|
||||
def getConfiguration(self):
|
||||
@ -340,4 +422,5 @@ 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('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-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"))
|
||||
@ -36,8 +36,10 @@ class ConfigurationGetter(object):
|
||||
"playerClass": None,
|
||||
"slowdownThreshold": constants.DEFAULT_SLOWDOWN_KICKIN_THRESHOLD,
|
||||
"rewindThreshold": constants.DEFAULT_REWIND_THRESHOLD,
|
||||
"fastforwardThreshold": constants.DEFAULT_FASTFORWARD_THRESHOLD,
|
||||
"rewindOnDesync": True,
|
||||
"slowOnDesync": True,
|
||||
"fastforwardOnDesync": True,
|
||||
"dontSlowDownWithMe": False,
|
||||
"filenamePrivacyMode": constants.PRIVACY_SENDRAW_MODE,
|
||||
"filesizePrivacyMode": constants.PRIVACY_SENDRAW_MODE,
|
||||
@ -50,9 +52,8 @@ class ConfigurationGetter(object):
|
||||
"showSlowdownOSD" : True,
|
||||
"showDifferentRoomOSD" : False,
|
||||
"showSameRoomOSD" : True,
|
||||
"showNonControllerOSD" : False,
|
||||
"showContactInfo" : True,
|
||||
"showButtonLabels" : True,
|
||||
"showTooltips" : True,
|
||||
"showDurationNotification" : True
|
||||
}
|
||||
|
||||
@ -87,21 +88,21 @@ class ConfigurationGetter(object):
|
||||
"showSlowdownOSD",
|
||||
"showDifferentRoomOSD",
|
||||
"showSameRoomOSD",
|
||||
"showNonControllerOSD",
|
||||
"showContactInfo" ,
|
||||
"showButtonLabels",
|
||||
"showTooltips",
|
||||
"showDurationNotification"
|
||||
]
|
||||
|
||||
self._numeric = [
|
||||
"slowdownThreshold",
|
||||
"rewindThreshold"
|
||||
"rewindThreshold",
|
||||
"fastforwardThreshold",
|
||||
]
|
||||
|
||||
self._iniStructure = {
|
||||
"server_data": ["host", "port", "password"],
|
||||
"client_settings": ["name", "room", "playerPath", "slowdownThreshold", "rewindThreshold", "slowOnDesync", "rewindOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "pauseOnLeave"],
|
||||
"gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showContactInfo" , "showButtonLabels", "showTooltips", "showDurationNotification"],
|
||||
"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"]
|
||||
}
|
||||
|
||||
@ -143,20 +144,20 @@ class ConfigurationGetter(object):
|
||||
if player:
|
||||
self._config["playerClass"] = player
|
||||
else:
|
||||
raise InvalidConfigValue("Player path is not set properly")
|
||||
if player.__name__ in ['MpvPlayer', 'MplayerPlayer']:
|
||||
if not self._config['file']:
|
||||
raise InvalidConfigValue("File must be selected before starting your player")
|
||||
raise InvalidConfigValue(getMessage("player-path-config-error"))
|
||||
playerPathErrors = player.getPlayerPathErrors(self._config["playerPath"], self._config['file'] if self._config['file'] else None)
|
||||
if playerPathErrors:
|
||||
raise InvalidConfigValue(playerPathErrors)
|
||||
elif key == "host":
|
||||
self._config["host"], self._config["port"] = self._splitPortAndHost(self._config["host"])
|
||||
hostNotValid = (self._config["host"] == "" or self._config["host"] is None)
|
||||
portNotValid = (_isPortValid(self._config["port"]) == False)
|
||||
if hostNotValid:
|
||||
raise InvalidConfigValue("Hostname can't be empty")
|
||||
raise InvalidConfigValue(getMessage("no-hostname-config-error"))
|
||||
elif portNotValid:
|
||||
raise InvalidConfigValue("Port must be valid")
|
||||
raise InvalidConfigValue(getMessage("invalid-port-config-error"))
|
||||
elif self._config[key] == "" or self._config[key] is None:
|
||||
raise InvalidConfigValue("{} can't be empty".format(key.capitalize()))
|
||||
raise InvalidConfigValue(getMessage("empty-value-config-error").format(key.capitalize()))
|
||||
|
||||
def _overrideConfigWithArgs(self, args):
|
||||
for key, val in vars(args).items():
|
||||
@ -385,4 +386,4 @@ class SafeConfigParserUnicode(SafeConfigParser):
|
||||
if (value is not None) or (self._optcre == self.OPTCRE):
|
||||
key = " = ".join((key, unicode(value).replace('\n', '\n\t')))
|
||||
fp.write("%s\n" % key)
|
||||
fp.write("\n")
|
||||
fp.write("\n")
|
||||
@ -5,7 +5,7 @@ from syncplay.players.playerFactory import PlayerFactory
|
||||
|
||||
import os
|
||||
import sys
|
||||
from syncplay.messages import getMessage
|
||||
from syncplay.messages import getMessage, getLanguages, setLanguage
|
||||
from syncplay import constants
|
||||
|
||||
class GuiConfiguration:
|
||||
@ -15,7 +15,6 @@ class GuiConfiguration:
|
||||
self._availablePlayerPaths = []
|
||||
self.error = error
|
||||
|
||||
|
||||
def run(self):
|
||||
if QCoreApplication.instance() is None:
|
||||
self.app = QtGui.QApplication(sys.argv)
|
||||
@ -71,36 +70,63 @@ class ConfigDialog(QtGui.QDialog):
|
||||
def openHelp(self):
|
||||
self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/client/")
|
||||
|
||||
def _isURL(self, path):
|
||||
if path is None:
|
||||
return False
|
||||
|
||||
if "http://" in path:
|
||||
return True
|
||||
|
||||
def safenormcaseandpath(self, path):
|
||||
if self._isURL(path):
|
||||
return path
|
||||
else:
|
||||
return os.path.normcase(os.path.normpath(path))
|
||||
|
||||
def _tryToFillPlayerPath(self, playerpath, playerpathlist):
|
||||
settings = QSettings("Syncplay", "PlayerList")
|
||||
settings.beginGroup("PlayerList")
|
||||
savedPlayers = settings.value("PlayerList", [])
|
||||
if not isinstance(savedPlayers, list):
|
||||
savedPlayers = []
|
||||
playerpathlist = list(set(os.path.normcase(os.path.normpath(path)) for path in set(playerpathlist + savedPlayers)))
|
||||
else:
|
||||
for i, savedPlayer in enumerate(savedPlayers):
|
||||
savedPlayers[i] = self.safenormcaseandpath(savedPlayer)
|
||||
playerpathlist = list(set(playerpathlist + savedPlayers))
|
||||
settings.endGroup()
|
||||
foundpath = ""
|
||||
|
||||
if playerpath != None and playerpath != "":
|
||||
if not os.path.isfile(playerpath):
|
||||
expandedpath = PlayerFactory().getExpandedPlayerPathByPath(playerpath)
|
||||
if expandedpath != None and os.path.isfile(expandedpath):
|
||||
playerpath = expandedpath
|
||||
|
||||
if os.path.isfile(playerpath):
|
||||
if self._isURL(playerpath):
|
||||
foundpath = playerpath
|
||||
self.executablepathCombobox.addItem(foundpath)
|
||||
|
||||
else:
|
||||
if not os.path.isfile(playerpath):
|
||||
expandedpath = PlayerFactory().getExpandedPlayerPathByPath(playerpath)
|
||||
if expandedpath != None and os.path.isfile(expandedpath):
|
||||
playerpath = expandedpath
|
||||
|
||||
if os.path.isfile(playerpath):
|
||||
foundpath = playerpath
|
||||
self.executablepathCombobox.addItem(foundpath)
|
||||
|
||||
for path in playerpathlist:
|
||||
if os.path.isfile(path) and os.path.normcase(os.path.normpath(path)) != os.path.normcase(os.path.normpath(foundpath)):
|
||||
if self._isURL(path):
|
||||
if foundpath == "":
|
||||
foundpath = path
|
||||
if path != playerpath:
|
||||
self.executablepathCombobox.addItem(path)
|
||||
|
||||
elif os.path.isfile(path) and os.path.normcase(os.path.normpath(path)) != os.path.normcase(os.path.normpath(foundpath)):
|
||||
self.executablepathCombobox.addItem(path)
|
||||
if foundpath == "":
|
||||
foundpath = path
|
||||
|
||||
if foundpath != "":
|
||||
settings.beginGroup("PlayerList")
|
||||
playerpathlist.append(os.path.normcase(os.path.normpath(foundpath)))
|
||||
settings.setValue("PlayerList", list(set(os.path.normcase(os.path.normpath(path)) for path in set(playerpathlist))))
|
||||
playerpathlist.append(self.safenormcaseandpath(foundpath))
|
||||
settings.setValue("PlayerList", list(set(playerpathlist)))
|
||||
settings.endGroup()
|
||||
return foundpath
|
||||
|
||||
@ -113,6 +139,9 @@ class ConfigDialog(QtGui.QDialog):
|
||||
else:
|
||||
self.executableiconLabel.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage()))
|
||||
|
||||
def languageChanged(self):
|
||||
setLanguage(unicode(self.languageCombobox.itemData(self.languageCombobox.currentIndex())))
|
||||
QtGui.QMessageBox.information(self, "Syncplay", getMessage("language-changed-msgbox-label"))
|
||||
|
||||
def browsePlayerpath(self):
|
||||
options = QtGui.QFileDialog.Options()
|
||||
@ -194,7 +223,8 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.config['host'] = self.hostTextbox.text() if ":" in self.hostTextbox.text() else self.hostTextbox.text() + ":" + unicode(constants.DEFAULT_PORT)
|
||||
else:
|
||||
self.config['host'] = None
|
||||
self.config['playerPath'] = unicode(self.executablepathCombobox.currentText())
|
||||
self.config['playerPath'] = unicode(self.safenormcaseandpath(self.executablepathCombobox.currentText()))
|
||||
self.config['language'] = unicode(self.languageCombobox.itemData(self.languageCombobox.currentIndex()))
|
||||
if self.mediapathTextbox.text() == "":
|
||||
self.config['file'] = None
|
||||
elif os.path.isfile(os.path.abspath(self.mediapathTextbox.text())):
|
||||
@ -206,8 +236,11 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.slowdownThresholdSpinbox.value = constants.DEFAULT_SLOWDOWN_KICKIN_THRESHOLD
|
||||
if not self.rewindThresholdSpinbox.text:
|
||||
self.rewindThresholdSpinbox.value = constants.DEFAULT_REWIND_THRESHOLD
|
||||
if not self.fastforwardThresholdSpinbox.text:
|
||||
self.fastforwardThresholdSpinbox.value = constants.DEFAULT_FASTFORWARD_THRESHOLD
|
||||
self.config['slowdownThreshold'] = self.slowdownThresholdSpinbox.value()
|
||||
self.config['rewindThreshold'] = self.rewindThresholdSpinbox.value()
|
||||
self.config['fastforwardThreshold'] = self.fastforwardThresholdSpinbox.value()
|
||||
|
||||
self.pressedclosebutton = True
|
||||
self.close()
|
||||
@ -216,8 +249,10 @@ class ConfigDialog(QtGui.QDialog):
|
||||
def closeEvent(self, event):
|
||||
if self.pressedclosebutton == False:
|
||||
sys.exit()
|
||||
raise GuiConfiguration.WindowClosed
|
||||
event.accept()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == Qt.Key_Escape:
|
||||
sys.exit()
|
||||
|
||||
def dragEnterEvent(self, event):
|
||||
data = event.mimeData()
|
||||
@ -229,10 +264,7 @@ class ConfigDialog(QtGui.QDialog):
|
||||
data = event.mimeData()
|
||||
urls = data.urls()
|
||||
if urls and urls[0].scheme() == 'file':
|
||||
if sys.platform.startswith('win'):
|
||||
dropfilepath = unicode(urls[0].path())[1:] # Removes starting slash
|
||||
else:
|
||||
dropfilepath = unicode(urls[0].path())
|
||||
dropfilepath = os.path.abspath(unicode(event.mimeData().urls()[0].toLocalFile()))
|
||||
if dropfilepath[-4:].lower() == ".exe":
|
||||
self.executablepathCombobox.setEditText(dropfilepath)
|
||||
else:
|
||||
@ -347,6 +379,7 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.connectionSettingsLayout.addWidget(self.defaultroomLabel, 3, 0)
|
||||
self.connectionSettingsLayout.addWidget(self.defaultroomTextbox, 3, 1)
|
||||
self.connectionSettingsGroup.setLayout(self.connectionSettingsLayout)
|
||||
self.connectionSettingsGroup.setMaximumHeight(self.connectionSettingsGroup.minimumSizeHint().height())
|
||||
|
||||
self.mediaplayerSettingsGroup = QtGui.QGroupBox(getMessage("media-setting-title"))
|
||||
self.executableiconImage = QtGui.QImage()
|
||||
@ -398,8 +431,11 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.errorLabel.setAlignment(Qt.AlignCenter)
|
||||
|
||||
self.basicOptionsLayout.addWidget(self.errorLabel, 0, 0)
|
||||
self.connectionSettingsGroup.setMaximumHeight(self.connectionSettingsGroup.minimumSizeHint().height())
|
||||
self.basicOptionsLayout.setAlignment(Qt.AlignTop)
|
||||
self.basicOptionsLayout.addWidget(self.connectionSettingsGroup)
|
||||
self.basicOptionsLayout.addSpacing(12)
|
||||
self.basicOptionsLayout.addSpacing(17)
|
||||
self.mediaplayerSettingsGroup.setMaximumHeight(self.mediaplayerSettingsGroup.minimumSizeHint().height())
|
||||
self.basicOptionsLayout.addWidget(self.mediaplayerSettingsGroup)
|
||||
|
||||
self.basicOptionsFrame.setLayout(self.basicOptionsLayout)
|
||||
@ -409,7 +445,7 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.syncSettingsFrame = QtGui.QFrame()
|
||||
self.syncSettingsLayout = QtGui.QVBoxLayout()
|
||||
|
||||
self.desyncSettingsGroup = QtGui.QGroupBox("If others are lagging behind...")
|
||||
self.desyncSettingsGroup = QtGui.QGroupBox(getMessage("sync-lagging-title"))
|
||||
self.desyncOptionsFrame = QtGui.QFrame()
|
||||
self.desyncSettingsOptionsLayout = QtGui.QHBoxLayout()
|
||||
config = self.config
|
||||
@ -418,6 +454,8 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.slowdownCheckbox.setObjectName("slowOnDesync")
|
||||
self.rewindCheckbox = QCheckBox(getMessage("rewindondesync-label"))
|
||||
self.rewindCheckbox.setObjectName("rewindOnDesync")
|
||||
self.fastforwardCheckbox = QCheckBox(getMessage("fastforwardondesync-label"))
|
||||
self.fastforwardCheckbox.setObjectName("fastforwardOnDesync")
|
||||
|
||||
self.spaceLabel = QLabel()
|
||||
self.spaceLabel.setFixedHeight(5)
|
||||
@ -429,7 +467,7 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.desyncFrame.setMidLineWidth(0)
|
||||
|
||||
self.slowdownThresholdLabel = QLabel(getMessage("slowdown-threshold-label"), self)
|
||||
self.slowdownThresholdLabel.setStyleSheet(constants.STYLE_SUBLABEL.format(self.posixresourcespath + "bullet_black.png"))
|
||||
self.slowdownThresholdLabel.setStyleSheet(constants.STYLE_SUBLABEL.format(self.posixresourcespath + "chevrons_right.png"))
|
||||
|
||||
self.slowdownThresholdSpinbox = QDoubleSpinBox()
|
||||
try:
|
||||
@ -446,7 +484,7 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.slowdownThresholdSpinbox.adjustSize()
|
||||
|
||||
self.rewindThresholdLabel = QLabel(getMessage("rewind-threshold-label"), self)
|
||||
self.rewindThresholdLabel.setStyleSheet(constants.STYLE_SUBLABEL.format(self.posixresourcespath + "bullet_black.png"))
|
||||
self.rewindThresholdLabel.setStyleSheet(constants.STYLE_SUBLABEL.format(self.posixresourcespath + "chevrons_right.png"))
|
||||
self.rewindThresholdSpinbox = QDoubleSpinBox()
|
||||
try:
|
||||
rewindThreshold = float(config['rewindThreshold'])
|
||||
@ -461,6 +499,22 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.rewindThresholdSpinbox.setSuffix(getMessage("seconds-suffix"))
|
||||
self.rewindThresholdSpinbox.adjustSize()
|
||||
|
||||
self.fastforwardThresholdLabel = QLabel(getMessage("fastforward-threshold-label"), self)
|
||||
self.fastforwardThresholdLabel.setStyleSheet(constants.STYLE_SUBLABEL.format(self.posixresourcespath + "chevrons_right.png"))
|
||||
self.fastforwardThresholdSpinbox = QDoubleSpinBox()
|
||||
try:
|
||||
fastforwardThreshold = float(config['fastforwardThreshold'])
|
||||
self.fastforwardThresholdSpinbox.setValue(fastforwardThreshold)
|
||||
if fastforwardThreshold < constants.MINIMUM_FASTFORWARD_THRESHOLD:
|
||||
constants.MINIMUM_FASTFORWARD_THRESHOLD = fastforwardThreshold
|
||||
except ValueError:
|
||||
self.fastforwardThresholdSpinbox.setValue(constants.DEFAULT_FASTFORWARD_THRESHOLD)
|
||||
self.fastforwardThresholdSpinbox.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)
|
||||
self.fastforwardThresholdSpinbox.setMinimum(constants.MINIMUM_FASTFORWARD_THRESHOLD)
|
||||
self.fastforwardThresholdSpinbox.setSingleStep(0.1)
|
||||
self.fastforwardThresholdSpinbox.setSuffix(getMessage("seconds-suffix"))
|
||||
self.fastforwardThresholdSpinbox.adjustSize()
|
||||
|
||||
self.slowdownThresholdLabel.setObjectName("slowdown-threshold")
|
||||
self.slowdownThresholdSpinbox.setObjectName("slowdown-threshold")
|
||||
self.rewindThresholdLabel.setObjectName("rewind-threshold")
|
||||
@ -483,19 +537,29 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.syncSettingsLayout.addWidget(self.desyncSettingsGroup)
|
||||
self.desyncFrame.setLayout(self.syncSettingsLayout)
|
||||
|
||||
self.othersyncSettingsGroup = QtGui.QGroupBox("Other sync options")
|
||||
self.othersyncSettingsGroup = QtGui.QGroupBox(getMessage("sync-other-title"))
|
||||
self.othersyncOptionsFrame = QtGui.QFrame()
|
||||
self.othersyncSettingsLayout = QtGui.QGridLayout()
|
||||
|
||||
|
||||
self.dontslowwithmeCheckbox = QCheckBox(getMessage("dontslowdownwithme-label"))
|
||||
self.pauseonleaveCheckbox = QCheckBox(getMessage("pauseonleave-label"))
|
||||
self.othersyncSettingsLayout.addWidget(self.dontslowwithmeCheckbox)
|
||||
self.othersyncSettingsLayout.addWidget(self.pauseonleaveCheckbox)
|
||||
self.dontslowwithmeCheckbox.setObjectName("dontSlowDownWithMe")
|
||||
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.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.subitems['fastforwardOnDesync'] = ["fastforward-threshold"]
|
||||
|
||||
self.othersyncSettingsGroup.setLayout(self.othersyncSettingsLayout)
|
||||
self.othersyncSettingsGroup.setMaximumHeight(self.othersyncSettingsGroup.minimumSizeHint().height())
|
||||
self.syncSettingsLayout.addWidget(self.othersyncSettingsGroup)
|
||||
|
||||
self.syncSettingsFrame.setLayout(self.syncSettingsLayout)
|
||||
@ -508,7 +572,7 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.messageLayout = QtGui.QVBoxLayout()
|
||||
|
||||
# OSD
|
||||
self.osdSettingsGroup = QtGui.QGroupBox("On-screen Display settings")
|
||||
self.osdSettingsGroup = QtGui.QGroupBox(getMessage("messages-osd-title"))
|
||||
self.osdSettingsLayout = QtGui.QVBoxLayout()
|
||||
self.osdSettingsFrame = QtGui.QFrame()
|
||||
|
||||
@ -518,54 +582,68 @@ class ConfigDialog(QtGui.QDialog):
|
||||
|
||||
self.showSameRoomOSDCheckbox = QCheckBox(getMessage("showsameroomosd-label"))
|
||||
self.showSameRoomOSDCheckbox.setObjectName("showSameRoomOSD")
|
||||
self.showSameRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "bullet_black.png"))
|
||||
self.showSameRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png"))
|
||||
self.osdSettingsLayout.addWidget(self.showSameRoomOSDCheckbox)
|
||||
|
||||
self.showNonControllerOSDCheckbox = QCheckBox(getMessage("shownoncontrollerosd-label"))
|
||||
self.showNonControllerOSDCheckbox.setObjectName("showNonControllerOSD")
|
||||
self.showNonControllerOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png"))
|
||||
self.osdSettingsLayout.addWidget(self.showNonControllerOSDCheckbox)
|
||||
|
||||
self.showDifferentRoomOSDCheckbox = QCheckBox(getMessage("showdifferentroomosd-label"))
|
||||
self.showDifferentRoomOSDCheckbox.setObjectName("showDifferentRoomOSD")
|
||||
self.showDifferentRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "bullet_black.png"))
|
||||
self.showDifferentRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png"))
|
||||
self.osdSettingsLayout.addWidget(self.showDifferentRoomOSDCheckbox)
|
||||
|
||||
self.slowdownOSDCheckbox = QCheckBox(getMessage("showslowdownosd-label"))
|
||||
self.slowdownOSDCheckbox.setObjectName("showSlowdownOSD")
|
||||
self.slowdownOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "bullet_black.png"))
|
||||
self.slowdownOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png"))
|
||||
self.osdSettingsLayout.addWidget(self.slowdownOSDCheckbox)
|
||||
|
||||
self.showOSDWarningsCheckbox = QCheckBox(getMessage("showosdwarnings-label"))
|
||||
self.showOSDWarningsCheckbox.setObjectName("showOSDWarnings")
|
||||
self.showOSDWarningsCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "bullet_black.png"))
|
||||
self.showOSDWarningsCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png"))
|
||||
self.osdSettingsLayout.addWidget(self.showOSDWarningsCheckbox)
|
||||
|
||||
self.subitems['showOSD'] = ["showSameRoomOSD", "showDifferentRoomOSD", "showSlowdownOSD", "showOSDWarnings"]
|
||||
|
||||
self.osdSettingsGroup.setLayout(self.osdSettingsLayout)
|
||||
self.osdSettingsGroup.setMaximumHeight(self.osdSettingsGroup.minimumSizeHint().height())
|
||||
self.osdSettingsLayout.setAlignment(Qt.AlignTop)
|
||||
self.messageLayout.addWidget(self.osdSettingsGroup)
|
||||
|
||||
# Other display
|
||||
|
||||
self.displaySettingsGroup = QtGui.QGroupBox("Other display settings")
|
||||
self.displaySettingsLayout = QtGui.QVBoxLayout()
|
||||
self.displaySettingsGroup = QtGui.QGroupBox(getMessage("messages-other-title"))
|
||||
self.displaySettingsLayout = QtGui.QGridLayout()
|
||||
self.displaySettingsLayout.setAlignment(Qt.AlignTop)
|
||||
self.displaySettingsFrame = QtGui.QFrame()
|
||||
|
||||
self.showDurationNotificationCheckbox = QCheckBox(getMessage("showdurationnotification-label"))
|
||||
self.showDurationNotificationCheckbox.setObjectName("showDurationNotification")
|
||||
self.displaySettingsLayout.addWidget(self.showDurationNotificationCheckbox)
|
||||
self.displaySettingsLayout.addWidget(self.showDurationNotificationCheckbox, 0, 0, 1, 2)
|
||||
|
||||
self.showcontactinfoCheckbox = QCheckBox(getMessage("showcontactinfo-label"))
|
||||
self.showcontactinfoCheckbox.setObjectName("showContactInfo")
|
||||
self.displaySettingsLayout.addWidget(self.showcontactinfoCheckbox)
|
||||
self.displaySettingsLayout.addWidget(self.showcontactinfoCheckbox, 1, 0, 1, 2)
|
||||
|
||||
self.showButtonLabelsCheckbox = QCheckBox(getMessage("showbuttonlabels-label"))
|
||||
self.showButtonLabelsCheckbox.setObjectName("showButtonLabels")
|
||||
self.displaySettingsLayout.addWidget(self.showButtonLabelsCheckbox)
|
||||
self.languageLabel = QLabel(getMessage("language-label"), self)
|
||||
self.languageCombobox = QtGui.QComboBox(self)
|
||||
self.languages = getLanguages()
|
||||
for lang in self.languages:
|
||||
self.languageCombobox.addItem(self.languages[lang], lang)
|
||||
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.showTooltipsCheckbox = QCheckBox(getMessage("showtooltips-label"))
|
||||
self.showTooltipsCheckbox.setObjectName("showTooltips")
|
||||
self.displaySettingsLayout.addWidget(self.showTooltipsCheckbox)
|
||||
self.languageLabel.setObjectName("language")
|
||||
self.languageCombobox.setObjectName("language")
|
||||
|
||||
|
||||
self.displaySettingsGroup.setLayout(self.displaySettingsLayout)
|
||||
self.displaySettingsGroup.setMaximumHeight(self.displaySettingsGroup.minimumSizeHint().height())
|
||||
self.displaySettingsLayout.setAlignment(Qt.AlignTop)
|
||||
self.messageLayout.addWidget(self.displaySettingsGroup)
|
||||
|
||||
@ -574,7 +652,7 @@ class ConfigDialog(QtGui.QDialog):
|
||||
self.stackedLayout.addWidget(self.messageFrame)
|
||||
|
||||
def addPrivacyTab(self):
|
||||
self.privacySettingsGroup = QtGui.QGroupBox("Privacy settings")
|
||||
self.privacySettingsGroup = QtGui.QGroupBox(getMessage("privacy-title"))
|
||||
self.privacySettingsLayout = QtGui.QVBoxLayout()
|
||||
self.privacySettingsFrame = QtGui.QFrame()
|
||||
self.privacyFrame = QtGui.QFrame()
|
||||
@ -703,6 +781,7 @@ 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")
|
||||
|
||||
@ -35,7 +35,10 @@ class ConsoleUI(threading.Thread):
|
||||
self._executeCommand(data)
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
|
||||
def updateRoomName(self, room=""):
|
||||
pass
|
||||
|
||||
def promptFor(self, prompt=">", message=""):
|
||||
if message <> "":
|
||||
print(message)
|
||||
@ -49,14 +52,17 @@ class ConsoleUI(threading.Thread):
|
||||
message = u"In room '{}':".format(room)
|
||||
self.showMessage(message, True)
|
||||
for user in rooms[room]:
|
||||
username = "*<{}>*".format(user.username) if user == currentUser else "<{}>".format(user.username)
|
||||
userflags = u""
|
||||
if user.isController():
|
||||
userflags = userflags + u"(Controller) "
|
||||
username = userflags + u"*<{}>*".format(user.username) if user == currentUser else userflags + u"<{}>".format(user.username)
|
||||
if user.file:
|
||||
message = u"{} is playing:".format(username)
|
||||
self.showMessage(message, True)
|
||||
message = u" File: '{}' ({})".format(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 += " (their file size is different from yours!)"
|
||||
message += u" (their file size is different from yours!)"
|
||||
self.showMessage(message, True)
|
||||
else:
|
||||
message = u"{} is not playing a file".format(username)
|
||||
@ -74,7 +80,7 @@ class ConsoleUI(threading.Thread):
|
||||
|
||||
def showDebugMessage(self, message):
|
||||
print(message)
|
||||
|
||||
|
||||
def showErrorMessage(self, message, criticalerror = False):
|
||||
print("ERROR:\t" + message)
|
||||
|
||||
@ -134,6 +140,15 @@ class ConsoleUI(threading.Thread):
|
||||
|
||||
self._syncplayClient.setRoom(room)
|
||||
self._syncplayClient.sendRoom()
|
||||
elif command.group('command') in constants.COMMANDS_CREATE:
|
||||
roombasename = command.group('parameter')
|
||||
if roombasename == None:
|
||||
roombasename = self._syncplayClient.getRoom()
|
||||
roombasename = utils.stripRoomName(roombasename)
|
||||
self._syncplayClient.createControlledRoom(roombasename)
|
||||
elif command.group('command') in constants.COMMANDS_AUTH:
|
||||
controlpassword = command.group('parameter')
|
||||
self._syncplayClient.identifyAsController(controlpassword)
|
||||
else:
|
||||
if self._tryAdvancedCommands(data):
|
||||
return
|
||||
@ -146,6 +161,8 @@ 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/create"), True)
|
||||
self.showMessage(getMessage("commandlist-notification/auth"), True)
|
||||
self.showMessage(getMessage("syncplay-version-notification").format(syncplay.version), True)
|
||||
self.showMessage(getMessage("more-info-notification").format(syncplay.projectURL), True)
|
||||
|
||||
|
||||
@ -1,15 +1,74 @@
|
||||
from PySide import QtGui #@UnresolvedImport
|
||||
from PySide.QtCore import Qt, QSettings, QSize, QPoint #@UnresolvedImport
|
||||
from PySide import QtGui
|
||||
from PySide.QtCore import Qt, QSettings, QSize, QPoint
|
||||
from syncplay import utils, constants, version
|
||||
from syncplay.messages import getMessage
|
||||
import sys
|
||||
import time
|
||||
import re
|
||||
import os
|
||||
import threading
|
||||
from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration
|
||||
from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize
|
||||
|
||||
class UserlistItemDelegate(QtGui.QStyledItemDelegate):
|
||||
def __init__(self):
|
||||
QtGui.QStyledItemDelegate.__init__(self)
|
||||
|
||||
def sizeHint(self, option, index):
|
||||
size = QtGui.QStyledItemDelegate.sizeHint(self, option, index)
|
||||
if (index.column() == 0):
|
||||
size.setWidth(size.width() + constants.USERLIST_GUI_USERNAME_OFFSET)
|
||||
return size
|
||||
|
||||
def paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex):
|
||||
column = indexQModelIndex.column()
|
||||
if column == 0:
|
||||
currentQAbstractItemModel = indexQModelIndex.model()
|
||||
itemQModelIndex = currentQAbstractItemModel.index(indexQModelIndex.row(), 0, indexQModelIndex.parent())
|
||||
if sys.platform.startswith('win'):
|
||||
resourcespath = utils.findWorkingDir() + "\\resources\\"
|
||||
else:
|
||||
resourcespath = utils.findWorkingDir() + "/resources/"
|
||||
controlIconQPixmap = QtGui.QPixmap(resourcespath + "user_key.png")
|
||||
tickIconQPixmap = QtGui.QPixmap(resourcespath + "tick.png")
|
||||
crossIconQPixmap = QtGui.QPixmap(resourcespath + "cross.png")
|
||||
roomController = currentQAbstractItemModel.data(itemQModelIndex, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE)
|
||||
userReady = currentQAbstractItemModel.data(itemQModelIndex, Qt.UserRole + constants.USERITEM_READY_ROLE)
|
||||
|
||||
if roomController and not controlIconQPixmap.isNull():
|
||||
itemQPainter.drawPixmap (
|
||||
optionQStyleOptionViewItem.rect.x()+6,
|
||||
optionQStyleOptionViewItem.rect.y(),
|
||||
controlIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio))
|
||||
|
||||
if userReady and not tickIconQPixmap.isNull():
|
||||
itemQPainter.drawPixmap (
|
||||
(optionQStyleOptionViewItem.rect.x()-10),
|
||||
optionQStyleOptionViewItem.rect.y(),
|
||||
tickIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio))
|
||||
|
||||
elif userReady == False and not crossIconQPixmap.isNull():
|
||||
itemQPainter.drawPixmap (
|
||||
(optionQStyleOptionViewItem.rect.x()-10),
|
||||
optionQStyleOptionViewItem.rect.y(),
|
||||
crossIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio))
|
||||
isUserRow = indexQModelIndex.parent() != indexQModelIndex.parent().parent()
|
||||
if isUserRow:
|
||||
optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+constants.USERLIST_GUI_USERNAME_OFFSET)
|
||||
QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex)
|
||||
|
||||
class MainWindow(QtGui.QMainWindow):
|
||||
class topSplitter(QtGui.QSplitter):
|
||||
def createHandle(self):
|
||||
return self.topSplitterHandle(self.orientation(), self)
|
||||
|
||||
class topSplitterHandle(QtGui.QSplitterHandle):
|
||||
def mouseReleaseEvent(self, event):
|
||||
QtGui.QSplitterHandle.mouseReleaseEvent(self, event)
|
||||
self.parent().parent().parent().updateListGeometry()
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
QtGui.QSplitterHandle.mouseMoveEvent(self, event)
|
||||
self.parent().parent().parent().updateListGeometry()
|
||||
|
||||
def addClient(self, client):
|
||||
self._syncplayClient = client
|
||||
self.roomInput.setText(self._syncplayClient.getRoom())
|
||||
@ -17,29 +76,14 @@ class MainWindow(QtGui.QMainWindow):
|
||||
try:
|
||||
if self.contactLabel and not self.config['showContactInfo']:
|
||||
self.contactLabel.hide()
|
||||
if not self.config['showButtonLabels']:
|
||||
if constants.MERGE_PLAYPAUSE_BUTTONS:
|
||||
self.playpauseButton.setText("")
|
||||
else:
|
||||
self.playButton.setText("")
|
||||
self.playButton.setFixedWidth(self.playButton.minimumSizeHint().width())
|
||||
self.pauseButton.setText("")
|
||||
self.pauseButton.setFixedWidth(self.pauseButton.minimumSizeHint().width())
|
||||
self.roomButton.setText("")
|
||||
self.roomButton.setFixedWidth(self.roomButton.minimumSizeHint().width())
|
||||
self.seekButton.setText("")
|
||||
self.seekButton.setFixedWidth(self.seekButton.minimumSizeHint().width())
|
||||
self.unseekButton.setText("")
|
||||
self.unseekButton.setFixedWidth(self.unseekButton.minimumSizeHint().width())
|
||||
self.roomGroup.setFixedWidth(self.roomGroup.sizeHint().width())
|
||||
self.seekGroup.setFixedWidth(self.seekGroup.minimumSizeHint().width())
|
||||
self.miscGroup.setFixedWidth(self.miscGroup.minimumSizeHint().width())
|
||||
if not constants.SHOW_BUTTON_LABELS:
|
||||
self.hideRoomSeekLabels()
|
||||
self.hideMiscLabels()
|
||||
except ():
|
||||
pass
|
||||
|
||||
|
||||
def promptFor(self, prompt=">", message=""):
|
||||
#TODO: Prompt user
|
||||
# TODO: Prompt user
|
||||
return None
|
||||
|
||||
def showMessage(self, message, noTimestamp=False):
|
||||
@ -52,89 +96,132 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.newMessage(message + "<br />")
|
||||
else:
|
||||
self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "<br />")
|
||||
|
||||
|
||||
def showUserList(self, currentUser, rooms):
|
||||
self._usertreebuffer = QtGui.QStandardItemModel()
|
||||
self._usertreebuffer.setColumnCount(2)
|
||||
self._usertreebuffer.setHorizontalHeaderLabels((getMessage("roomuser-heading-label"),getMessage("fileplayed-heading-label")))
|
||||
self._usertreebuffer.setHorizontalHeaderLabels(
|
||||
(getMessage("roomuser-heading-label"), getMessage("size-heading-label"), getMessage("duration-heading-label"), getMessage("filename-heading-label") ))
|
||||
usertreeRoot = self._usertreebuffer.invisibleRootItem()
|
||||
|
||||
|
||||
for room in rooms:
|
||||
roomitem = QtGui.QStandardItem(room)
|
||||
font = QtGui.QFont()
|
||||
font.setItalic(True)
|
||||
if room == currentUser.room:
|
||||
font = QtGui.QFont()
|
||||
font.setWeight(QtGui.QFont.Bold)
|
||||
roomitem.setFont(font)
|
||||
blankitem = QtGui.QStandardItem("")
|
||||
roomitem.setFlags(roomitem.flags() & ~Qt.ItemIsEditable)
|
||||
blankitem.setFlags(blankitem.flags() & ~Qt.ItemIsEditable)
|
||||
usertreeRoot.appendRow((roomitem, blankitem))
|
||||
roomitem.setFont(font)
|
||||
roomitem.setFlags(roomitem.flags() & ~Qt.ItemIsEditable)
|
||||
usertreeRoot.appendRow(roomitem)
|
||||
isControlledRoom = RoomPasswordProvider.isControlledRoom(room)
|
||||
|
||||
if isControlledRoom:
|
||||
if room == currentUser.room and currentUser.isController():
|
||||
roomitem.setIcon(QtGui.QIcon(self.resourcespath + 'lock_open.png'))
|
||||
else:
|
||||
roomitem.setIcon(QtGui.QIcon(self.resourcespath + 'lock.png'))
|
||||
else:
|
||||
roomitem.setIcon(QtGui.QIcon(self.resourcespath + 'chevrons_right.png'))
|
||||
|
||||
for user in rooms[room]:
|
||||
useritem = QtGui.QStandardItem(user.username)
|
||||
fileitem = QtGui.QStandardItem("")
|
||||
isController = user.isController()
|
||||
useritem.setData(isController, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE)
|
||||
if user.file:
|
||||
fileitem = QtGui.QStandardItem(u"{} ({})".format(user.file['name'], formatTime(user.file['duration'])))
|
||||
filesizeitem = QtGui.QStandardItem(formatSize(user.file['size']))
|
||||
filedurationitem = QtGui.QStandardItem("({})".format(formatTime(user.file['duration'])))
|
||||
filenameitem = QtGui.QStandardItem((user.file['name']))
|
||||
if currentUser.file:
|
||||
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
|
||||
differentName = not sameName
|
||||
differentSize = not sameSize
|
||||
differentDuration = not sameDuration
|
||||
if sameName or sameRoom:
|
||||
if differentSize and sameDuration:
|
||||
fileitem = QtGui.QStandardItem(u"{} ({}) ({})".format(user.file['name'], formatTime(user.file['duration']), getMessage("differentsize-note")))
|
||||
elif differentSize and differentDuration:
|
||||
fileitem = QtGui.QStandardItem(u"{} ({}) ({})".format(user.file['name'], formatTime(user.file['duration']), getMessage("differentsizeandduration-note")))
|
||||
elif differentDuration:
|
||||
fileitem = QtGui.QStandardItem(u"{} ({}) ({})".format(user.file['name'], formatTime(user.file['duration']), getMessage("differentduration-note")))
|
||||
if sameRoom and (differentName or differentSize or differentDuration):
|
||||
fileitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR)))
|
||||
underlinefont = QtGui.QFont()
|
||||
underlinefont.setUnderline(True)
|
||||
if sameRoom:
|
||||
if not sameName:
|
||||
filenameitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR)))
|
||||
filenameitem.setFont(underlinefont)
|
||||
if not sameSize:
|
||||
if currentUser.file is not None and formatSize(user.file['size']) == formatSize(currentUser.file['size']):
|
||||
filesizeitem = QtGui.QStandardItem(formatSize(user.file['size'],precise=True))
|
||||
filesizeitem.setFont(underlinefont)
|
||||
filesizeitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR)))
|
||||
if not sameDuration:
|
||||
filedurationitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR)))
|
||||
filedurationitem.setFont(underlinefont)
|
||||
else:
|
||||
fileitem = QtGui.QStandardItem(getMessage("nofile-note"))
|
||||
filenameitem = QtGui.QStandardItem(getMessage("nofile-note"))
|
||||
filedurationitem = QtGui.QStandardItem("")
|
||||
filesizeitem = QtGui.QStandardItem("")
|
||||
if room == currentUser.room:
|
||||
fileitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOFILEITEM_COLOR)))
|
||||
filenameitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOFILEITEM_COLOR)))
|
||||
font = QtGui.QFont()
|
||||
if currentUser.username == user.username:
|
||||
font = QtGui.QFont()
|
||||
font.setWeight(QtGui.QFont.Bold)
|
||||
useritem.setFont(font)
|
||||
useritem.setFlags(useritem.flags() & ~Qt.ItemIsEditable)
|
||||
fileitem.setFlags(fileitem.flags() & ~Qt.ItemIsEditable)
|
||||
roomitem.appendRow((useritem, fileitem))
|
||||
|
||||
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)
|
||||
roomitem.appendRow((useritem, filesizeitem, filedurationitem, filenameitem))
|
||||
self.listTreeModel = self._usertreebuffer
|
||||
self.listTreeView.setModel(self.listTreeModel)
|
||||
self.listTreeView.setItemDelegate(UserlistItemDelegate())
|
||||
self.listTreeView.setItemsExpandable(False)
|
||||
self.listTreeView.setRootIsDecorated(False)
|
||||
self.listTreeView.expandAll()
|
||||
self.listTreeView.resizeColumnToContents(0)
|
||||
self.listTreeView.resizeColumnToContents(1)
|
||||
|
||||
self.updateListGeometry()
|
||||
|
||||
def updateListGeometry(self):
|
||||
try:
|
||||
roomtocheck = 0
|
||||
while self.listTreeModel.item(roomtocheck):
|
||||
self.listTreeView.setFirstColumnSpanned(roomtocheck, self.listTreeView.rootIndex(), True)
|
||||
roomtocheck += 1
|
||||
self.listTreeView.header().setStretchLastSection(False)
|
||||
self.listTreeView.header().setResizeMode(0, QtGui.QHeaderView.ResizeToContents)
|
||||
self.listTreeView.header().setResizeMode(1, QtGui.QHeaderView.ResizeToContents)
|
||||
self.listTreeView.header().setResizeMode(2, QtGui.QHeaderView.ResizeToContents)
|
||||
self.listTreeView.header().setResizeMode(3, QtGui.QHeaderView.ResizeToContents)
|
||||
NarrowTabsWidth = self.listTreeView.header().sectionSize(0)+self.listTreeView.header().sectionSize(1)+self.listTreeView.header().sectionSize(2)
|
||||
if self.listTreeView.header().width() < (NarrowTabsWidth+self.listTreeView.header().sectionSize(3)):
|
||||
self.listTreeView.header().resizeSection(3,self.listTreeView.header().width()-NarrowTabsWidth)
|
||||
else:
|
||||
self.listTreeView.header().setResizeMode(3, QtGui.QHeaderView.Stretch)
|
||||
self.listTreeView.expandAll()
|
||||
except:
|
||||
pass
|
||||
|
||||
def roomClicked(self, item):
|
||||
while item.parent().row() != -1:
|
||||
item = item.parent()
|
||||
self.joinRoom(item.sibling(item.row(), 0).data())
|
||||
|
||||
|
||||
def userListChange(self):
|
||||
self._syncplayClient.showUserList()
|
||||
|
||||
|
||||
def updateRoomName(self, room=""):
|
||||
self.roomInput.setText(room)
|
||||
|
||||
def showDebugMessage(self, message):
|
||||
print(message)
|
||||
|
||||
def showErrorMessage(self, message, criticalerror = False):
|
||||
|
||||
def showErrorMessage(self, message, criticalerror=False):
|
||||
message = unicode(message)
|
||||
if criticalerror:
|
||||
QtGui.QMessageBox.critical(self,"Syncplay", message)
|
||||
QtGui.QMessageBox.critical(self, "Syncplay", message)
|
||||
message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">")
|
||||
message = message.replace("\n", "<br />")
|
||||
message = "<span style=\"{}\">".format(constants.STYLE_ERRORNOTIFICATION) + message + "</span>"
|
||||
self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "<br />")
|
||||
|
||||
def joinRoom(self, room = None):
|
||||
def joinRoom(self, room=None):
|
||||
if room == None:
|
||||
room = self.roomInput.text()
|
||||
if room == "":
|
||||
if self._syncplayClient.userlist.currentUser.file:
|
||||
if self._syncplayClient.userlist.currentUser.file:
|
||||
room = self._syncplayClient.userlist.currentUser.file["name"]
|
||||
else:
|
||||
room = self._syncplayClient.defaultRoom
|
||||
@ -151,46 +238,113 @@ class MainWindow(QtGui.QMainWindow):
|
||||
if t is None:
|
||||
return
|
||||
if sign:
|
||||
t = self._syncplayClient.getGlobalPosition() + sign * t
|
||||
t = self._syncplayClient.getGlobalPosition() + sign * t
|
||||
self._syncplayClient.setPosition(t)
|
||||
|
||||
else:
|
||||
self.showErrorMessage("Invalid seek value")
|
||||
|
||||
|
||||
def undoSeek(self):
|
||||
tmp_pos = self._syncplayClient.getPlayerPosition()
|
||||
self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek)
|
||||
self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos
|
||||
|
||||
|
||||
def togglePause(self):
|
||||
self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused())
|
||||
|
||||
|
||||
def play(self):
|
||||
self._syncplayClient.setPaused(False)
|
||||
|
||||
|
||||
def pause(self):
|
||||
self._syncplayClient.setPaused(True)
|
||||
|
||||
|
||||
def exitSyncplay(self):
|
||||
self._syncplayClient.stop()
|
||||
|
||||
|
||||
def closeEvent(self, event):
|
||||
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")
|
||||
self.mediadirectory = settings.value("mediadir", "")
|
||||
settings.endGroup()
|
||||
|
||||
|
||||
def saveMediaBrowseSettings(self):
|
||||
settings = QSettings("Syncplay", "MediaBrowseDialog")
|
||||
settings.beginGroup("MediaBrowseDialog")
|
||||
settings.setValue("mediadir", self.mediadirectory)
|
||||
settings.endGroup()
|
||||
|
||||
|
||||
def browseMediapath(self):
|
||||
if self._syncplayClient._player.customOpenDialog == True:
|
||||
self._syncplayClient._player.openCustomOpenDialog()
|
||||
return
|
||||
|
||||
self.loadMediaBrowseSettings()
|
||||
options = QtGui.QFileDialog.Options()
|
||||
if os.path.isdir(self.mediadirectory):
|
||||
@ -201,16 +355,30 @@ class MainWindow(QtGui.QMainWindow):
|
||||
defaultdirectory = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.HomeLocation)
|
||||
else:
|
||||
defaultdirectory = ""
|
||||
browserfilter = "All files (*)"
|
||||
fileName, filtr = QtGui.QFileDialog.getOpenFileName(self,getMessage("browseformedia-label"),defaultdirectory,
|
||||
browserfilter, "", options)
|
||||
browserfilter = "All files (*)"
|
||||
fileName, filtr = QtGui.QFileDialog.getOpenFileName(self, getMessage("browseformedia-label"), defaultdirectory,
|
||||
browserfilter, "", options)
|
||||
if fileName:
|
||||
if sys.platform.startswith('win'):
|
||||
fileName = fileName.replace("/","\\")
|
||||
fileName = fileName.replace("/", "\\")
|
||||
self.mediadirectory = os.path.dirname(fileName)
|
||||
self.saveMediaBrowseSettings()
|
||||
self._syncplayClient._player.openFile(fileName)
|
||||
|
||||
|
||||
def createControlledRoom(self):
|
||||
controlroom, ok = QtGui.QInputDialog.getText(self, getMessage("createcontrolledroom-msgbox-label"),
|
||||
getMessage("controlledroominfo-msgbox-label"), QtGui.QLineEdit.Normal,
|
||||
utils.stripRoomName(self._syncplayClient.getRoom()))
|
||||
if ok and controlroom != '':
|
||||
self._syncplayClient.createControlledRoom(controlroom)
|
||||
|
||||
def identifyAsController(self):
|
||||
msgboxtitle = getMessage("identifyascontroller-msgbox-label")
|
||||
msgboxtext = getMessage("identifyinfo-msgbox-label")
|
||||
controlpassword, ok = QtGui.QInputDialog.getText(self, msgboxtitle, msgboxtext, QtGui.QLineEdit.Normal, "")
|
||||
if ok and controlpassword != '':
|
||||
self._syncplayClient.identifyAsController(controlpassword)
|
||||
|
||||
def _extractSign(self, m):
|
||||
if m:
|
||||
if m == "-":
|
||||
@ -219,11 +387,11 @@ class MainWindow(QtGui.QMainWindow):
|
||||
return 1
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def setOffset(self):
|
||||
newoffset, ok = QtGui.QInputDialog.getText(self,getMessage("setoffset-msgbox-label"),
|
||||
getMessage("offsetinfo-msgbox-label"), QtGui.QLineEdit.Normal,
|
||||
"")
|
||||
newoffset, ok = QtGui.QInputDialog.getText(self, getMessage("setoffset-msgbox-label"),
|
||||
getMessage("offsetinfo-msgbox-label"), QtGui.QLineEdit.Normal,
|
||||
"")
|
||||
if ok and newoffset != '':
|
||||
o = re.match(constants.UI_OFFSET_REGEX, "o " + newoffset)
|
||||
if o:
|
||||
@ -232,13 +400,13 @@ class MainWindow(QtGui.QMainWindow):
|
||||
if t is None:
|
||||
return
|
||||
if o.group('sign') == "/":
|
||||
t = self._syncplayClient.getPlayerPosition() - t
|
||||
t = self._syncplayClient.getPlayerPosition() - t
|
||||
elif sign:
|
||||
t = self._syncplayClient.getUserOffset() + sign * t
|
||||
t = self._syncplayClient.getUserOffset() + sign * t
|
||||
self._syncplayClient.setUserOffset(t)
|
||||
else:
|
||||
self.showErrorMessage("Invalid offset value")
|
||||
|
||||
|
||||
def openUserGuide(self):
|
||||
if sys.platform.startswith('linux'):
|
||||
self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/linux/")
|
||||
@ -249,9 +417,9 @@ class MainWindow(QtGui.QMainWindow):
|
||||
|
||||
def drop(self):
|
||||
self.close()
|
||||
|
||||
def addTopLayout(self, window):
|
||||
window.topSplit = QtGui.QSplitter(Qt.Horizontal)
|
||||
|
||||
def addTopLayout(self, window):
|
||||
window.topSplit = self.topSplitter(Qt.Horizontal, self)
|
||||
|
||||
window.outputLayout = QtGui.QVBoxLayout()
|
||||
window.outputbox = QtGui.QTextEdit()
|
||||
@ -260,21 +428,22 @@ class MainWindow(QtGui.QMainWindow):
|
||||
window.outputFrame = QtGui.QFrame()
|
||||
window.outputFrame.setLineWidth(0)
|
||||
window.outputFrame.setMidLineWidth(0)
|
||||
window.outputLayout.setContentsMargins(0,0,0,0)
|
||||
window.outputLayout.setContentsMargins(0, 0, 0, 0)
|
||||
window.outputLayout.addWidget(window.outputlabel)
|
||||
window.outputLayout.addWidget(window.outputbox)
|
||||
window.outputFrame.setLayout(window.outputLayout)
|
||||
|
||||
|
||||
window.listLayout = QtGui.QVBoxLayout()
|
||||
window.listTreeModel = QtGui.QStandardItemModel()
|
||||
window.listTreeView = QtGui.QTreeView()
|
||||
window.listTreeView.setModel(window.listTreeModel)
|
||||
window.listTreeView.setIndentation(21)
|
||||
window.listTreeView.doubleClicked.connect(self.roomClicked)
|
||||
window.listlabel = QtGui.QLabel(getMessage("userlist-heading-label"))
|
||||
window.listFrame = QtGui.QFrame()
|
||||
window.listFrame.setLineWidth(0)
|
||||
window.listFrame.setMidLineWidth(0)
|
||||
window.listLayout.setContentsMargins(0,0,0,0)
|
||||
window.listLayout.setContentsMargins(0, 0, 0, 0)
|
||||
window.listLayout.addWidget(window.listlabel)
|
||||
window.listLayout.addWidget(window.listTreeView)
|
||||
window.contactLabel = QtGui.QLabel()
|
||||
@ -288,13 +457,13 @@ class MainWindow(QtGui.QMainWindow):
|
||||
window.contactLabel.setOpenExternalLinks(True)
|
||||
window.listLayout.addWidget(window.contactLabel)
|
||||
window.listFrame.setLayout(window.listLayout)
|
||||
|
||||
|
||||
window.topSplit.addWidget(window.outputFrame)
|
||||
window.topSplit.addWidget(window.listFrame)
|
||||
window.topSplit.setStretchFactor(0,4)
|
||||
window.topSplit.setStretchFactor(1,5)
|
||||
window.mainLayout.addWidget(window.topSplit)
|
||||
window.topSplit.setSizePolicy(QtGui.QSizePolicy.Preferred,QtGui.QSizePolicy.Expanding)
|
||||
window.topSplit.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)
|
||||
|
||||
def addBottomLayout(self, window):
|
||||
window.bottomLayout = QtGui.QHBoxLayout()
|
||||
@ -303,156 +472,169 @@ class MainWindow(QtGui.QMainWindow):
|
||||
window.addSeekBox(MainWindow)
|
||||
window.addMiscBox(MainWindow)
|
||||
|
||||
window.bottomLayout.addWidget(window.roomGroup, Qt.AlignLeft)
|
||||
window.bottomLayout.addWidget(window.seekGroup, Qt.AlignLeft)
|
||||
window.bottomLayout.addWidget(window.miscGroup, Qt.AlignLeft)
|
||||
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 = 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.setFixedSize(window.roomGroup.sizeHint())
|
||||
|
||||
window.roomGroup.setFixedHeight(window.roomGroup.sizeHint().height())
|
||||
|
||||
def addSeekBox(self, window):
|
||||
window.seekGroup = QtGui.QGroupBox(getMessage("seek-heading-label"))
|
||||
|
||||
|
||||
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 = 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.setText("0:00")
|
||||
window.seekInput.setFixedWidth(60)
|
||||
|
||||
|
||||
window.seekLayout.addWidget(window.seekInput)
|
||||
window.seekLayout.addWidget(window.seekButton)
|
||||
|
||||
|
||||
window.seekGroup.setLayout(window.seekLayout)
|
||||
window.seekGroup.setFixedSize(window.seekGroup.sizeHint())
|
||||
|
||||
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.unseekButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'arrow_undo.png'),
|
||||
getMessage("undoseek-guibuttonlabel"))
|
||||
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 = 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 = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_play_blue.png'),
|
||||
getMessage("play-guibuttonlabel"))
|
||||
window.playButton.pressed.connect(self.play)
|
||||
window.playButton.setMaximumWidth(60)
|
||||
window.miscLayout.addWidget(window.playButton)
|
||||
window.pauseButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_pause_blue.png'),getMessage("pause-guibuttonlabel"))
|
||||
window.pauseButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_pause_blue.png'),
|
||||
getMessage("pause-guibuttonlabel"))
|
||||
window.pauseButton.pressed.connect(self.pause)
|
||||
window.pauseButton.setMaximumWidth(60)
|
||||
window.miscLayout.addWidget(window.pauseButton)
|
||||
self.playButton.setToolTip(getMessage("play-tooltip"))
|
||||
self.pauseButton.setToolTip(getMessage("pause-tooltip"))
|
||||
|
||||
|
||||
window.miscGroup.setLayout(window.miscLayout)
|
||||
window.miscGroup.setFixedSize(window.miscGroup.sizeHint())
|
||||
|
||||
window.miscGroup.setFixedHeight(window.miscGroup.sizeHint().height())
|
||||
|
||||
|
||||
def addMenubar(self, window):
|
||||
window.menuBar = QtGui.QMenuBar()
|
||||
|
||||
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 = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'folder_explore.png'),
|
||||
getMessage("openmedia-menu-label"))
|
||||
window.openAction.triggered.connect(self.browseMediapath)
|
||||
window.exitAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'cross.png'), getMessage("file-menu-label"))
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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"))
|
||||
window.setoffsetAction = window.advancedMenu.addAction(QtGui.QIcon(self.resourcespath + 'timeline_marker.png'),
|
||||
getMessage("setoffset-menu-label"))
|
||||
window.setoffsetAction.triggered.connect(self.setOffset)
|
||||
|
||||
window.createcontrolledroomAction = window.advancedMenu.addAction(
|
||||
QtGui.QIcon(self.resourcespath + 'page_white_key.png'), getMessage("createcontrolledroom-menu-label"))
|
||||
window.createcontrolledroomAction.triggered.connect(self.createControlledRoom)
|
||||
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.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 = window.helpMenu.addAction(QtGui.QIcon(self.resourcespath + 'help.png'),
|
||||
getMessage("userguide-menu-label"))
|
||||
window.userguideAction.triggered.connect(self.openUserGuide)
|
||||
|
||||
|
||||
window.menuBar.addMenu(window.helpMenu)
|
||||
window.mainLayout.setMenuBar(window.menuBar)
|
||||
|
||||
|
||||
def addMainFrame(self, window):
|
||||
window.mainFrame = QtGui.QFrame()
|
||||
window.mainFrame.setLineWidth(0)
|
||||
window.mainFrame.setMidLineWidth(0)
|
||||
window.mainFrame.setContentsMargins(0,0,0,0)
|
||||
window.mainFrame.setContentsMargins(0, 0, 0, 0)
|
||||
window.mainFrame.setLayout(window.mainLayout)
|
||||
|
||||
|
||||
window.setCentralWidget(window.mainFrame)
|
||||
|
||||
|
||||
def newMessage(self, message):
|
||||
self.outputbox.moveCursor(QtGui.QTextCursor.End)
|
||||
self.outputbox.insertHtml(message)
|
||||
self.outputbox.moveCursor(QtGui.QTextCursor.End)
|
||||
|
||||
|
||||
def resetList(self):
|
||||
self.listbox.setText("")
|
||||
|
||||
|
||||
def newListItem(self, item):
|
||||
self.listbox.moveCursor(QtGui.QTextCursor.End)
|
||||
self.listbox.insertHtml(item)
|
||||
self.listbox.moveCursor(QtGui.QTextCursor.End)
|
||||
|
||||
|
||||
def dragEnterEvent(self, event):
|
||||
data = event.mimeData()
|
||||
urls = data.urls()
|
||||
if urls and urls[0].scheme() == 'file':
|
||||
event.acceptProposedAction()
|
||||
|
||||
|
||||
def dropEvent(self, event):
|
||||
rewindFile = False
|
||||
if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction:
|
||||
QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted
|
||||
QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted
|
||||
rewindFile = True
|
||||
data = event.mimeData()
|
||||
urls = data.urls()
|
||||
if urls and urls[0].scheme() == 'file':
|
||||
if sys.platform.startswith('win'):
|
||||
dropfilepath = unicode(urls[0].path().replace("/", "\\"))[1:] # Removes starting slash
|
||||
else:
|
||||
dropfilepath = unicode(urls[0].path())
|
||||
dropfilepath = os.path.abspath(unicode(event.mimeData().urls()[0].toLocalFile()))
|
||||
if rewindFile == False:
|
||||
self._syncplayClient._player.openFile(dropfilepath)
|
||||
else:
|
||||
self._syncplayClient.setPosition(0)
|
||||
self._syncplayClient._player.openFile(dropfilepath)
|
||||
self._syncplayClient._player.openFile(dropfilepath, resetPosition=True)
|
||||
self._syncplayClient.setPosition(0)
|
||||
|
||||
|
||||
def saveSettings(self):
|
||||
settings = QSettings("Syncplay", "MainWindow")
|
||||
settings.beginGroup("MainWindow")
|
||||
settings.setValue("size", self.size())
|
||||
settings.setValue("pos", self.pos())
|
||||
settings.endGroup()
|
||||
|
||||
|
||||
def loadSettings(self):
|
||||
settings = QSettings("Syncplay", "MainWindow")
|
||||
settings.beginGroup("MainWindow")
|
||||
@ -467,14 +649,16 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.resourcespath = utils.findWorkingDir() + "\\resources\\"
|
||||
else:
|
||||
self.resourcespath = utils.findWorkingDir() + "/resources/"
|
||||
self.setWindowFlags(self.windowFlags() & Qt.AA_DontUseNativeMenuBar)
|
||||
self.setWindowTitle("Syncplay v" + version)
|
||||
self.mainLayout = QtGui.QVBoxLayout()
|
||||
self.addTopLayout(self)
|
||||
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.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint)
|
||||
self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.AA_DontUseNativeMenuBar & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint)
|
||||
self.show()
|
||||
self.setAcceptDrops(True)
|
||||
self.setAcceptDrops(True)
|
||||
|
||||
@ -7,6 +7,8 @@ import sys
|
||||
import os
|
||||
import itertools
|
||||
import hashlib
|
||||
import random
|
||||
import string
|
||||
|
||||
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
|
||||
"""Retry calling the decorated function using an exponential backoff.
|
||||
@ -93,6 +95,19 @@ def formatTime(timeInSeconds, weeksAsTitles=True):
|
||||
formattedTime = "{0:} (Title {1:.0f})".format(formattedTime, title)
|
||||
return formattedTime
|
||||
|
||||
def formatSize (bytes, precise=False):
|
||||
if bytes == 0: # E.g. when file size privacy is enabled
|
||||
return "???"
|
||||
try:
|
||||
megabytes = int(bytes) / 1048576.0 # Technically this is a mebibyte, but whatever
|
||||
if precise:
|
||||
megabytes = round(megabytes, 1)
|
||||
else:
|
||||
megabytes = int(megabytes)
|
||||
return str(megabytes) + getMessage("megabyte-suffix")
|
||||
except: # E.g. when filesize is hashed
|
||||
return "???"
|
||||
|
||||
def findWorkingDir():
|
||||
frozen = getattr(sys, 'frozen', '')
|
||||
if not frozen:
|
||||
@ -141,7 +156,19 @@ def blackholeStdoutForFrozenWindow():
|
||||
# Relate to file hashing / difference checking:
|
||||
|
||||
def stripfilename(filename):
|
||||
return re.sub(constants.FILENAME_STRIP_REGEX, "", filename)
|
||||
if filename:
|
||||
return re.sub(constants.FILENAME_STRIP_REGEX, "", filename)
|
||||
else:
|
||||
return ""
|
||||
|
||||
def stripRoomName(RoomName):
|
||||
if RoomName:
|
||||
try:
|
||||
return re.sub(constants.ROOM_NAME_STRIP_REGEX, "\g<roomnamebase>", RoomName)
|
||||
except IndexError:
|
||||
return RoomName
|
||||
else:
|
||||
return ""
|
||||
|
||||
def hashFilename(filename):
|
||||
return hashlib.sha256(stripfilename(filename).encode('utf-8')).hexdigest()[:12]
|
||||
@ -182,3 +209,69 @@ def sameFileduration (duration1, duration2):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def meetsMinVersion(version, minVersion):
|
||||
def versiontotuple(ver):
|
||||
return tuple(map(int, ver.split(".")))
|
||||
return versiontotuple(version) >= versiontotuple(minVersion)
|
||||
|
||||
class RoomPasswordProvider(object):
|
||||
CONTROLLED_ROOM_REGEX = re.compile("^\+(.*):(\w{12})$")
|
||||
PASSWORD_REGEX = re.compile("[A-Z]{2}-\d{3}-\d{3}")
|
||||
|
||||
@staticmethod
|
||||
def isControlledRoom(roomName):
|
||||
return bool(re.match(RoomPasswordProvider.CONTROLLED_ROOM_REGEX, roomName))
|
||||
|
||||
@staticmethod
|
||||
def check(roomName, password, salt):
|
||||
if not password or not re.match(RoomPasswordProvider.PASSWORD_REGEX, password):
|
||||
raise ValueError()
|
||||
|
||||
if not roomName:
|
||||
raise NotControlledRoom()
|
||||
match = re.match(RoomPasswordProvider.CONTROLLED_ROOM_REGEX, roomName)
|
||||
if not match:
|
||||
raise NotControlledRoom()
|
||||
roomHash = match.group(2)
|
||||
computedHash = RoomPasswordProvider._computeRoomHash(match.group(1), password, salt)
|
||||
return roomHash == computedHash
|
||||
|
||||
@staticmethod
|
||||
def getControlledRoomName(roomName, password, salt):
|
||||
return "+" + roomName + ":" + RoomPasswordProvider._computeRoomHash(roomName, password, salt)
|
||||
|
||||
@staticmethod
|
||||
def _computeRoomHash(roomName, password, salt):
|
||||
roomName = roomName.encode('utf8')
|
||||
salt = hashlib.sha256(salt).hexdigest()
|
||||
provisionalHash = hashlib.sha256(roomName + salt).hexdigest()
|
||||
return hashlib.sha1(provisionalHash + salt + password).hexdigest()[:12].upper()
|
||||
|
||||
class RandomStringGenerator(object):
|
||||
@staticmethod
|
||||
def generate_room_password():
|
||||
parts = (
|
||||
RandomStringGenerator._get_random_letters(2),
|
||||
RandomStringGenerator._get_random_numbers(3),
|
||||
RandomStringGenerator._get_random_numbers(3)
|
||||
)
|
||||
return "{}-{}-{}".format(*parts)
|
||||
|
||||
@staticmethod
|
||||
def generate_server_salt():
|
||||
parts = (
|
||||
RandomStringGenerator._get_random_letters(10),
|
||||
)
|
||||
return "{}".format(*parts)
|
||||
|
||||
@staticmethod
|
||||
def _get_random_letters(quantity):
|
||||
return ''.join(random.choice(string.ascii_uppercase) for _ in xrange(quantity))
|
||||
|
||||
@staticmethod
|
||||
def _get_random_numbers(quantity):
|
||||
return ''.join(random.choice(string.digits) for _ in xrange(quantity))
|
||||
|
||||
class NotControlledRoom(Exception):
|
||||
pass
|
||||
@ -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))
|
||||
reactor.listenTCP(int(args.port), SyncFactory(args.password, args.motd_file, args.isolate_rooms, args.salt))
|
||||
reactor.run()
|
||||
|
||||