Merge origin

This commit is contained in:
Et0h 2015-10-01 20:45:50 +01:00
commit 280922e928
15 changed files with 724 additions and 180 deletions

View File

@ -653,7 +653,8 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock
'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/chevrons_right.png', 'resources/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/user_key.png', 'resources/lock.png', 'resources/key_go.png', 'resources/page_white_key.png',
'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png', 'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png',
'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png' 'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png',
'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png'
] ]
resources = ["resources/icon.ico", "resources/syncplay.png"] resources = ["resources/icon.ico", "resources/syncplay.png"]
resources.extend(guiIcons) resources.extend(guiIcons)
@ -676,7 +677,7 @@ info = dict(
options={'py2exe': { options={'py2exe': {
'dist_dir': OUT_DIR, 'dist_dir': OUT_DIR,
'packages': 'PySide.QtUiTools', 'packages': 'PySide.QtUiTools',
'includes': 'twisted, sys, encodings, datetime, os, time, math, PySide, liburl', 'includes': 'twisted, sys, encodings, datetime, os, time, math, PySide, liburl, ast',
'excludes': 'venv, _ssl, doctest, pdb, unittest, win32clipboard, win32file, win32pdh, win32security, win32trace, win32ui, winxpgui, win32pipe, win32process, Tkinter', 'excludes': 'venv, _ssl, doctest, pdb, unittest, win32clipboard, win32file, win32pdh, win32security, win32trace, win32ui, winxpgui, win32pipe, win32process, Tkinter',
'dll_excludes': 'msvcr71.dll, MSVCP90.dll, POWRPROF.dll', 'dll_excludes': 'msvcr71.dll, MSVCP90.dll, POWRPROF.dll',
'optimize': 2, 'optimize': 2,

BIN
resources/arrow_refresh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

BIN
resources/film_go.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

View File

@ -5,7 +5,7 @@
Principal author: Etoh Principal author: Etoh
Other contributors: DerGenaue, jb Other contributors: DerGenaue, jb
Project: http://syncplay.pl/ Project: http://syncplay.pl/
Version: 0.2.6 Version: 0.2.8
Note: Note:
* This interface module is intended to be used in conjunction with Syncplay. * This interface module is intended to be used in conjunction with Syncplay.
@ -84,7 +84,7 @@ You may also need to re-copy the syncplay.lua file when you update VLC.
--]==========================================================================] --]==========================================================================]
local connectorversion = "0.2.6" local connectorversion = "0.2.8"
local vlcversion = vlc.misc.version() local vlcversion = vlc.misc.version()
local durationdelay = 500000 -- Pause for get_duration command etc for increased reliability (uses microseconds) local durationdelay = 500000 -- Pause for get_duration command etc for increased reliability (uses microseconds)
local loopsleepduration = 2500 -- Pause for every event loop (uses microseconds) local loopsleepduration = 2500 -- Pause for every event loop (uses microseconds)
@ -329,8 +329,10 @@ function get_filepath ()
response = ":::DVD:::" response = ":::DVD:::"
else else
local metas = item:metas() local metas = item:metas()
if metas and metas["title"] and string.len(metas["title"]) > 0 then if metas and metas["url"] and string.len(metas["url"]) > 0 then
response = ":::(Stream: "..metas["title"]..")" response = metas["url"]
elseif item:uri() and string.len(item:uri()) > 0 then
response = item:uri()
else else
response = unknownstream response = unknownstream
end end

BIN
resources/world_go.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

View File

@ -1,4 +1,4 @@
version = '1.3.1' version = '1.3.4'
milestone = 'Chami' milestone = 'Chami'
release_number = '10' release_number = '21'
projectURL = 'http://syncplay.pl/' projectURL = 'http://syncplay.pl/'

View File

@ -3,9 +3,11 @@ import os.path
import time import time
import re import re
import sys import sys
import ast
from twisted.internet.protocol import ClientFactory from twisted.internet.protocol import ClientFactory
from twisted.internet import reactor, task from twisted.internet import reactor, task
from functools import wraps from functools import wraps
from copy import deepcopy
from syncplay.protocols import SyncClientProtocol from syncplay.protocols import SyncClientProtocol
from syncplay import utils, constants from syncplay import utils, constants
from syncplay.messages import getMissingStrings, getMessage from syncplay.messages import getMissingStrings, getMessage
@ -388,6 +390,9 @@ class SyncplayClient(object):
return self._globalPaused return self._globalPaused
def updateFile(self, filename, duration, path): def updateFile(self, filename, duration, path):
if utils.isURL(path):
filename = path
if not path: if not path:
return return
try: try:
@ -399,7 +404,7 @@ class SyncplayClient(object):
except: except:
size = 0 size = 0
filename, size = self.__executePrivacySettings(filename, size) filename, size = self.__executePrivacySettings(filename, size)
self.userlist.currentUser.setFile(filename, duration, size) self.userlist.currentUser.setFile(filename, duration, size, path)
self.sendFile() self.sendFile()
def __executePrivacySettings(self, filename, size): def __executePrivacySettings(self, filename, size):
@ -416,8 +421,20 @@ class SyncplayClient(object):
def setServerVersion(self, version): def setServerVersion(self, version):
self.serverVersion = version self.serverVersion = version
def getSanitizedCurrentUserFile(self):
if self.userlist.currentUser.file:
file_ = deepcopy(self.userlist.currentUser.file)
if constants.PRIVATE_FILE_FIELDS:
for PrivateField in constants.PRIVATE_FILE_FIELDS:
if file_.has_key(PrivateField):
file_.pop(PrivateField)
return file_
else:
return None
def sendFile(self): def sendFile(self):
file_ = self.userlist.currentUser.file file_ = self.getSanitizedCurrentUserFile()
if self._protocol and self._protocol.logged and file_: if self._protocol and self._protocol.logged and file_:
self._protocol.sendFileSetting(file_) self._protocol.sendFileSetting(file_)
@ -494,7 +511,10 @@ class SyncplayClient(object):
return return
self._running = True self._running = True
if self._playerClass: if self._playerClass:
reactor.callLater(0.1, self._playerClass.run, self, self._config['playerPath'], self._config['file'], self._config['playerArgs']) perPlayerArguments = utils.getPlayerArgumentsByPathAsArray(self._config['perPlayerArguments'],self._config['playerPath'])
if perPlayerArguments:
self._config['playerArgs'].extend(perPlayerArguments)
reactor.callLater(0.1, self._playerClass.run, self, self._config['playerPath'], self._config['file'], self._config['playerArgs'], )
self._playerClass = None self._playerClass = None
self.protocolFactory = SyncClientFactory(self) self.protocolFactory = SyncClientFactory(self)
port = int(port) port = int(port)
@ -670,9 +690,13 @@ class SyncplayClient(object):
response = f.read() response = f.read()
response = response.replace("<p>","").replace("</p>","").replace("<br />","").replace("&#8220;","\"").replace("&#8221;","\"") # Fix Wordpress response = response.replace("<p>","").replace("</p>","").replace("<br />","").replace("&#8220;","\"").replace("&#8221;","\"") # Fix Wordpress
response = json.loads(response) response = json.loads(response)
return response["version-status"], response["version-message"] if response.has_key("version-message") else None, response["version-url"] if response.has_key("version-url") else None publicServers = None
if response["public-servers"]:
publicServers = response["public-servers"].replace("&#8221;","'").replace(":&#8217;","'").replace("&#8217;","'").replace("&#8242;","'").replace("\n","").replace("\r","")
publicServers = ast.literal_eval(publicServers)
return response["version-status"], response["version-message"] if response.has_key("version-message") else None, response["version-url"] if response.has_key("version-url") else None, publicServers
except: except:
return "failed", getMessage("update-check-failed-notification").format(syncplay.version), constants.SYNCPLAY_DOWNLOAD_URL return "failed", getMessage("update-check-failed-notification").format(syncplay.version), constants.SYNCPLAY_DOWNLOAD_URL, None
class _WarningManager(object): class _WarningManager(object):
def __init__(self, player, userlist, ui, client): def __init__(self, player, userlist, ui, client):
@ -796,11 +820,12 @@ class SyncplayUser(object):
self.file = file_ self.file = file_
self._controller = False self._controller = False
def setFile(self, filename, duration, size): def setFile(self, filename, duration, size, path=None):
file_ = { file_ = {
"name": filename, "name": filename,
"duration": duration, "duration": duration,
"size": size "size": size,
"path": path
} }
self.file = file_ self.file = file_

View File

@ -16,6 +16,7 @@ SHOW_CONTACT_INFO = True # Displays dev contact details below list in GUI
SHOW_TOOLTIPS = True SHOW_TOOLTIPS = True
WARN_ABOUT_MISSING_STRINGS = False # (If debug mode is enabled) WARN_ABOUT_MISSING_STRINGS = False # (If debug mode is enabled)
FALLBACK_INITIAL_LANGUAGE = "en" FALLBACK_INITIAL_LANGUAGE = "en"
FALLBACK_PUBLIC_SYNCPLAY_SERVERS = [[u'syncplay.pl:8995 (France)', u'syncplay.pl:8995'],[u'syncplay.pl:8996 (France)', u'syncplay.pl:8996'],[u'syncplay.pl:8997 (France)', u'syncplay.pl:8997'],[u'syncplay.pl:8998 (France)', u'syncplay.pl:8998'],[u'syncplay.pl:8999 (France)', u'syncplay.pl:8999']]
#Overriden by config #Overriden by config
SHOW_OSD = True # Sends Syncplay messages to media player OSD SHOW_OSD = True # Sends Syncplay messages to media player OSD
@ -48,6 +49,11 @@ SERVER_STATE_INTERVAL = 1
WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1 WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1
AUTOPLAY_DELAY = 3.0 AUTOPLAY_DELAY = 3.0
SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear on some media players SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear on some media players
# Options for the File Switch feature:
FOLDER_SEARCH_TIMEOUT = 60.0 # Secs - How long to wait until searches in folder to update cache are aborted (may be longer than this if hard drive needs to spin up)
FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL = 120.0 # Secs - Frequency of updating cache when someone is playing a file not in current cache
#Usually there's no need to adjust these #Usually there's no need to adjust these
LAST_PAUSED_DIFF_THRESHOLD = 2 LAST_PAUSED_DIFF_THRESHOLD = 2
FILENAME_STRIP_REGEX = u"[-~_\.\[\](): ]" FILENAME_STRIP_REGEX = u"[-~_\.\[\](): ]"
@ -63,8 +69,9 @@ COMMANDS_AUTH = ['a','auth']
COMMANDS_TOGGLE = ['t','toggle'] COMMANDS_TOGGLE = ['t','toggle']
MPC_MIN_VER = "1.6.4" MPC_MIN_VER = "1.6.4"
VLC_MIN_VERSION = "2.2.1" VLC_MIN_VERSION = "2.2.1"
VLC_INTERFACE_MIN_VERSION = "0.2.6" VLC_INTERFACE_MIN_VERSION = "0.2.8"
VLC_LATENCY_ERROR_THRESHOLD = 2.0 VLC_LATENCY_ERROR_THRESHOLD = 2.0
MPV_UNRESPONSIVE_THRESHOLD = 60.0
CONTROLLED_ROOMS_MIN_VERSION = "1.3.0" CONTROLLED_ROOMS_MIN_VERSION = "1.3.0"
USER_READY_MIN_VERSION = "1.3.0" USER_READY_MIN_VERSION = "1.3.0"
MPC_PATHS = [ MPC_PATHS = [
@ -131,10 +138,12 @@ STYLE_NOFILEITEM_COLOR = 'blue'
STYLE_NOTCONTROLLER_COLOR = 'grey' STYLE_NOTCONTROLLER_COLOR = 'grey'
USERLIST_GUI_USERNAME_OFFSET = 21 # Pixels USERLIST_GUI_USERNAME_OFFSET = 21 # Pixels
USERLIST_GUI_USERNAME_COLUMN = 0
USERLIST_GUI_FILENAME_COLUMN = 3
MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4', '-af', 'scaletempo'] MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4', '-af-add', 'scaletempo']
# --quiet works with both mpv 0.2 and 0.3 MPV_ARGS = ['--force-window', '--idle', '--hr-seek=always', '--keep-open', '--af-add=scaletempo']
MPV_SLAVE_ARGS = ['--force-window', '--idle', '--hr-seek=always', '--quiet', '--keep-open', '--af=scaletempo', '--input-terminal=no', '--input-file=/dev/stdin'] MPV_SLAVE_ARGS = ['--quiet', '--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>', '--terminal=yes'] MPV_SLAVE_ARGS_NEW = ['--term-playing-msg=<SyncplayUpdateFile>\nANS_filename=${filename}\nANS_length=${=length}\nANS_path=${path}\n</SyncplayUpdateFile>', '--terminal=yes']
MPV_NEW_VERSION = False MPV_NEW_VERSION = False
VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek', VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek',
@ -163,6 +172,13 @@ CONFIG_NAME_MARKER = ":"
CONFIG_VALUE_MARKER = "=" CONFIG_VALUE_MARKER = "="
USERITEM_CONTROLLER_ROLE = 0 USERITEM_CONTROLLER_ROLE = 0
USERITEM_READY_ROLE = 1 USERITEM_READY_ROLE = 1
FILEITEM_SWITCH_ROLE = 1
FILEITEM_SWITCH_NO_SWITCH = 0
FILEITEM_SWITCH_FILE_SWITCH = 1
FILEITEM_SWITCH_STREAM_SWITCH = 2
SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params
SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/" SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/"
SYNCPLAY_PUBLIC_SERVER_LIST_URL = u"http://syncplay.pl/listpublicservers?{}" # Params
PRIVATE_FILE_FIELDS = ["path"]

View File

@ -38,7 +38,7 @@ en = {
"ready-to-unpause-notification" : u"You are now set as ready - unpause again to unpause", "ready-to-unpause-notification" : u"You are now set as ready - unpause again to unpause",
"set-as-ready-notification" : u"You are now set as ready", "set-as-ready-notification" : u"You are now set as ready",
"set-as-not-ready-notification" : u"You are now set as not ready", "set-as-not-ready-notification" : u"You are now set as not ready",
"autoplaying-notification" : u"Auto-playing in {}...", "autoplaying-notification" : u"Auto-playing in {}...", # Number of seconds until playback will start
"identifying-as-controller-notification" : u"Identifying as room operator with password '{}'...", "identifying-as-controller-notification" : u"Identifying as room operator with password '{}'...",
"failed-to-identify-as-controller-notification" : u"<{}> failed to identify as a room operator.", "failed-to-identify-as-controller-notification" : u"<{}> failed to identify as a room operator.",
@ -94,6 +94,7 @@ en = {
"vlc-interface-oldversion-warning": "Warning: Syncplay detected that an old version version of the Syncplay interface module for VLC was installed in the VLC directory. Please refer to the Syncplay User Guide at http://syncplay.pl/guide/ for instructions on how to install syncplay.lua.", "vlc-interface-oldversion-warning": "Warning: Syncplay detected that an old version version of the Syncplay interface module for VLC was installed in the VLC directory. Please refer to the Syncplay User Guide at http://syncplay.pl/guide/ for instructions on how to install syncplay.lua.",
"vlc-interface-not-installed": "Warning: The Syncplay interface module for VLC was not found in the VLC directory. As such, if you are running VLC 2.0 then VLC will use the syncplay.lua module contained within the Syncplay directory, but this will mean that other custom interface scripts and extensions will not work. Please refer to the Syncplay User Guide at http://syncplay.pl/guide/ for instructions on how to install syncplay.lua.", "vlc-interface-not-installed": "Warning: The Syncplay interface module for VLC was not found in the VLC directory. As such, if you are running VLC 2.0 then VLC will use the syncplay.lua module contained within the Syncplay directory, but this will mean that other custom interface scripts and extensions will not work. Please refer to the Syncplay User Guide at http://syncplay.pl/guide/ for instructions on how to install syncplay.lua.",
"media-player-latency-warning": u"Warning: The media player took {} seconds to respond. If you experience syncing issues then close applications to free up system resources, and if that doesn't work then try a different media player.", # Seconds to respond "media-player-latency-warning": u"Warning: The media player took {} seconds to respond. If you experience syncing issues then close applications to free up system resources, and if that doesn't work then try a different media player.", # Seconds to respond
"mpv-unresponsive-error": u"mpv has not responded for {} seconds so appears to have malfunctioned. Please restart Syncplay.", # Seconds to respond
# Client prompts # Client prompts
"enter-to-exit-prompt" : "Press enter to exit\n", "enter-to-exit-prompt" : "Press enter to exit\n",
@ -134,6 +135,11 @@ en = {
"invalid-seek-value" : u"Invalid seek value", "invalid-seek-value" : u"Invalid seek value",
"invalid-offset-value" : u"Invalid offset value", "invalid-offset-value" : u"Invalid offset value",
"switch-file-not-found-error" : u"Could not switch to file '{0}'. Syncplay looks in the folder of the currently playing file and specified media directories.", # File not found
"folder-search-timeout-error" : u"The search for media in '{}' was aborted as it took too long. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. Until Syncplay is restarted only the directory of the currently open file will be checked.", #Folder
"failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.",
# Client arguments # Client arguments
"argument-description" : 'Solution to synchronize playback of multiple MPlayer and MPC-HC instances over the network.', "argument-description" : 'Solution to synchronize playback of multiple MPlayer and MPC-HC instances over the network.',
"argument-epilog" : 'If no options supplied _config values will be used', "argument-epilog" : 'If no options supplied _config values will be used',
@ -166,7 +172,9 @@ en = {
"media-setting-title" : "Media player settings", "media-setting-title" : "Media player settings",
"executable-path-label" : "Path to media player:", "executable-path-label" : "Path to media player:",
"media-path-label" : "Path to media file:", "media-path-label" : "Path to media file:",
"player-arguments-label" : "Player arguments (if any):",
"browse-label" : "Browse", "browse-label" : "Browse",
"update-server-list-label" : u"Update list",
"more-title" : "Show more settings", "more-title" : "Show more settings",
"never-rewind-value" : "Never", "never-rewind-value" : "Never",
@ -200,6 +208,7 @@ en = {
"misc-label" : u"Misc", "misc-label" : u"Misc",
"core-behaviour-title" : "Core room behaviour", "core-behaviour-title" : "Core room behaviour",
"syncplay-internals-title" : u"Syncplay internals", "syncplay-internals-title" : u"Syncplay internals",
"syncplay-mediasearchdirectories-title" : u"Directories to search for media (one path per line)",
"sync-label" : "Sync", "sync-label" : "Sync",
"sync-otherslagging-title" : "If others are lagging behind...", "sync-otherslagging-title" : "If others are lagging behind...",
"sync-youlaggging-title" : "If you are lagging behind...", "sync-youlaggging-title" : "If you are lagging behind...",
@ -270,6 +279,8 @@ en = {
"identifyascontroller-msgbox-label" : "Identify as room operator", "identifyascontroller-msgbox-label" : "Identify as room operator",
"identifyinfo-msgbox-label" : "Enter operator password for this room\r\n(see http://syncplay.pl/guide/ for usage instructions):", "identifyinfo-msgbox-label" : "Enter operator password for this room\r\n(see http://syncplay.pl/guide/ for usage instructions):",
"public-server-msgbox-label" : u"Select the public server for this viewing session",
"megabyte-suffix" : " MB", "megabyte-suffix" : " MB",
# Tooltips # Tooltips
@ -281,6 +292,8 @@ en = {
"executable-path-tooltip" : "Location of your chosen supported media player (MPC-HC, VLC, mplayer2 or mpv).", "executable-path-tooltip" : "Location of your chosen supported media player (MPC-HC, VLC, mplayer2 or mpv).",
"media-path-tooltip" : "Location of video or stream to be opened. Necessary for mpv and mplayer2.", "media-path-tooltip" : "Location of video or stream to be opened. Necessary for mpv and mplayer2.",
"player-arguments-tooltip" : "Additional command line arguments / switches to pass on to this media player.",
"mediasearcdirectories-arguments-tooltip" : u"Directories where Syncplay will search for media files, e.g. when you are using the click to switch feature. Syncplay will look recursively through sub-folders.",
"more-tooltip" : "Display less frequently used settings.", "more-tooltip" : "Display less frequently used settings.",
"filename-privacy-tooltip" : "Privacy mode for sending currently playing filename to server.", "filename-privacy-tooltip" : "Privacy mode for sending currently playing filename to server.",
@ -302,7 +315,7 @@ en = {
"showsameroomosd-tooltip" : "Show OSD notifications for events relating to room user is in.", "showsameroomosd-tooltip" : "Show OSD notifications for events relating to room user is in.",
"shownoncontrollerosd-tooltip" : "Show OSD notifications for events relating to non-operators who are in managed rooms.", "shownoncontrollerosd-tooltip" : "Show OSD notifications for events relating to non-operators who are in managed rooms.",
"showdifferentroomosd-tooltip" : "Show OSD notifications for events relating to room user is not in.", "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.", "showslowdownosd-tooltip" : "Show notifications of slowing down / reverting on time difference.",
"showdurationnotification-tooltip" : "Useful for when a segment in a multi-part file is missing, but can result in false positives.", "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.", "language-tooltip" : u"Language to be used by Syncplay.",
"unpause-always-tooltip" : u"If you press unpause it always sets you as ready and unpause, rather than just setting you as ready.", "unpause-always-tooltip" : u"If you press unpause it always sets you as ready and unpause, rather than just setting you as ready.",
@ -312,11 +325,13 @@ en = {
"help-tooltip" : "Opens the Syncplay.pl user guide.", "help-tooltip" : "Opens the Syncplay.pl user guide.",
"reset-tooltip" : "Reset all settings to the default configuration.", "reset-tooltip" : "Reset all settings to the default configuration.",
"update-server-list-tooltip" : u"Connect to syncplay.pl to update list of public servers.",
"joinroom-tooltip" : "Leave current room and joins specified room.", "joinroom-tooltip" : "Leave current room and joins specified room.",
"seektime-msgbox-label" : "Jump to specified time (in seconds / min:sec). Use +/- for relative seek.", "seektime-msgbox-label" : "Jump to specified time (in seconds / min:sec). Use +/- for relative seek.",
"ready-tooltip" : "Indicates whether you are ready to watch.", "ready-tooltip" : "Indicates whether you are ready to watch.",
"autoplay-tooltip" : "Auto-play when all users who have readiness indicator are ready and minimum user threshold met.", "autoplay-tooltip" : "Auto-play when all users who have readiness indicator are ready and minimum user threshold met.",
"switch-to-file-tooltip" : "Double click to switch to {}", # Filename
# In-userlist notes (GUI) # In-userlist notes (GUI)
"differentsize-note" : "Different size!", "differentsize-note" : "Different size!",
@ -372,7 +387,7 @@ ru = {
"retrying-notification" : u"%s, следующая попытка через %d секунд(ы)...", # Seconds "retrying-notification" : u"%s, следующая попытка через %d секунд(ы)...", # Seconds
"rewind-notification" : u"Перемотано из-за разницы во времени с <{}>", # User "rewind-notification" : u"Перемотано из-за разницы во времени с <{}>", # User
"fastforward-notification" : u"Fast-forwarded due to time difference with <{}>", # User # TODO: Translate into Russian "fastforward-notification" : u"Ускорено из-за разницы во времени с <{}>", # User
"slowdown-notification" : u"Воспроизведение замедлено из-за разницы во времени с <{}>", # User "slowdown-notification" : u"Воспроизведение замедлено из-за разницы во времени с <{}>", # User
"revert-notification" : u"Возвращаемся к нормальной скорости воспроизведения", "revert-notification" : u"Возвращаемся к нормальной скорости воспроизведения",
@ -388,39 +403,39 @@ ru = {
"playing-notification" : u"<{}> включил '{}' ({})", # User, file, duration "playing-notification" : u"<{}> включил '{}' ({})", # User, file, duration
"playing-notification/room-addendum" : u" в комнате: '{}'", # Room "playing-notification/room-addendum" : u" в комнате: '{}'", # Room
"not-all-ready" : u"Not ready: {}", # Usernames # TODO: Translate into Russian "not-all-ready" : u"Не готовы: {}", # Usernames
"all-users-ready" : u"Everyone is ready ({} users)", #Number of ready users # TODO: Translate into Russian "all-users-ready" : u"Все пользователи готовы ({} чел.)", #Number of ready users
"ready-to-unpause-notification" : u"You are now set as ready - unpause again to unpause", # TODO: Translate into Russian "ready-to-unpause-notification" : u"Вы помечены как готовый - нажмите еще раз, чтобы продолжить воспроизведение",
"set-as-ready-notification" : u"You are now set as ready", # TODO: Translate into Russian "set-as-ready-notification" : u"Вы помечены как готовый",
"set-as-not-ready-notification" : u"You are now set as not ready", # TODO: Translate into Russian "set-as-not-ready-notification" : u"Вы помечены как неготовый",
"autoplaying-notification" : u"Auto-playing in {}...", # TODO: Translate into Russian "autoplaying-notification" : u"Автовоспроизведение через {}...", # Number of seconds until playback will start
"identifying-as-controller-notification" : u"Identifying as room operator with password '{}'...", # TODO: Translate into Russian "identifying-as-controller-notification" : u"Идентификация как оператора комнаты с паролем '{}'...",
"failed-to-identify-as-controller-notification" : u"<{}> failed to identify as a room operator.", # TODO: Translate into Russian "failed-to-identify-as-controller-notification" : u"<{}> не прошел идентификацию в качестве оператора комнаты.",
"authenticated-as-controller-notification" : u"<{}> authenticated as a room operator", # TODO: Translate into Russian "authenticated-as-controller-notification" : u"<{}> вошел как оператор комнаты.",
"created-controlled-room-notification" : u"Created managed room '{}' with password '{}'. Please save this information for future reference!", # RoomName, operatorPassword # TODO: Translate into Russian "created-controlled-room-notification" : u"Создана управляемая комната '{}' с паролем '{}'. Сохраните эти данные!", # RoomName, operatorPassword
"file-different-notification" : u"Вероятно, файл, который Вы смотрите, отличается от того, который смотрит <{}>.", # User "file-different-notification" : u"Вероятно, файл, который Вы смотрите, отличается от того, который смотрит <{}>.", # User
"file-differences-notification" : u"Ваш файл отличается: {}", # Differences "file-differences-notification" : u"Ваш файл отличается: {}", # Differences
"room-file-differences" : u"File differences: {}", # File differences (filename, size, and/or duration) "room-file-differences" : u"Несовпадения файла: {}", # File differences (filename, size, and/or duration)
"file-difference-filename" : u"name", # TODO: Translate into Russian "file-difference-filename" : u"имя",
"file-difference-filesize" : u"size", # TODO: Translate into Russian "file-difference-filesize" : u"размер",
"file-difference-duration" : u"duration", # TODO: Translate into Russian "file-difference-duration" : u"длительность",
"alone-in-the-room" : u"В этой комнате кроме Вас никого нет.", "alone-in-the-room" : u"В этой комнате кроме Вас никого нет.",
"different-filesize-notification" : u" (размер Вашего файла не совпадает с размером их файла!)", "different-filesize-notification" : u" (размер Вашего файла не совпадает с размером их файла!)",
"userlist-playing-notification" : u"{} is playing:", #Username # TODO: Translate into Russian (same as playing-notification?) "userlist-playing-notification" : u"{} смотрит:", #Username
"file-played-by-notification" : u"Файл: {} просматривают:", # File "file-played-by-notification" : u"Файл: {} просматривают:", # File
"no-file-played-notification" : u"{} is not playing a file", # Username # TODO: Translate into Russian "no-file-played-notification" : u"{} не смотрит ничего", # Username
"notplaying-notification" : u"Люди, которые не смотрят ничего:", "notplaying-notification" : u"Люди, которые не смотрят ничего:",
"userlist-room-notification" : u"В комнате '{}':", # Room "userlist-room-notification" : u"В комнате '{}':", # Room
"userlist-file-notification" : u"File", # TODO: Translate into Russian (Файл?) "userlist-file-notification" : u"Файл",
"controller-userlist-userflag" : u"Operator", # TODO: Translate into Russian (this is to indicate a user is an operator in the ConsoleUI userlist) "controller-userlist-userflag" : u"Оператор",
"ready-userlist-userflag" : u"Ready", # TODO: Translate into Russian (this is to indicate a user is ready to watch in the ConsoleUI userlist) "ready-userlist-userflag" : u"Готов",
"update-check-failed-notification" : u"Could not automatically check whether Syncplay {} is up to date. Want to visit http://syncplay.pl/ to manually check for updates?", #Syncplay version # TODO: Translate into Russian "update-check-failed-notification" : u"Невозможно автоматически проверить, что версия Syncplay {} все еще актуальна. Хотите зайти на http://syncplay.pl/ и вручную проверить наличие обновлений?",
"syncplay-uptodate-notification" : u"Syncplay is up to date", # TODO: Translate into Russian "syncplay-uptodate-notification" : u"Syncplay обновлен",
"syncplay-updateavailable-notification" : u"A new version of Syncplay is available. Do you want to visit the release page?", # TODO: Translate into Russian "syncplay-updateavailable-notification" : u"Доступна новая версия Syncplay. Хотите открыть страницу релиза?",
"mplayer-file-required-notification" : u"Для использования Syncplay с mplayer необходимо передать файл в качестве параметра", "mplayer-file-required-notification" : u"Для использования Syncplay с mplayer необходимо передать файл в качестве параметра",
"mplayer-file-required-notification/example" : u"Пример использования: syncplay [options] [url|path/]filename", "mplayer-file-required-notification/example" : u"Пример использования: syncplay [options] [url|path/]filename",
@ -434,21 +449,22 @@ ru = {
"commandlist-notification/pause" : u"\tp - вкл./выкл. паузу", "commandlist-notification/pause" : u"\tp - вкл./выкл. паузу",
"commandlist-notification/seek" : u"\t[s][+-]time - перемотать к заданному моменту времени, если не указан + или -, то время считается абсолютным (от начала файла) в секундах или мин:сек", "commandlist-notification/seek" : u"\t[s][+-]time - перемотать к заданному моменту времени, если не указан + или -, то время считается абсолютным (от начала файла) в секундах или мин:сек",
"commandlist-notification/help" : u"\th - помощь", "commandlist-notification/help" : u"\th - помощь",
"commandlist-notification/toggle" : u"\tt - toggles whether you are ready to watch or not", # TODO: Translate into Russian "commandlist-notification/toggle" : u"\tt - переключить статус готов/неготов к просмотру",
"commandlist-notification/create" : u"\tc [name] - create managed room using name of current room", # TODO: Translate into Russian "commandlist-notification/create" : u"\tc [name] - создать управляемую комнату с таким же именем, как у текущей",
"commandlist-notification/auth" : u"\ta [password] - authenticate as room operator with operator password", # TODO: Translate into Russian "commandlist-notification/auth" : u"\ta [password] - авторизоваться как оператор комнаты с помощью пароля",
"syncplay-version-notification" : u"Версия Syncplay: {}", # syncplay.version "syncplay-version-notification" : u"Версия Syncplay: {}", # syncplay.version
"more-info-notification" : u"Больше информации на {}", # projectURL "more-info-notification" : u"Больше информации на {}", # projectURL
"gui-data-cleared-notification" : u"Syncplay очистил путь и информацию о состоянии окна, использованного GUI.", "gui-data-cleared-notification" : u"Syncplay очистил путь и информацию о состоянии окна, использованного GUI.",
"language-changed-msgbox-label" : u"Language will be changed when you run Syncplay.", # TODO: Translate into Russian "language-changed-msgbox-label" : u"Язык переключится при следующем запуске SYncplay.",
"promptforupdate-label" : u"Is it okay for Syncplay to automatically check for updates from time to time?", # TODO: Translate into Russian "promptforupdate-label" : u"Вы не против, если Syncplay будет автоматически изредка проверять наличие обновлений?",
"vlc-version-mismatch" : u"Внимание: Вы используете VLC устаревшей версии {}. К сожалению, Syncplay способен работать с VLC {} и выше.", # VLC version, VLC min version "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-version-mismatch" : u"Внимание: В используете модуль интерфейса Syncplay устаревшей версии {} для VLC. К сожалению, Syncplay способен работать с версией {} и выше.", # VLC interface version, VLC interface min version
"vlc-interface-oldversion-warning" : u"Внимание: Syncplay обнаружил, что старая версия модуля интерфейса Syncplay для VLC уже установлена в директорию VLC. Пожалуйста, обратитесь к Руководству Пользователя Syncplay (http://syncplay.pl/guide/) за инструкциями о том, как установить syncplay.lua.", "vlc-interface-oldversion-warning" : u"Внимание: Syncplay обнаружил, что старая версия модуля интерфейса Syncplay для VLC уже установлена в директорию VLC. Пожалуйста, обратитесь к Руководству Пользователя 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.", "vlc-interface-not-installed" : u"Внимание: Модуль интерфейса Syncplay для VLC не обнаружен в директории VLC. По существу, если Вы используете VLC 2.0, то VLC будет использовать модуль syncplay.lua из директории Syncplay, но в таком случае другие пользовательские скрипты и расширения интерфейса не будут работать. Пожалуйста, обратитесь к Руководству Пользователя Syncplay (http://syncplay.pl/guide/) за инструкциями о том, как установить syncplay.lua.",
"media-player-latency-warning": u"Warning: The media player took {} seconds to respond. If you experience syncing issues then close applications to free up system resources, and if that doesn't work then try a different media player.", # Seconds to respond # TODO: Translate into Russian "media-player-latency-warning": u"Внимание: У Вашего проигрывателя слишком большой отклик ({} секунд). Если Вы замечаете проблемы с синхронизацией, то закройте ресурсоемкие приложения, а если это не помогло - попробуйте другой проигрыватель.", # Seconds to respond
"mpv-unresponsive-error": u"mpv has not responded for {} seconds so appears to have malfunctioned. Please restart Syncplay.", # Seconds to respond # TODO: Translate to Russian
# Client prompts # Client prompts
"enter-to-exit-prompt" : u"Для выхода нажмите Enter\n", "enter-to-exit-prompt" : u"Для выхода нажмите Enter\n",
@ -463,18 +479,18 @@ ru = {
"player-path-error" : u"Путь к проигрывателю задан неверно.", "player-path-error" : u"Путь к проигрывателю задан неверно.",
"hostname-empty-error" : u"Имя пользователя не может быть пустым.", "hostname-empty-error" : u"Имя пользователя не может быть пустым.",
"empty-error" : u"{} не может быть пустым.", # Configuration "empty-error" : u"{} не может быть пустым.", # Configuration
"media-player-error" : u"Ошибка Media player: \"{}\"", # Error line "media-player-error" : u"Ошибка проигрывателя: \"{}\"", # Error line
"unable-import-gui-error" : u"Невозможно импортировать библиотеки GUI (графического интерфейса). Необходимо установить PySide, иначе графический интерфейс не будет работать.", "unable-import-gui-error" : u"Невозможно импортировать библиотеки GUI (графического интерфейса). Необходимо установить PySide, иначе графический интерфейс не будет работать.",
"arguments-missing-error" : u"Некоторые необходимые аргументы отсутствуют, обратитесь к --help", "arguments-missing-error" : u"Некоторые необходимые аргументы отсутствуют, обратитесь к --help",
"unable-to-start-client-error" : u"Невозможно запустить клиент", "unable-to-start-client-error" : u"Невозможно запустить клиент",
"player-path-config-error": u"Player path is not set properly", # TODO: Translate into Russian "player-path-config-error": u"Путь к проигрывателю установлен неверно",
"no-file-path-config-error" : u"File must be selected before starting your player", # TODO: Translate into Russian "no-file-path-config-error" : u"Файл должен быть указан до включения проигрывателя",
"no-hostname-config-error": u"Hostname can't be empty", # TODO: Translate into Russian "no-hostname-config-error": u"Имя сервера не может быть пустым",
"invalid-port-config-error" : u"Port must be valid", # TODO: Translate into Russian "invalid-port-config-error" : u"Неверный номер порта",
"empty-value-config-error" : u"{} can't be empty", # Config option # TODO: Translate into Russian "empty-value-config-error" : u"Поле '{}' не может быть пустым", # Config option
"not-json-error" : u"Не является закодированной json-строкой\n", "not-json-error" : u"Не является закодированной json-строкой\n",
"hello-arguments-error" : u"Не хватает аргументов Hello\n", "hello-arguments-error" : u"Не хватает аргументов Hello\n",
@ -484,10 +500,15 @@ ru = {
"vlc-failed-versioncheck" : u"Данная версия VLC не поддерживается Syncplay. Пожалуйста, используйте VLC версии 2 или выше.", "vlc-failed-versioncheck" : u"Данная версия VLC не поддерживается Syncplay. Пожалуйста, используйте VLC версии 2 или выше.",
"vlc-failed-other" : u"Во время загрузки скрипта интерфейса syncplay.lua в VLC произошла следующая ошибка: {}", # Syncplay Error "vlc-failed-other" : u"Во время загрузки скрипта интерфейса syncplay.lua в VLC произошла следующая ошибка: {}", # Syncplay Error
"not-supported-by-server-error" : "This feature is not supported by the server. The feature requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion #TODO: Translate into Russian "not-supported-by-server-error" : u"Эта возможность не поддерживается сервером. The feature requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion #TODO: Translate into Russian
"invalid-seek-value" : u"Invalid seek value", # TODO: Translate into Russian "invalid-seek-value" : u"Некорректное значение для перемотки",
"invalid-offset-value" : u"Invalid offset value", # TODO: Translate into Russian "invalid-offset-value" : u"Некорректное смещение",
"switch-file-not-found-error" : u"Невозможно переключиться на файл '{0}'. Syncplay looks in the folder of the currently playing file and specified media directories.", # File not found # TODO: Translate last part into Russian
"folder-search-timeout-error" : u"The search for media in '{}' was aborted as it took too long. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. Until Syncplay is restarted only the directory of the currently open file will be checked.", #Folder # TODO: Translate into Russian
"failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.", # TODO: Translate into Russian
# Client arguments # Client arguments
"argument-description" : u'Решение для синхронного воспроизведения в VLC, MPlayer или MPC-HC через Интернет.', "argument-description" : u'Решение для синхронного воспроизведения в VLC, MPlayer или MPC-HC через Интернет.',
@ -521,7 +542,9 @@ ru = {
"media-setting-title" : u"Параметры проигрывателя", "media-setting-title" : u"Параметры проигрывателя",
"executable-path-label" : u"Путь к проигрывателю:", "executable-path-label" : u"Путь к проигрывателю:",
"media-path-label" : u"Путь к видеофайлу:", "media-path-label" : u"Путь к видеофайлу:",
"player-arguments-label" : u"Аргументы для запуска проигрывателя:",
"browse-label" : u"Выбрать", "browse-label" : u"Выбрать",
"update-server-list-label" : u"Update list", # TODO: Translate into Russian
"more-title" : u"Больше настроек", "more-title" : u"Больше настроек",
"never-rewind-value" : u"Никогда", "never-rewind-value" : u"Никогда",
@ -531,38 +554,39 @@ ru = {
"privacy-dontsend-option" : u"не отпр.", "privacy-dontsend-option" : u"не отпр.",
"filename-privacy-label" : u"Имя файла:", "filename-privacy-label" : u"Имя файла:",
"filesize-privacy-label" : u"Размер файла:", "filesize-privacy-label" : u"Размер файла:",
"checkforupdatesautomatically-label" : u"Проверять обновления автоматически", # TODO: Confirm Russian translation "checkforupdatesautomatically-label" : u"Проверять обновления автоматически",
"slowondesync-label" : u"Замедлять при небольших рассинхронизациях (не поддерживаетя в MPC-HC)", "slowondesync-label" : u"Замедлять при небольших рассинхронизациях (не поддерживаетя в MPC-HC)",
"rewindondesync-label" : u"Перемотка при больших рассинхронизациях (настоятельно рекомендуется)", "rewindondesync-label" : u"Перемотка при больших рассинхронизациях (настоятельно рекомендуется)",
"dontslowdownwithme-label" : u"Никогда не замедлять или перематывать видео другим", # TODO: Update new wording into Russian (should state "Experimental" in brackets at the end) "dontslowdownwithme-label" : u"Никогда не замедлять и не перематывать видео другим (функция тестируется)",
"pauseonleave-label" : u"Приостанавливать, когда кто-то уходит (например, отключился)", "pauseonleave-label" : u"Приостанавливать, когда кто-то уходит (например, отключился)",
"readyatstart-label" : u"Set me as 'ready to watch' by default", # TODO: Translate into Russian "readyatstart-label" : u"Выставить статус 'готово к просмотру' по умолчанию",
"fastforwardondesync-label" : u"Fast-forward if lagging behind (recommended)", # TODO: Translate into Russian "fastforwardondesync-label" : u"Ускорять видео при отставании (рекомендуется)",
"forceguiprompt-label" : u"Не показывать больше этот диалог", # (Inverted) "forceguiprompt-label" : u"Не показывать больше этот диалог", # (Inverted)
"nostore-label" : u"Не сохранять текущую конфигурацию", # (Inverted) "nostore-label" : u"Не сохранять текущую конфигурацию", # (Inverted)
"showosd-label" : u"Включить экранные сообщения (поверх видео)", "showosd-label" : u"Включить экранные сообщения (поверх видео)",
"showosdwarnings-label" : u"Показывать предупреждения (напр., когда файлы не совпадают)", "showosdwarnings-label" : u"Показывать предупреждения (напр., когда файлы не совпадают)",
"showsameroomosd-label" : u"Показывать события Вашей комнаты", "showsameroomosd-label" : u"Показывать события Вашей комнаты",
"shownoncontrollerosd-label" : u"Include events from non-operators in managed rooms", # TODO: Translate into Russiann "shownoncontrollerosd-label" : u"Включить события, связанные с не-операторами в управляемой комнате.",
"showdifferentroomosd-label" : u"Показывать события других комнат", "showdifferentroomosd-label" : u"Показывать события других комнат",
"showslowdownosd-label" : u"Показывать уведомления о замедлении/перемотке", "showslowdownosd-label" : u"Показывать уведомления о замедлении/перемотке",
"language-label" : u"Language:", # TODO: Translate into Russian "language-label" : u"Язык:",
"automatic-language" : u"Default ({})", # Automatic language # TODO: Translate into Russian "automatic-language" : u"По умолчанию ({})", # Automatic language
"showdurationnotification-label" : u"Предупреждать о несовпадении продолжительности видео", "showdurationnotification-label" : u"Предупреждать о несовпадении продолжительности видео",
"basics-label" : u"Основное", "basics-label" : u"Основное",
"readiness-label" : u"Play/Pause", # TODO: Translate into Russian "readiness-label" : u"Воспроизведение/Пауза", # TODO: Confirm translation of play/pause
"misc-label" : u"Misc", # TODO: Translate into Russian "misc-label" : u"Прочее",
"core-behaviour-title" : "Core room behaviour", # TODO: Translate into Russian "core-behaviour-title" : u"Core room behaviour", # TODO: Translate into Russian
"syncplay-internals-title" : u"Syncplay internals", # TODO: Translate into Russian "syncplay-internals-title" : u"Syncplay internals", # TODO: Translate into Russian
"syncplay-mediasearchdirectories-title" : u"Directories to search for media (one path per line)", # TODO: Translate into Russian
"sync-label" : u"Синхронизация", "sync-label" : u"Синхронизация",
"sync-otherslagging-title" : u"If others are lagging behind...", # TODO: Translate into Russian "sync-otherslagging-title" : u"При отставании других зрителей...",
"sync-youlaggging-title" : u"If you are lagging behind...", # TODO: Translate into Russian "sync-youlaggging-title" : u"Когда я отстаю ...",
"messages-label" : u"Сообщения", "messages-label" : u"Сообщения",
"messages-osd-title" : u"On-screen Display settings", # TODO: Translate into Russian "messages-osd-title" : u"Настройки OSD",
"messages-other-title" : u"Other display settings", # TODO: Translate into Russian "messages-other-title" : u"Другие настройки отображения",
"privacy-label" : u"Приватность", "privacy-label" : u"Приватность",
"privacy-title" : u"Privacy settings", # TODO: Translate into Russian "privacy-title" : u"Настройки приватности",
"unpause-title" : u"If you press play, set as ready and:", # TODO: Translate into Russian "unpause-title" : u"If you press play, set as ready and:", # TODO: Translate into Russian
"unpause-ifalreadyready-option" : u"Unpause if already set as ready", # TODO: Translate into Russian "unpause-ifalreadyready-option" : u"Unpause if already set as ready", # TODO: Translate into Russian
"unpause-ifothersready-option" : u"Unpause if already ready or others in room are ready (default)", # TODO: Translate into Russian "unpause-ifothersready-option" : u"Unpause if already ready or others in room are ready (default)", # TODO: Translate into Russian
@ -581,17 +605,17 @@ ru = {
"undoseek-menu-label" : u"Отменить перемотку", "undoseek-menu-label" : u"Отменить перемотку",
"play-menu-label" : u"Play", "play-menu-label" : u"Play",
"pause-menu-label" : u"Пауза", "pause-menu-label" : u"Пауза",
"playbackbuttons-menu-label" : u"Show playback buttons", # TODO: Translate into Russian "playbackbuttons-menu-label" : u"Показывать кнопки воспроизведения",
"autoplay-menu-label" : u"Show auto-play button", # TODO: Translate into Russian "autoplay-menu-label" : u"Показывать кнопку автовоспроизведения",
"autoplay-guipushbuttonlabel" : u"Auto-play when everyone is ready", # TODO: Translate into Russian "autoplay-guipushbuttonlabel" : u"Воспроизвести автоматически, когда все будут готовы",
"autoplay-minimum-label" : u"Min users:", # TODO: Translate into Russian "autoplay-minimum-label" : u"Минимум пользователей:",
"ready-guipushbuttonlabel" : u"I'm ready to watch!", # TODO: Translate into Russian "ready-guipushbuttonlabel" : u"Я готов к просмотру!",
"roomuser-heading-label" : u"Комната / Пользователь", "roomuser-heading-label" : u"Комната / Пользователь",
"size-heading-label" : u"Size", # TODO: Translate into Russian "size-heading-label" : u"Размер",
"duration-heading-label" : u"Length", # TODO: Translate into Russian "duration-heading-label" : u"Длительность",
"filename-heading-label" : u"Filename", # TODO: Translate into Russian "filename-heading-label" : u"Имя файла",
"notifications-heading-label" : u"Уведомления", "notifications-heading-label" : u"Уведомления",
"userlist-heading-label" : u"Кто что смотрит", "userlist-heading-label" : u"Кто что смотрит",
@ -599,33 +623,35 @@ ru = {
"file-menu-label" : u"&Файл", # & precedes shortcut key "file-menu-label" : u"&Файл", # & precedes shortcut key
"openmedia-menu-label" : u"&Открыть видеофайл", "openmedia-menu-label" : u"&Открыть видеофайл",
"openstreamurl-menu-label" : u"Open &media stream URL", # TODO: Translate into Russian "openstreamurl-menu-label" : u"Открыть URL &потокового вещания",
"exit-menu-label" : u"&Выход", "exit-menu-label" : u"&Выход",
"advanced-menu-label" : u"&Дополнительно", "advanced-menu-label" : u"&Дополнительно",
"window-menu-label" : u"&Window", # TODO: Translate into Russian "window-menu-label" : u"&Окна",
"setoffset-menu-label" : u"Установить &смещение", "setoffset-menu-label" : u"Установить &смещение",
"createcontrolledroom-menu-label" : u"&Create managed room", # TODO: Translate into Russian "createcontrolledroom-menu-label" : u"&Создать управляемую комнату",
"identifyascontroller-menu-label" : u"&Identify as room operator", # TODO: Translate into Russian "identifyascontroller-menu-label" : u"&Войти как оператор комнаты",
"playback-menu-label" : u"&Playback", # TODO: Translate into Russian "playback-menu-label" : u"&Воспроизведение",
"help-menu-label" : u"&Помощь", "help-menu-label" : u"&Помощь",
"userguide-menu-label" : u"&Руководство Пользователя", "userguide-menu-label" : u"&Руководство Пользователя",
"update-menu-label" : u"Check for &update", # TODO: Translate into Russian "update-menu-label" : u"Проверить &обновления",
"setoffset-msgbox-label" : u"Установить смещение", "setoffset-msgbox-label" : u"Установить смещение",
"offsetinfo-msgbox-label" : u"Смещение (см. как использовать на http://syncplay.pl/guide/):", "offsetinfo-msgbox-label" : u"Смещение (см. инструкцию на странице http://syncplay.pl/guide/):",
"promptforstreamurl-msgbox-label" : u"Open media stream URL", # TODO: Translate into Russian "promptforstreamurl-msgbox-label" : u"Открыть URL потокового вещания",
"promptforstreamurlinfo-msgbox-label" : u"Stream URL", # TODO: Translate into Russian "promptforstreamurlinfo-msgbox-label" : u"URL потока",
"createcontrolledroom-msgbox-label" : u"Create managed room", # TODO: Translate into Russian "createcontrolledroom-msgbox-label" : u"Создать управляемую комнату",
"controlledroominfo-msgbox-label" : u"Enter name of managed room\r\n(see http://syncplay.pl/guide/ for usage instructions):", # TODO: Translate into Russian "controlledroominfo-msgbox-label" : u"Введите имя управляемой комнаты\r\n(см. инструкцию на странице http://syncplay.pl/guide/):",
"identifyascontroller-msgbox-label" : u"Identify as room operator", # TODO: Translate into Russian "identifyascontroller-msgbox-label" : u"Войти как оператор комнаты",
"identifyinfo-msgbox-label" : u"Enter operator password for this room\r\n(see http://syncplay.pl/guide/ for usage instructions):", # TODO: Translate into Russian "identifyinfo-msgbox-label" : u"Введите пароль оператора комнаты\r\n(см. инструкцию на странице http://syncplay.pl/guide/):",
"megabyte-suffix" : u" MB", # Technically it is a mebibyte # TODO: Translate into Russian "public-server-msgbox-label" : u"Select the public server for this viewing session", # TODO: Translate into Russian
"megabyte-suffix" : u" МБ", # Technically it is a mebibyte
# Tooltips # Tooltips
@ -636,6 +662,8 @@ ru = {
"executable-path-tooltip" : u"Расположение Вашего видеопроигрывателя (MPC-HC, VLC, mplayer2 или mpv).", "executable-path-tooltip" : u"Расположение Вашего видеопроигрывателя (MPC-HC, VLC, mplayer2 или mpv).",
"media-path-tooltip" : u"Расположение видеофайла или потока для просмотра. Обязательно для mpv и mplayer2.", "media-path-tooltip" : u"Расположение видеофайла или потока для просмотра. Обязательно для mpv и mplayer2.",
"player-arguments-tooltip" : u"Передавать дополнительные аргументы командной строки этому проигрывателю.",
"mediasearcdirectories-arguments-tooltip" : u"Directories where Syncplay will search for media files, e.g. when you are using the click to switch feature. Syncplay will look recursively through sub-folders.", # TODO: Translate into Russian
"more-tooltip" : u"Показать дополнительные настройки.", "more-tooltip" : u"Показать дополнительные настройки.",
"filename-privacy-tooltip" : u"Режим приватности для передачи имени воспроизводимого файла на сервер.", "filename-privacy-tooltip" : u"Режим приватности для передачи имени воспроизводимого файла на сервер.",
@ -643,23 +671,23 @@ ru = {
"privacy-sendraw-tooltip" : u"Отправляет эту информацию без шифрования. Рекомендуемая опция с наибольшей функциональностью.", "privacy-sendraw-tooltip" : u"Отправляет эту информацию без шифрования. Рекомендуемая опция с наибольшей функциональностью.",
"privacy-sendhashed-tooltip" : u"Отправляет хэш-сумму этой информации, делая ее невидимой для других пользователей.", "privacy-sendhashed-tooltip" : u"Отправляет хэш-сумму этой информации, делая ее невидимой для других пользователей.",
"privacy-dontsend-tooltip" : u"Не отправлять эту информацию на сервер. Предоставляет наибольшую приватность.", "privacy-dontsend-tooltip" : u"Не отправлять эту информацию на сервер. Предоставляет наибольшую приватность.",
"checkforupdatesautomatically-tooltip" : u"Regularly check with the Syncplay website to see whether a new version of Syncplay is available.", # TODO: Translate into Russian "checkforupdatesautomatically-tooltip" : u"Syncplay будет регулярно заходить на сайт и проверять наличие новых версий.",
"slowondesync-tooltip" : u"Временно уменьшить скорость воспроизведения в целях синхронизации с другими зрителями. Не поддерживается в MPC-HC.", "slowondesync-tooltip" : u"Временно уменьшить скорость воспроизведения в целях синхронизации с другими зрителями. Не поддерживается в MPC-HC.",
"dontslowdownwithme-tooltip" : u"Ваши лаги не будут влиять на других зрителей.", "dontslowdownwithme-tooltip" : u"Ваши лаги не будут влиять на других зрителей.",
"pauseonleave-tooltip" : u"Приостановить воспроизведение, если Вы покинули комнату или кто-то из зрителей отключился от сервера.", "pauseonleave-tooltip" : u"Приостановить воспроизведение, если Вы покинули комнату или кто-то из зрителей отключился от сервера.",
"readyatstart-tooltip" : u"Set yourself as 'ready' at start (otherwise you are set as 'not ready' until you change your readiness state)", # TODO: Translate into Russian "readyatstart-tooltip" : u"Отметить Вас готовым к просмотру сразу же (по умолчанию Вы отмечены не готовым)",
"forceguiprompt-tooltip" : u"Окно настройки не будет отображаться при открытии файла в Syncplay.", # (Inverted) "forceguiprompt-tooltip" : u"Окно настройки не будет отображаться при открытии файла в Syncplay.", # (Inverted)
"nostore-tooltip" : u"Запустить Syncplay с данной конфигурацией, но не сохранять изменения навсегда.", # (Inverted) "nostore-tooltip" : u"Запустить Syncplay с данной конфигурацией, но не сохранять изменения навсегда.", # (Inverted)
"rewindondesync-tooltip" : u"Перематывать назад, когда это необходимо для синхронизации. Отключение этой опции может привести к большим рассинхронизациям!", "rewindondesync-tooltip" : u"Перематывать назад, когда это необходимо для синхронизации. Отключение этой опции может привести к большим рассинхронизациям!",
"fastforwardondesync-tooltip" : u"Jump forward when out of sync with room operator (or your pretend position if 'Never slow down or rewind others' enabled).", # TODO: Translate into Russian "fastforwardondesync-tooltip" : u"Перематывать вперед при рассинхронизации с оператором комнаты (или если включена опция 'Никогда не замедлять и не перематывать видео другим').",
"showosd-tooltip" : u"Отправлять сообщения Syncplay в видеопроигрыватель и отображать их поверх видео (OSD - On Screen Display).", "showosd-tooltip" : u"Отправлять сообщения Syncplay в видеопроигрыватель и отображать их поверх видео (OSD - On Screen Display).",
"showosdwarnings-tooltip" : u"Показывать OSC-предупреждения, если проигрываются разные файлы или если Вы в комнате больше никого нет.", "showosdwarnings-tooltip" : u"Показывать OSC-предупреждения, если проигрываются разные файлы или если Вы в комнате больше никого нет.",
"showsameroomosd-tooltip" : u"Показывать OSD-уведомления о событиях, относящихся к комнате, в которой Вы находитесь.", "showsameroomosd-tooltip" : u"Показывать OSD-уведомления о событиях, относящихся к комнате, в которой Вы находитесь.",
"shownoncontrollerosd-tooltip" : u"Show OSD notifications for events relating to non-operators who are in controllerd rooms.", # TODO: Translate into Russian "shownoncontrollerosd-tooltip" : u"Показывать OSD-уведомления о событиях, относящихся к не-операторам в управляемой комнате.",
"showdifferentroomosd-tooltip" : u"Показывать OSD-уведомления о событиях, относящихся к любым другим комнатам.", "showdifferentroomosd-tooltip" : u"Показывать OSD-уведомления о событиях, относящихся к любым другим комнатам.",
"showslowdownosd-tooltip" : u"Показывать уведомления о замедлении или перемотке в целях синхронизации.", "showslowdownosd-tooltip" : u"Показывать уведомления о замедлении или перемотке в целях синхронизации.",
"showdurationnotification-tooltip" : u"Полезно, когда сегмент составного файла отсутствует. Возможны ложные срабатывания.", "showdurationnotification-tooltip" : u"Полезно, когда сегмент составного файла отсутствует. Возможны ложные срабатывания.",
"language-tooltip" : u"Language to be used by Syncplay.", # TODO: Translate into Russian "language-tooltip" : u"Язык, используемый Syncplay.",
"unpause-always-tooltip" : u"If you press unpause it always sets you as ready and unpause, rather than just setting you as ready.", # TODO: Translate into Russian "unpause-always-tooltip" : u"If you press unpause it always sets you as ready and unpause, rather than just setting you as ready.", # TODO: Translate into Russian
"unpause-ifalreadyready-tooltip" : u"If you press unpause when not ready it will set you as ready - press unpause again to unpause.", # TODO: Translate into Russian "unpause-ifalreadyready-tooltip" : u"If you press unpause when not ready it will set you as ready - press unpause again to unpause.", # TODO: Translate into Russian
"unpause-ifothersready-tooltip" : u"If you press unpause when not ready, it will only upause if others are ready.", # TODO: Translate into Russian "unpause-ifothersready-tooltip" : u"If you press unpause when not ready, it will only upause if others are ready.", # TODO: Translate into Russian
@ -667,11 +695,13 @@ ru = {
"help-tooltip" : u"Открыть Руководство Пользователя на Syncplay.pl.", "help-tooltip" : u"Открыть Руководство Пользователя на Syncplay.pl.",
"reset-tooltip" : u"Сбрасывает все настройки Syncplay в начальное состояние.", "reset-tooltip" : u"Сбрасывает все настройки Syncplay в начальное состояние.",
"update-server-list-tooltip" : u"Connect to syncplay.pl to update list of public servers.", # TODO: Translate to Russian
"joinroom-tooltip" : u"Покинуть комнату и зайти в другую, указанную комнату.", "joinroom-tooltip" : u"Покинуть комнату и зайти в другую, указанную комнату.",
"seektime-msgbox-label" : u"Перемотать к определенному моменту времени (указывать в секундах или мин:сек). Используйте +/-, чтобы перемотать вперед/назад относительно настоящего момента.", "seektime-msgbox-label" : u"Перемотать к определенному моменту времени (указывать в секундах или мин:сек). Используйте +/-, чтобы перемотать вперед/назад относительно настоящего момента.",
"ready-tooltip" : "Indicates whether you are ready to watch.", # TODO: Translate into Russian "ready-tooltip" : u"Показывает, готовы ли Вы к просмотру или нет.",
"autoplay-tooltip" : "Auto-play when all users who have readiness indicator are ready and minimum user threshold met.", # TODO: Translate into Russian "autoplay-tooltip" : u"Автоматическое воспроизведение, когда все пользователи с индикаторами готовности будут готовы и присутствует достаточное число пользователей.",
"switch-to-file-tooltip" : u"Double click to switch to {}", # Filename # TODO: Translate to Russian
# In-userlist notes (GUI) # In-userlist notes (GUI)
"differentsize-note" : u"Размер файла не совпадает!", "differentsize-note" : u"Размер файла не совпадает!",
@ -683,10 +713,10 @@ ru = {
"new-syncplay-available-motd-message" : u"<NOTICE> Вы используете Syncplay версии {}. Доступна более новая версия на http://syncplay.pl/ . </NOTICE>", # ClientVersion "new-syncplay-available-motd-message" : u"<NOTICE> Вы используете Syncplay версии {}. Доступна более новая версия на http://syncplay.pl/ . </NOTICE>", # ClientVersion
# Server notifications # Server notifications
"welcome-server-notification" : u"Welcome to Syncplay server, ver. {0}", # version # TODO: Translate into Russian "welcome-server-notification" : u"Добро пожаловать на сервер Syncplay версии {0}", # version
"client-connected-room-server-notification" : u"{0}({2}) connected to room '{1}'", # username, host, room # TODO: Translate into Russian "client-connected-room-server-notification" : u"{0}({2}) подключился(-лась) к комнате '{1}'", # username, host, room
"client-left-server-notification" : u"{0} left server", # name # TODO: Translate into Russian "client-left-server-notification" : u"{0} покинул(а) сервер", # name
"no-salt-notification" : u"PLEASE NOTE: To allow room operator passwords generated by this server instance to still work when the server is restarted, please add the following command line argument when running the Syncplay server in the future: --salt {}", #Salt # TODO: Translate into Russian "no-salt-notification" : u"ВНИМАНИЕ: Чтобы сгенерированные сервером пароли операторов комнат работали после перезагрузки сервера, необходимо указать следующий аргумент командной строки при запуске сервера Syncplay: --salt {}", #Salt
# Server arguments # Server arguments
"server-argument-description" : u'Решение для синхронного воспроизведения в VLC, MPlayer или MPC-HC через Интернет. Серверная часть', "server-argument-description" : u'Решение для синхронного воспроизведения в VLC, MPlayer или MPC-HC через Интернет. Серверная часть',
@ -694,8 +724,8 @@ ru = {
"server-port-argument" : u'номер TCP порта сервера', "server-port-argument" : u'номер TCP порта сервера',
"server-password-argument" : u'пароль к серверу', "server-password-argument" : u'пароль к серверу',
"server-isolate-room-argument" : u'должны ли комнаты быть изолированными?', "server-isolate-room-argument" : u'должны ли комнаты быть изолированными?',
"server-salt-argument" : u"random string used to generate managed room passwords", # TODO: Translate into Russian (note: as you may already be aware, 'salt' means Соль (криптография)) "server-salt-argument" : u"генерировать пароли к управляемым комнатам на основании указанной строки (соли)",
"server-disable-ready-argument" : u"disable readiness feature", # TODO: Translate into Russian "server-disable-ready-argument" : u"отключить статусы готов/не готов",
"server-motd-argument" : u"путь к файлу, из которого будет извлекаться MOTD-сообщение", "server-motd-argument" : u"путь к файлу, из которого будет извлекаться MOTD-сообщение",
"server-messed-up-motd-unescaped-placeholders" : u"MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).", "server-messed-up-motd-unescaped-placeholders" : u"MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).",
"server-messed-up-motd-too-long" : u"MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).", "server-messed-up-motd-too-long" : u"MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).",
@ -746,9 +776,9 @@ de = {
"not-all-ready" : u"Noch nicht bereit: {}", # Usernames "not-all-ready" : u"Noch nicht bereit: {}", # Usernames
"all-users-ready" : u"Alle sind bereit ({} Nutzer)", #Number of ready users "all-users-ready" : u"Alle sind bereit ({} Nutzer)", #Number of ready users
"ready-to-unpause-notification" : u"Du bist bereit - noch einmal fortsetzen klicken zum abspielen", "ready-to-unpause-notification" : u"Du bist bereit - noch einmal fortsetzen klicken zum abspielen",
"set-as-ready-notification" : u"Du bist bereit", # TODO: Check German translation "set-as-ready-notification" : u"Du bist bereit",
"set-as-not-ready-notification" : u"You are now set as not ready", # TODO: Translate into German "set-as-not-ready-notification" : u"Du bist nicht bereit",
"autoplaying-notification" : u"Starte in {}...", "autoplaying-notification" : u"Starte in {}...", # Number of seconds until playback will start
"identifying-as-controller-notification" : u"Identifiziere als Raumleiter mit Passwort '{}'...", # TODO: find a better translation to "room operator" "identifying-as-controller-notification" : u"Identifiziere als Raumleiter mit Passwort '{}'...", # TODO: find a better translation to "room operator"
"failed-to-identify-as-controller-notification" : u"<{}> konnte sich nicht als Raumleiter identifizieren.", "failed-to-identify-as-controller-notification" : u"<{}> konnte sich nicht als Raumleiter identifizieren.",
@ -803,7 +833,8 @@ de = {
"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-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-warning": u"Warnung: Es ist eine alte Version des Syncplay Interface-Moduls für VLC im VLC-Verzeichnis installiert. In der Syncplay-Anleitung unter http://syncplay.pl/guide/ [Englisch] findest du Details zur Installation des syncplay.lua-Skripts.", "vlc-interface-oldversion-warning": u"Warnung: Es ist eine alte Version des Syncplay Interface-Moduls für VLC im VLC-Verzeichnis installiert. 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.", "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.",
"media-player-latency-warning": u"Warning: The media player took {} seconds to respond. If you experience syncing issues then close applications to free up system resources, and if that doesn't work then try a different media player.", # Seconds to respond # TODO: Translate into German "media-player-latency-warning": u"Warnung: Der Mediaplayer brauchte {} Sekunden zum Antworten. Wenn Probleme bei der Synchronisation auftreten, schließe bitte andere Anwendungen, um Ressourcen freizugeben. Sollte das nicht funktionieren, versuche es mit einem anderen Media-Player.", # Seconds to respond
"mpv-unresponsive-error": u"mpv has not responded for {} seconds so appears to have malfunctioned. Please restart Syncplay.", # Seconds to respond # TODO: Translate to German
# Client prompts # Client prompts
"enter-to-exit-prompt" : u"Enter drücken zum Beenden\n", "enter-to-exit-prompt" : u"Enter drücken zum Beenden\n",
@ -839,11 +870,16 @@ de = {
"vlc-failed-versioncheck": u"Diese VLC-Version wird von Syncplay nicht unterstützt. Bitte nutze VLC 2.0", "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 "vlc-failed-other" : u"Beim Laden des syncplay.lua Interface-Skripts durch VLC trat folgender Fehler auf: {}", # Syncplay Error
"not-supported-by-server-error" : "Dieses Feature wird vom Server nicht unterstützt. Es wird ein Server mit Syncplay Version {}+ benötigt, aktuell verwendet wird jedoch Version {}.", #minVersion, serverVersion "not-supported-by-server-error" : u"Dieses Feature wird vom Server nicht unterstützt. Es wird ein Server mit Syncplay Version {}+ benötigt, aktuell verwendet wird jedoch Version {}.", #minVersion, serverVersion
"invalid-seek-value" : u"Ungültige Zeitangabe", "invalid-seek-value" : u"Ungültige Zeitangabe",
"invalid-offset-value" : u"Ungültiger Offset-Wert", "invalid-offset-value" : u"Ungültiger Offset-Wert",
"switch-file-not-found-error" : u"Konnte nicht zur Datei '{0}' wechseln. Syncplay sucht im Ordner der aktuellen Datei und angegebenen Medien-Verzeichnissen.", # File not found, folder it was not found in
"folder-search-timeout-error" : u"Die Suche nach Mediendateien in '{}' wurde abgebrochen weil sie zu lange gedauert hat. Dies tritt auf, wenn ein zu durchsuchender Medienordner zu viele Unterordner hat. Syncplay wird bis zum Neustart nur noch das Verzeichnis der aktuellen Datei durchsuchen.", #Folder
"failed-to-load-server-list-error" : u"Konnte die Liste der öffentlichen Server nicht laden. Bitte besuche http://www.syncplay.pl/ [Englisch] mit deinem Browser.",
# Client arguments # Client arguments
"argument-description" : u'Syncplay ist eine Anwendung um mehrere MPlayer, MPC-HC und VLC-Instanzen über das Internet zu synchronisieren.', "argument-description" : u'Syncplay ist eine 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', "argument-epilog" : u'Wenn keine Optionen angegeben sind, werden die _config-Werte verwendet',
@ -876,7 +912,9 @@ de = {
"media-setting-title" : u"Media-Player Einstellungen", "media-setting-title" : u"Media-Player Einstellungen",
"executable-path-label" : u"Pfad zum Media-Player:", "executable-path-label" : u"Pfad zum Media-Player:",
"media-path-label" : u"Pfad zur Datei:", "media-path-label" : u"Pfad zur Datei:",
"player-arguments-label" : u"Playerparameter:",
"browse-label" : u"Durchsuchen", "browse-label" : u"Durchsuchen",
"update-server-list-label" : u"Liste aktualisieren",
"more-title" : u"Mehr Einstellungen zeigen", "more-title" : u"Mehr Einstellungen zeigen",
"never-rewind-value" : u"Niemals", "never-rewind-value" : u"Niemals",
@ -901,13 +939,14 @@ de = {
"showdifferentroomosd-label" : u"Zeige Ereignisse in anderen Räumen", "showdifferentroomosd-label" : u"Zeige Ereignisse in anderen Räumen",
"showslowdownosd-label" : u"Zeige Verlangsamungs/Zurücksetzungs-Benachrichtigung", "showslowdownosd-label" : u"Zeige Verlangsamungs/Zurücksetzungs-Benachrichtigung",
"language-label" : u"Sprache:", "language-label" : u"Sprache:",
"automatic-language" : u"Automatisch ({})", # Default language # TODO: Change to German translation of "default" / Current translation represents Idea better "automatic-language" : u"Automatisch ({})", # Default language
"showdurationnotification-label" : u"Zeige Warnung wegen unterschiedlicher Dauer", "showdurationnotification-label" : u"Zeige Warnung wegen unterschiedlicher Dauer",
"basics-label" : u"Grundlagen", "basics-label" : u"Grundlagen",
"readiness-label" : u"Play/Pause", # TODO: Translate into German "readiness-label" : u"Play/Pause",
"misc-label" : u"Diverse", "misc-label" : u"Diverse",
"core-behaviour-title" : u"Verhalten des Raumes", "core-behaviour-title" : u"Verhalten des Raumes",
"syncplay-internals-title" : u"Syncplay intern", "syncplay-internals-title" : u"Syncplay intern",
"syncplay-mediasearchdirectories-title" : u"In diesen Verzeichnissen nach Medien suchen (ein Pfad pro Zeile)",
"sync-label" : u"Synchronisation", "sync-label" : u"Synchronisation",
"sync-otherslagging-title" : u"Wenn andere laggen...", "sync-otherslagging-title" : u"Wenn andere laggen...",
"sync-youlaggging-title" : u"Wenn du laggst...", "sync-youlaggging-title" : u"Wenn du laggst...",
@ -916,11 +955,11 @@ de = {
"messages-other-title" : u"Weitere Display-Einstellungen", "messages-other-title" : u"Weitere Display-Einstellungen",
"privacy-label" : u"Privatsphäre", "privacy-label" : u"Privatsphäre",
"privacy-title" : u"Privatsphäreneinstellungen", "privacy-title" : u"Privatsphäreneinstellungen",
"unpause-title" : u"If you press play, set as ready and:", # TODO: Translate into German "unpause-title" : u"Wenn du Play drückst, auf Bereit setzen und:",
"unpause-ifalreadyready-option" : u"Unpause if already set as ready", # TODO: Translate into German "unpause-ifalreadyready-option" : u"Wiedergeben wenn bereits als Bereit gesetzt",
"unpause-ifothersready-option" : u"Unpause if already ready or others in room are ready (default)", # TODO: Translate into German "unpause-ifothersready-option" : u"Wiedergeben wenn bereits als Bereit gesetzt oder alle anderen bereit sind (Standard)",
"unpause-ifminusersready-option" : u"Unpause if already ready or if all others ready and min users ready", # TODO: Translate into German "unpause-ifminusersready-option" : u"Wiedergeben wenn bereits als Bereit gesetzt oder die minimale Anzahl anderer Nutzer bereit ist",
"unpause-always" : u"Always unpause", # TODO: Translate into German "unpause-always" : u"Immer wiedergeben",
"help-label" : u"Hilfe", "help-label" : u"Hilfe",
"reset-label" : u"Standardwerte zurücksetzen", "reset-label" : u"Standardwerte zurücksetzen",
@ -978,6 +1017,8 @@ de = {
"identifyascontroller-msgbox-label" : u"Als Raumleiter identifizieren", "identifyascontroller-msgbox-label" : u"Als Raumleiter identifizieren",
"identifyinfo-msgbox-label" : u"Passwort des zentral gesteuerten Raums eingeben\r\n(siehe http://syncplay.pl/guide/ für eine Anleitung [Englisch]):", "identifyinfo-msgbox-label" : u"Passwort des zentral gesteuerten Raums eingeben\r\n(siehe http://syncplay.pl/guide/ für eine Anleitung [Englisch]):",
"public-server-msgbox-label" : u"Einen öffentlichen Server für diese Sitzung auswählen",
"megabyte-suffix" : u" MB", "megabyte-suffix" : u" MB",
# Tooltips # Tooltips
@ -989,6 +1030,8 @@ de = {
"executable-path-tooltip" : u"Pfad zum ausgewählten, unterstützten Mediaplayer (MPC-HC, VLC, mplayer2 or mpv).", "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.", "media-path-tooltip" : u"Pfad zum wiederzugebenden Video oder Stream. Notwendig für mpv und mplayer2.",
"player-arguments-tooltip" : u"Zusätzliche Kommandozeilenparameter / -schalter für diesen Mediaplayer.",
"mediasearcdirectories-arguments-tooltip" : u"Verzeichnisse, in denen Syncplay nach Mediendateien suchen soll, z.B. wenn du das Click-to-switch-Feature verwendest. Syncplay wird rekursiv Unterordner durchsuchen.", # TODO: Translate Click-to-switch? (or use as name for feature)
"more-tooltip" : u"Weitere Einstellungen anzeigen.", "more-tooltip" : u"Weitere Einstellungen anzeigen.",
"filename-privacy-tooltip" : u"Privatheitsmodus beim Senden des Namens der aktuellen Datei zum Server.", "filename-privacy-tooltip" : u"Privatheitsmodus beim Senden des Namens der aktuellen Datei zum Server.",
@ -1015,18 +1058,20 @@ de = {
"showslowdownosd-tooltip" : u"Meldungen bei Geschwindigkeitsänderung anzeigen.", "showslowdownosd-tooltip" : u"Meldungen bei Geschwindigkeitsänderung anzeigen.",
"showdurationnotification-tooltip" : u"Nützlich, wenn z.B. ein Teil eines mehrteiligen Videos fehlt, kann jedoch auch fehlerhaft anschlagen.", "showdurationnotification-tooltip" : u"Nützlich, wenn z.B. ein Teil eines mehrteiligen Videos fehlt, kann jedoch auch fehlerhaft anschlagen.",
"language-tooltip" : u"Die verwendete Sprache von Syncplay", "language-tooltip" : u"Die verwendete Sprache von Syncplay",
"unpause-always-tooltip" : u"Fortsetzen startet immer (anstatt nur den Bereitschaftsstatus zu ändern)", "unpause-always-tooltip" : u"Wiedergabe startet immer (anstatt nur den Bereitschaftsstatus zu ändern)",
"unpause-ifalreadyready-tooltip" : u"If you press unpause when not ready it will set you as ready - press unpause again to unpause.", # TODO: Translate into German "unpause-ifalreadyready-tooltip" : u"Wenn du nicht bereit bist und Play drückst wirst du als bereit gesetzt - zum Starten der Wiedergabe nochmal drücken.",
"unpause-ifothersready-tooltip" : u"If you press unpause when not ready, it will only upause if others are ready.", # TODO: Translate into German "unpause-ifothersready-tooltip" : u"Wenn du Play drückst und nicht bereit bist, wird nur gestartet, wenn alle anderen bereit sind.",
"unpause-ifminusersready-tooltip" : u"If you press unpause when not ready, it will only upause if others are ready and minimum users threshold is met.", # TODO: Translate into German "unpause-ifminusersready-tooltip" : u"Wenn du Play drückst und nicht bereit bist, wird nur gestartet, wenn die minimale Anzahl anderer Benutzer bereit ist.",
"help-tooltip" : u"Öffnet Hilfe auf syncplay.pl [Englisch]", "help-tooltip" : u"Öffnet Hilfe auf syncplay.pl [Englisch]",
"reset-tooltip" : u"Alle Einstellungen auf Standardwerte zurücksetzen.", "reset-tooltip" : u"Alle Einstellungen auf Standardwerte zurücksetzen.",
"update-server-list-tooltip" : u"Mit syncplay.pl verbinden um die Liste öffentlicher Server zu aktualisieren.",
"joinroom-tooltip" : u"Den aktuellen Raum verlassen und stattdessen den angegebenen betreten.", "joinroom-tooltip" : u"Den aktuellen Raum verlassen und stattdessen den angegebenen betreten.",
"seektime-msgbox-label" : u"Springe zur angegebenen Zeit (in Sekunden oder min:sek). Verwende +/- zum relativen Springen.", "seektime-msgbox-label" : u"Springe zur angegebenen Zeit (in Sekunden oder min:sek). Verwende +/- zum relativen Springen.",
"ready-tooltip" : "Zeigt an, ob du bereit zum anschauen bist", "ready-tooltip" : u"Zeigt an, ob du bereit zum anschauen bist",
"autoplay-tooltip" : "Automatisch abspielen, wenn alle Nutzer bereit sind und die minimale Nutzerzahl erreicht ist.", "autoplay-tooltip" : u"Automatisch abspielen, wenn alle Nutzer bereit sind oder die minimale Nutzerzahl erreicht ist.",
"switch-to-file-tooltip" : u"Doppelklicken um zu {} zu wechseln", # Filename
# In-userlist notes (GUI) # In-userlist notes (GUI)
"differentsize-note" : u"Verschiedene Größe!", "differentsize-note" : u"Verschiedene Größe!",

View File

@ -207,8 +207,12 @@ class MplayerPlayer(BasePlayer):
return constants.MPLAYER_ICONPATH return constants.MPLAYER_ICONPATH
@staticmethod @staticmethod
def getStartupArgs(path): def getStartupArgs(path, userArgs):
return constants.MPLAYER_SLAVE_ARGS args = []
if userArgs:
args.extend(userArgs)
args.extend(constants.MPLAYER_SLAVE_ARGS)
return args
@staticmethod @staticmethod
def isValidPlayerPath(path): def isValidPlayerPath(path):
@ -270,9 +274,7 @@ class MplayerPlayer(BasePlayer):
filePath = None filePath = None
else: else:
call.extend([filePath]) call.extend([filePath])
if args: call.extend(playerController.getStartupArgs(playerPath, args))
call.extend(args)
call.extend(playerController.getStartupArgs(playerPath))
# At least mpv may output escape sequences which result in syncplay # At least mpv may output escape sequences which result in syncplay
# trying to parse something like # trying to parse something like
# "\x1b[?1l\x1b>ANS_filename=blah.mkv". Work around this by # "\x1b[?1l\x1b>ANS_filename=blah.mkv". Work around this by

View File

@ -22,8 +22,11 @@ class MpvPlayer(MplayerPlayer):
return OldMpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args) return OldMpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args)
@staticmethod @staticmethod
def getStartupArgs(path): def getStartupArgs(path, userArgs):
args = constants.MPV_SLAVE_ARGS args = constants.MPV_ARGS
if userArgs:
args.extend(userArgs)
args.extend(constants.MPV_SLAVE_ARGS)
if constants.MPV_NEW_VERSION: if constants.MPV_NEW_VERSION:
args.extend(constants.MPV_SLAVE_ARGS_NEW) args.extend(constants.MPV_SLAVE_ARGS_NEW)
return args return args
@ -87,6 +90,9 @@ class OldMpvPlayer(MpvPlayer):
self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("mpv-version-error"), True) self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("mpv-version-error"), True)
self.drop() self.drop()
elif "[ytdl_hook] Your version of youtube-dl is too old" in line:
self._client.ui.showErrorMessage(line)
def _handleUnknownLine(self, line): def _handleUnknownLine(self, line):
self.mpvVersionErrorCheck(line) self.mpvVersionErrorCheck(line)
if "Playing: " in line: if "Playing: " in line:
@ -100,6 +106,14 @@ class OldMpvPlayer(MpvPlayer):
class NewMpvPlayer(OldMpvPlayer): class NewMpvPlayer(OldMpvPlayer):
lastResetTime = None lastResetTime = None
lastMPVPositionUpdate = None
def setPaused(self, value):
if self._paused <> value:
self._paused = not self._paused
self._listener.sendLine('cycle pause')
if value == False:
self.lastMPVPositionUpdate = time.time()
def _getProperty(self, property_): def _getProperty(self, property_):
floatProperties = ['length','time-pos'] floatProperties = ['length','time-pos']
@ -109,7 +123,24 @@ class NewMpvPlayer(OldMpvPlayer):
propertyID = property_ propertyID = property_
self._listener.sendLine(u"print_text ""ANS_{}=${{{}}}""".format(property_, propertyID)) self._listener.sendLine(u"print_text ""ANS_{}=${{{}}}""".format(property_, propertyID))
def getCalculatedPosition(self):
if self.fileLoaded == False:
return self._client.getGlobalPosition()
if self.lastMPVPositionUpdate is None:
return self._client.getGlobalPosition()
diff = time.time() - self.lastMPVPositionUpdate
if diff > constants.MPV_UNRESPONSIVE_THRESHOLD:
self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("mpv-unresponsive-error").format(int(diff)), True)
self.drop()
if diff > constants.PLAYER_ASK_DELAY and not self._paused:
self._client.ui.showDebugMessage("mpv did not response in time, so assuming position is {} ({}+{})".format(self._position + diff, self._position, diff))
return self._position + diff
else:
return self._position
def _storePosition(self, value): def _storePosition(self, value):
self.lastMPVPositionUpdate = time.time()
if self._recentlyReset(): if self._recentlyReset():
self._position = 0 self._position = 0
elif self._fileIsLoaded(): elif self._fileIsLoaded():
@ -130,7 +161,7 @@ class NewMpvPlayer(OldMpvPlayer):
self._getPosition() self._getPosition()
self._positionAsk.wait(constants.MPV_LOCK_WAIT_TIME) self._positionAsk.wait(constants.MPV_LOCK_WAIT_TIME)
self._pausedAsk.wait(constants.MPV_LOCK_WAIT_TIME) self._pausedAsk.wait(constants.MPV_LOCK_WAIT_TIME)
self._client.updatePlayerStatus(self._paused, self._position) self._client.updatePlayerStatus(self._paused if self.fileLoaded else self._client.getGlobalPaused(), self.getCalculatedPosition())
def _preparePlayer(self): def _preparePlayer(self):
if self.delayedFilePath: if self.delayedFilePath:
@ -146,6 +177,10 @@ class NewMpvPlayer(OldMpvPlayer):
self._clearFileLoaded() self._clearFileLoaded()
self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath))) self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath)))
def setPosition(self, value):
super(self.__class__, self).setPosition(value)
self.lastMPVPositionUpdate = time.time()
def openFile(self, filePath, resetPosition=False): def openFile(self, filePath, resetPosition=False):
if resetPosition: if resetPosition:
self.lastResetTime = time.time() self.lastResetTime = time.time()

View File

@ -2,6 +2,7 @@ from ConfigParser import SafeConfigParser, DEFAULTSECT
import argparse import argparse
import os import os
import sys import sys
import ast
from syncplay import constants, utils, version, milestone from syncplay import constants, utils, version, milestone
from syncplay.messages import getMessage, setLanguage, isValidLanguage from syncplay.messages import getMessage, setLanguage, isValidLanguage
from syncplay.players.playerFactory import PlayerFactory from syncplay.players.playerFactory import PlayerFactory
@ -31,6 +32,8 @@ class ConfigurationGetter(object):
"room": "", "room": "",
"password": None, "password": None,
"playerPath": None, "playerPath": None,
"perPlayerArguments": None,
"mediaSearchDirectories": None,
"file": None, "file": None,
"playerArgs": [], "playerArgs": [],
"playerClass": None, "playerClass": None,
@ -104,6 +107,11 @@ class ConfigurationGetter(object):
"autoplayInitialState", "autoplayInitialState",
] ]
self._serialised = [
"perPlayerArguments",
"mediaSearchDirectories",
]
self._numeric = [ self._numeric = [
"slowdownThreshold", "slowdownThreshold",
"rewindThreshold", "rewindThreshold",
@ -113,7 +121,7 @@ class ConfigurationGetter(object):
self._iniStructure = { self._iniStructure = {
"server_data": ["host", "port", "password"], "server_data": ["host", "port", "password"],
"client_settings": ["name", "room", "playerPath", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "unpauseAction", "pauseOnLeave", "readyAtStart", "autoplayMinUsers", "autoplayInitialState"], "client_settings": ["name", "room", "playerPath", "perPlayerArguments", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "unpauseAction", "pauseOnLeave", "readyAtStart", "autoplayMinUsers", "autoplayInitialState", "mediaSearchDirectories"],
"gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showDurationNotification"], "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showDurationNotification"],
"general": ["language", "checkForUpdatesAutomatically", "lastCheckedForUpdates"] "general": ["language", "checkForUpdatesAutomatically", "lastCheckedForUpdates"]
} }
@ -150,6 +158,12 @@ class ConfigurationGetter(object):
elif self._config[key] == "False": elif self._config[key] == "False":
self._config[key] = False self._config[key] = False
for key in self._serialised:
if self._config[key] is None or self._config[key] == "":
self._config[key] = {}
elif isinstance(self._config[key], (str, unicode)):
self._config[key] = ast.literal_eval(self._config[key])
for key in self._tristate: for key in self._tristate:
if self._config[key] == "True": if self._config[key] == "True":
self._config[key] = True self._config[key] = True

View File

@ -1,8 +1,9 @@
from PySide import QtCore, QtGui from PySide import QtCore, QtGui
from PySide.QtCore import QSettings, Qt, QCoreApplication, QUrl from PySide.QtCore import QSettings, Qt, QCoreApplication, QUrl
from PySide.QtGui import QApplication, QLineEdit, QCursor, QLabel, QCheckBox, QDesktopServices, QIcon, QImage, QButtonGroup, QRadioButton, QDoubleSpinBox from PySide.QtGui import QApplication, QLineEdit, QCursor, QLabel, QCheckBox, QDesktopServices, QIcon, QImage, QButtonGroup, QRadioButton, QDoubleSpinBox, QPlainTextEdit
from syncplay.players.playerFactory import PlayerFactory from syncplay.players.playerFactory import PlayerFactory
from datetime import datetime from datetime import datetime
from syncplay import utils
import os import os
import sys import sys
from syncplay.messages import getMessage, getLanguages, setLanguage, getInitialLanguage from syncplay.messages import getMessage, getLanguages, setLanguage, getInitialLanguage
@ -45,6 +46,7 @@ class ConfigDialog(QtGui.QDialog):
self.automaticupdatesCheckbox.setChecked(False) self.automaticupdatesCheckbox.setChecked(False)
def moreToggled(self): def moreToggled(self):
self.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)
if self.moreToggling == False: if self.moreToggling == False:
self.moreToggling = True self.moreToggling = True
@ -52,16 +54,24 @@ class ConfigDialog(QtGui.QDialog):
self.tabListFrame.show() self.tabListFrame.show()
self.resetButton.show() self.resetButton.show()
self.nostoreCheckbox.show() self.nostoreCheckbox.show()
self.playerargsTextbox.show()
self.playerargsLabel.show()
self.saveMoreState(True) self.saveMoreState(True)
self.tabListWidget.setCurrentRow(0) self.tabListWidget.setCurrentRow(0)
self.ensureTabListIsVisible() self.ensureTabListIsVisible()
self.stackedFrame.setFixedHeight(self.stackedFrame.minimumSizeHint().height())
else: else:
self.tabListFrame.hide() self.tabListFrame.hide()
self.resetButton.hide() self.resetButton.hide()
self.nostoreCheckbox.hide() self.nostoreCheckbox.hide()
self.playerargsTextbox.hide()
self.playerargsLabel.hide()
self.saveMoreState(False) self.saveMoreState(False)
self.stackedLayout.setCurrentIndex(0) self.stackedLayout.setCurrentIndex(0)
newHeight = self.connectionSettingsGroup.minimumSizeHint().height()+self.mediaplayerSettingsGroup.minimumSizeHint().height()+self.bottomButtonFrame.minimumSizeHint().height()+3
if self.error:
newHeight += self.errorLabel.height()+3
self.stackedFrame.setFixedHeight(newHeight)
self.adjustSize() self.adjustSize()
self.setFixedSize(self.sizeHint()) self.setFixedSize(self.sizeHint())
self.moreToggling = False self.moreToggling = False
@ -77,15 +87,8 @@ class ConfigDialog(QtGui.QDialog):
def openHelp(self): def openHelp(self):
self.QtGui.QDesktopServices.openUrl(QUrl("http://syncplay.pl/guide/client/")) self.QtGui.QDesktopServices.openUrl(QUrl("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): def safenormcaseandpath(self, path):
if self._isURL(path): if utils.isURL(path):
return path return path
else: else:
return os.path.normcase(os.path.normpath(path)) return os.path.normcase(os.path.normpath(path))
@ -104,7 +107,7 @@ class ConfigDialog(QtGui.QDialog):
foundpath = "" foundpath = ""
if playerpath != None and playerpath != "": if playerpath != None and playerpath != "":
if self._isURL(playerpath): if utils.isURL(playerpath):
foundpath = playerpath foundpath = playerpath
self.executablepathCombobox.addItem(foundpath) self.executablepathCombobox.addItem(foundpath)
@ -119,7 +122,7 @@ class ConfigDialog(QtGui.QDialog):
self.executablepathCombobox.addItem(foundpath) self.executablepathCombobox.addItem(foundpath)
for path in playerpathlist: for path in playerpathlist:
if self._isURL(path): if utils.isURL(path):
if foundpath == "": if foundpath == "":
foundpath = path foundpath = path
if path != playerpath: if path != playerpath:
@ -145,6 +148,20 @@ class ConfigDialog(QtGui.QDialog):
self.executableiconLabel.setPixmap(QtGui.QPixmap.fromImage(self.executableiconImage)) self.executableiconLabel.setPixmap(QtGui.QPixmap.fromImage(self.executableiconImage))
else: else:
self.executableiconLabel.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage())) self.executableiconLabel.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage()))
self.updatePlayerArguments(currentplayerpath)
def updatePlayerArguments(self, currentplayerpath):
argumentsForPath = utils.getPlayerArgumentsByPathAsText(self.perPlayerArgs, currentplayerpath)
self.playerargsTextbox.blockSignals(True)
self.playerargsTextbox.setText(argumentsForPath)
self.playerargsTextbox.blockSignals(False)
def changedPlayerArgs(self):
currentplayerpath = self.executablepathCombobox.currentText()
if currentplayerpath:
NewPlayerArgs = self.playerargsTextbox.text().split(u" ") if self.playerargsTextbox.text() else ""
self.perPlayerArgs[self.executablepathCombobox.currentText()]=NewPlayerArgs
def languageChanged(self): def languageChanged(self):
setLanguage(unicode(self.languageCombobox.itemData(self.languageCombobox.currentIndex()))) setLanguage(unicode(self.languageCombobox.itemData(self.languageCombobox.currentIndex())))
@ -188,6 +205,11 @@ class ConfigDialog(QtGui.QDialog):
else: else:
self.config["lastCheckedForUpdates"] = str(self.lastCheckedForUpdates) self.config["lastCheckedForUpdates"] = str(self.lastCheckedForUpdates)
def loadSavedPublicServerList(self):
settings = QSettings("Syncplay", "Interface")
settings.beginGroup("PublicServerList")
self.publicServers = settings.value("publicServers", None)
def loadMediaBrowseSettings(self): def loadMediaBrowseSettings(self):
settings = QSettings("Syncplay", "MediaBrowseDialog") settings = QSettings("Syncplay", "MediaBrowseDialog")
settings.beginGroup("MediaBrowseDialog") settings.beginGroup("MediaBrowseDialog")
@ -216,10 +238,34 @@ class ConfigDialog(QtGui.QDialog):
settings.setValue("ShowMoreSettings", morestate) settings.setValue("ShowMoreSettings", morestate)
settings.endGroup() settings.endGroup()
def updateServerList(self):
try:
servers = utils.getListOfPublicServers()
except IOError as e:
self.showErrorMessage(unicode(e))
return
currentServer = self.hostCombobox.currentText()
self.hostCombobox.clear()
if servers:
i = 0
for server in servers:
self.hostCombobox.addItem(server[1])
self.hostCombobox.setItemData(i, server[0], Qt.ToolTipRole)
i += 1
settings = QSettings("Syncplay", "Interface")
settings.beginGroup("PublicServerList")
settings.setValue("publicServers", servers)
self.hostCombobox.setEditText(currentServer)
def showErrorMessage(self, errorMessage):
QtGui.QMessageBox.warning(self, "Syncplay", errorMessage)
def browseMediapath(self): def browseMediapath(self):
self.loadMediaBrowseSettings() self.loadMediaBrowseSettings()
options = QtGui.QFileDialog.Options() options = QtGui.QFileDialog.Options()
if os.path.isdir(self.mediadirectory): if self.config["mediaSearchDirectories"] and os.path.isdir(self.config["mediaSearchDirectories"][0]):
defaultdirectory = self.config["mediaSearchDirectories"][0]
elif os.path.isdir(self.mediadirectory):
defaultdirectory = self.mediadirectory defaultdirectory = self.mediadirectory
elif os.path.isdir(QDesktopServices.storageLocation(QDesktopServices.MoviesLocation)): elif os.path.isdir(QDesktopServices.storageLocation(QDesktopServices.MoviesLocation)):
defaultdirectory = QDesktopServices.storageLocation(QDesktopServices.MoviesLocation) defaultdirectory = QDesktopServices.storageLocation(QDesktopServices.MoviesLocation)
@ -239,9 +285,12 @@ class ConfigDialog(QtGui.QDialog):
self.automaticUpdatePromptCheck() self.automaticUpdatePromptCheck()
self.loadLastUpdateCheckDate() self.loadLastUpdateCheckDate()
self.config["perPlayerArguments"] = self.perPlayerArgs
self.config["mediaSearchDirectories"] = utils.convertMultilineStringToList(self.mediasearchTextEdit.toPlainText())
self.processWidget(self, lambda w: self.saveValues(w)) self.processWidget(self, lambda w: self.saveValues(w))
if self.hostTextbox.text(): if self.hostCombobox.currentText():
self.config['host'] = self.hostTextbox.text() if ":" in self.hostTextbox.text() else self.hostTextbox.text() + ":" + unicode(constants.DEFAULT_PORT) self.config['host'] = self.hostCombobox.currentText() if ":" in self.hostCombobox.currentText() else self.hostCombobox.currentText() + ":" + unicode(constants.DEFAULT_PORT)
else: else:
self.config['host'] = None self.config['host'] = None
self.config['playerPath'] = unicode(self.safenormcaseandpath(self.executablepathCombobox.currentText())) self.config['playerPath'] = unicode(self.safenormcaseandpath(self.executablepathCombobox.currentText()))
@ -361,6 +410,7 @@ class ConfigDialog(QtGui.QDialog):
error = self.error error = self.error
if self.datacleared == True: if self.datacleared == True:
error = constants.ERROR_MESSAGE_MARKER + "{}".format(getMessage("gui-data-cleared-notification")) error = constants.ERROR_MESSAGE_MARKER + "{}".format(getMessage("gui-data-cleared-notification"))
self.error = error
if config['host'] == None: if config['host'] == None:
host = "" host = ""
elif ":" in config['host']: elif ":" in config['host']:
@ -368,10 +418,27 @@ class ConfigDialog(QtGui.QDialog):
else: else:
host = config['host'] + ":" + str(config['port']) host = config['host'] + ":" + str(config['port'])
self.perPlayerArgs = self.config["perPlayerArguments"]
self.mediaSearchDirectories = self.config["mediaSearchDirectories"]
self.connectionSettingsGroup = QtGui.QGroupBox(getMessage("connection-group-title")) self.connectionSettingsGroup = QtGui.QGroupBox(getMessage("connection-group-title"))
self.hostTextbox = QLineEdit(host, self) self.loadSavedPublicServerList()
self.hostCombobox = QtGui.QComboBox(self)
if self.publicServers:
i = 0
for publicServer in self.publicServers:
self.hostCombobox.addItem(publicServer[1])
self.hostCombobox.setItemData(i, publicServer[0], Qt.ToolTipRole)
i += 1
self.hostCombobox.setEditable(True)
self.hostCombobox.setEditText(host)
self.hostCombobox.setFixedWidth(165)
self.hostLabel = QLabel(getMessage("host-label"), self) self.hostLabel = QLabel(getMessage("host-label"), self)
self.findServerButton = QtGui.QPushButton(QtGui.QIcon(resourcespath + 'arrow_refresh.png'), getMessage("update-server-list-label"))
self.findServerButton.clicked.connect(self.updateServerList)
self.findServerButton.setToolTip(getMessage("update-server-list-tooltip"))
self.usernameTextbox = QLineEdit(self) self.usernameTextbox = QLineEdit(self)
self.usernameTextbox.setObjectName("name") self.usernameTextbox.setObjectName("name")
self.serverpassLabel = QLabel(getMessage("password-label"), self) self.serverpassLabel = QLabel(getMessage("password-label"), self)
self.defaultroomTextbox = QLineEdit(self) self.defaultroomTextbox = QLineEdit(self)
@ -380,7 +447,7 @@ class ConfigDialog(QtGui.QDialog):
self.defaultroomLabel = QLabel(getMessage("room-label"), self) self.defaultroomLabel = QLabel(getMessage("room-label"), self)
self.hostLabel.setObjectName("host") self.hostLabel.setObjectName("host")
self.hostTextbox.setObjectName(constants.LOAD_SAVE_MANUALLY_MARKER + "host") self.hostCombobox.setObjectName(constants.LOAD_SAVE_MANUALLY_MARKER + "host")
self.usernameLabel.setObjectName("name") self.usernameLabel.setObjectName("name")
self.usernameTextbox.setObjectName("name") self.usernameTextbox.setObjectName("name")
self.serverpassLabel.setObjectName("password") self.serverpassLabel.setObjectName("password")
@ -390,16 +457,21 @@ class ConfigDialog(QtGui.QDialog):
self.connectionSettingsLayout = QtGui.QGridLayout() self.connectionSettingsLayout = QtGui.QGridLayout()
self.connectionSettingsLayout.addWidget(self.hostLabel, 0, 0) self.connectionSettingsLayout.addWidget(self.hostLabel, 0, 0)
self.connectionSettingsLayout.addWidget(self.hostTextbox, 0, 1) self.connectionSettingsLayout.addWidget(self.hostCombobox, 0, 1)
self.connectionSettingsLayout.addWidget(self.findServerButton, 0, 2)
self.connectionSettingsLayout.addWidget(self.serverpassLabel, 1, 0) self.connectionSettingsLayout.addWidget(self.serverpassLabel, 1, 0)
self.connectionSettingsLayout.addWidget(self.serverpassTextbox, 1, 1) self.connectionSettingsLayout.addWidget(self.serverpassTextbox, 1, 1, 1, 2)
self.connectionSettingsLayout.addWidget(self.usernameLabel, 2, 0) self.connectionSettingsLayout.addWidget(self.usernameLabel, 2, 0)
self.connectionSettingsLayout.addWidget(self.usernameTextbox, 2, 1) self.connectionSettingsLayout.addWidget(self.usernameTextbox, 2, 1, 1, 2)
self.connectionSettingsLayout.addWidget(self.defaultroomLabel, 3, 0) self.connectionSettingsLayout.addWidget(self.defaultroomLabel, 3, 0)
self.connectionSettingsLayout.addWidget(self.defaultroomTextbox, 3, 1) self.connectionSettingsLayout.addWidget(self.defaultroomTextbox, 3, 1, 1, 2)
self.connectionSettingsGroup.setLayout(self.connectionSettingsLayout) self.connectionSettingsGroup.setLayout(self.connectionSettingsLayout)
self.connectionSettingsGroup.setMaximumHeight(self.connectionSettingsGroup.minimumSizeHint().height()) self.connectionSettingsGroup.setMaximumHeight(self.connectionSettingsGroup.minimumSizeHint().height())
self.playerargsTextbox = QLineEdit("", self)
self.playerargsTextbox.textEdited.connect(self.changedPlayerArgs)
self.playerargsLabel = QLabel(getMessage("player-arguments-label"), self)
self.mediaplayerSettingsGroup = QtGui.QGroupBox(getMessage("media-setting-title")) self.mediaplayerSettingsGroup = QtGui.QGroupBox(getMessage("media-setting-title"))
self.executableiconImage = QtGui.QImage() self.executableiconImage = QtGui.QImage()
self.executableiconLabel = QLabel(self) self.executableiconLabel = QLabel(self)
@ -423,6 +495,8 @@ class ConfigDialog(QtGui.QDialog):
self.executablepathCombobox.setObjectName("executable-path") self.executablepathCombobox.setObjectName("executable-path")
self.mediapathLabel.setObjectName("media-path") self.mediapathLabel.setObjectName("media-path")
self.mediapathTextbox.setObjectName(constants.LOAD_SAVE_MANUALLY_MARKER + "media-path") self.mediapathTextbox.setObjectName(constants.LOAD_SAVE_MANUALLY_MARKER + "media-path")
self.playerargsLabel.setObjectName("player-arguments")
self.playerargsTextbox.setObjectName(constants.LOAD_SAVE_MANUALLY_MARKER + "player-arguments")
self.mediaplayerSettingsLayout = QtGui.QGridLayout() self.mediaplayerSettingsLayout = QtGui.QGridLayout()
self.mediaplayerSettingsLayout.addWidget(self.executablepathLabel, 0, 0) self.mediaplayerSettingsLayout.addWidget(self.executablepathLabel, 0, 0)
@ -432,6 +506,8 @@ class ConfigDialog(QtGui.QDialog):
self.mediaplayerSettingsLayout.addWidget(self.mediapathLabel, 1, 0) self.mediaplayerSettingsLayout.addWidget(self.mediapathLabel, 1, 0)
self.mediaplayerSettingsLayout.addWidget(self.mediapathTextbox , 1, 2) self.mediaplayerSettingsLayout.addWidget(self.mediapathTextbox , 1, 2)
self.mediaplayerSettingsLayout.addWidget(self.mediabrowseButton , 1, 3) self.mediaplayerSettingsLayout.addWidget(self.mediabrowseButton , 1, 3)
self.mediaplayerSettingsLayout.addWidget(self.playerargsLabel, 2, 0, 1, 2)
self.mediaplayerSettingsLayout.addWidget(self.playerargsTextbox, 2, 2, 1, 2)
self.mediaplayerSettingsGroup.setLayout(self.mediaplayerSettingsLayout) self.mediaplayerSettingsGroup.setLayout(self.mediaplayerSettingsLayout)
self.showmoreCheckbox = QCheckBox(getMessage("more-title")) self.showmoreCheckbox = QCheckBox(getMessage("more-title"))
@ -448,15 +524,13 @@ class ConfigDialog(QtGui.QDialog):
self.errorLabel.setStyleSheet(constants.STYLE_SUCCESSLABEL) self.errorLabel.setStyleSheet(constants.STYLE_SUCCESSLABEL)
self.errorLabel.setText(error) self.errorLabel.setText(error)
self.errorLabel.setAlignment(Qt.AlignCenter) self.errorLabel.setAlignment(Qt.AlignCenter)
self.basicOptionsLayout.addWidget(self.errorLabel, 0, 0) self.basicOptionsLayout.addWidget(self.errorLabel, 0, 0)
self.connectionSettingsGroup.setMaximumHeight(self.connectionSettingsGroup.minimumSizeHint().height()) self.connectionSettingsGroup.setMaximumHeight(self.connectionSettingsGroup.minimumSizeHint().height())
self.basicOptionsLayout.setAlignment(Qt.AlignTop) self.basicOptionsLayout.setAlignment(Qt.AlignTop)
self.basicOptionsLayout.addWidget(self.connectionSettingsGroup) self.basicOptionsLayout.addWidget(self.connectionSettingsGroup)
self.basicOptionsLayout.addSpacing(5) self.basicOptionsLayout.addSpacing(5)
self.mediaplayerSettingsGroup.setMaximumHeight(self.mediaplayerSettingsGroup.minimumSizeHint().height())
self.basicOptionsLayout.addWidget(self.mediaplayerSettingsGroup) self.basicOptionsLayout.addWidget(self.mediaplayerSettingsGroup)
self.basicOptionsFrame.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)
self.basicOptionsFrame.setLayout(self.basicOptionsLayout) self.basicOptionsFrame.setLayout(self.basicOptionsLayout)
self.stackedLayout.addWidget(self.basicOptionsFrame) self.stackedLayout.addWidget(self.basicOptionsFrame)
@ -575,8 +649,21 @@ class ConfigDialog(QtGui.QDialog):
self.automaticupdatesCheckbox.setObjectName("checkForUpdatesAutomatically") self.automaticupdatesCheckbox.setObjectName("checkForUpdatesAutomatically")
self.internalSettingsLayout.addWidget(self.automaticupdatesCheckbox) self.internalSettingsLayout.addWidget(self.automaticupdatesCheckbox)
## Media path directories
self.mediasearchSettingsGroup = QtGui.QGroupBox(getMessage("syncplay-mediasearchdirectories-title"))
self.mediasearchSettingsLayout = QtGui.QVBoxLayout()
self.mediasearchSettingsGroup.setLayout(self.mediasearchSettingsLayout)
self.mediasearchTextEdit = QPlainTextEdit(utils.getListAsMultilineString(self.mediaSearchDirectories))
self.mediasearchTextEdit.setObjectName(constants.LOAD_SAVE_MANUALLY_MARKER + "mediasearcdirectories-arguments")
self.mediasearchTextEdit.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap)
self.mediasearchSettingsLayout.addWidget(self.mediasearchTextEdit)
self.mediasearchSettingsGroup.setMaximumHeight(self.mediasearchSettingsGroup.minimumSizeHint().height())
self.miscLayout.addWidget(self.coreSettingsGroup) self.miscLayout.addWidget(self.coreSettingsGroup)
self.miscLayout.addWidget(self.internalSettingsGroup) self.miscLayout.addWidget(self.internalSettingsGroup)
self.miscLayout.addWidget(self.mediasearchSettingsGroup)
self.miscLayout.setAlignment(Qt.AlignTop) self.miscLayout.setAlignment(Qt.AlignTop)
self.stackedLayout.addWidget(self.miscFrame) self.stackedLayout.addWidget(self.miscFrame)
@ -814,20 +901,37 @@ class ConfigDialog(QtGui.QDialog):
settings.beginGroup("Update") settings.beginGroup("Update")
settings.setValue("lastChecked", None) settings.setValue("lastChecked", None)
settings.endGroup() settings.endGroup()
settings.beginGroup("PublicServerList")
settings.setValue("publicServers", None)
settings.endGroup()
if not leaveMore: if not leaveMore:
settings = QSettings("Syncplay", "MoreSettings") settings = QSettings("Syncplay", "MoreSettings")
settings.clear() settings.clear()
self.datacleared = True self.datacleared = True
def populateEmptyServerList(self):
if self.publicServers is None:
if self.config["checkForUpdatesAutomatically"] == True:
self.updateServerList()
else:
currentServer = self.hostCombobox.currentText()
self.publicServers = constants.FALLBACK_PUBLIC_SYNCPLAY_SERVERS
i = 0
for server in self.publicServers:
self.hostCombobox.addItem(server[1])
self.hostCombobox.setItemData(i, server[0], Qt.ToolTipRole)
i += 1
self.hostCombobox.setEditText(currentServer)
def __init__(self, config, playerpaths, error, defaultConfig): def __init__(self, config, playerpaths, error, defaultConfig):
from syncplay import utils
self.config = config self.config = config
self.defaultConfig = defaultConfig self.defaultConfig = defaultConfig
self.playerpaths = playerpaths self.playerpaths = playerpaths
self.datacleared = False self.datacleared = False
self.config['resetConfig'] = False self.config['resetConfig'] = False
self.subitems = {} self.subitems = {}
self.publicServers = None
if self.config['clearGUIData'] == True: if self.config['clearGUIData'] == True:
self.config['clearGUIData'] = False self.config['clearGUIData'] = False
@ -866,14 +970,20 @@ class ConfigDialog(QtGui.QDialog):
self.mainLayout.addWidget(self.stackedFrame, 0, 1) self.mainLayout.addWidget(self.stackedFrame, 0, 1)
self.addBottomLayout() self.addBottomLayout()
if self.getMoreState() == False: if self.getMoreState() == False:
self.tabListFrame.hide() self.tabListFrame.hide()
self.nostoreCheckbox.hide() self.nostoreCheckbox.hide()
self.resetButton.hide() self.resetButton.hide()
self.playerargsTextbox.hide()
self.playerargsLabel.hide()
newHeight = self.connectionSettingsGroup.minimumSizeHint().height()+self.mediaplayerSettingsGroup.minimumSizeHint().height()+self.bottomButtonFrame.minimumSizeHint().height()+3
if self.error:
newHeight +=self.errorLabel.height()+3
self.stackedFrame.setFixedHeight(newHeight)
else: else:
self.showmoreCheckbox.setChecked(True) self.showmoreCheckbox.setChecked(True)
self.tabListWidget.setCurrentRow(0) self.tabListWidget.setCurrentRow(0)
self.stackedFrame.setFixedHeight(self.stackedFrame.minimumSizeHint().height())
self.showmoreCheckbox.toggled.connect(self.moreToggled) self.showmoreCheckbox.toggled.connect(self.moreToggled)
@ -885,4 +995,5 @@ class ConfigDialog(QtGui.QDialog):
if constants.SHOW_TOOLTIPS: if constants.SHOW_TOOLTIPS:
self.processWidget(self, lambda w: self.loadTooltips(w)) self.processWidget(self, lambda w: self.loadTooltips(w))
self.processWidget(self, lambda w: self.loadValues(w)) self.processWidget(self, lambda w: self.loadValues(w))
self.processWidget(self, lambda w: self.connectChildren(w)) self.processWidget(self, lambda w: self.connectChildren(w))
self.populateEmptyServerList()

View File

@ -4,11 +4,14 @@ from syncplay import utils, constants, version
from syncplay.messages import getMessage from syncplay.messages import getMessage
import sys import sys
import time import time
import urllib
from datetime import datetime from datetime import datetime
import re import re
import os import os
from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize, isURL
from functools import wraps from functools import wraps
from twisted.internet import task, threads
import threading
lastCheckedForUpdates = None lastCheckedForUpdates = None
class UserlistItemDelegate(QtGui.QStyledItemDelegate): class UserlistItemDelegate(QtGui.QStyledItemDelegate):
@ -17,15 +20,15 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate):
def sizeHint(self, option, index): def sizeHint(self, option, index):
size = QtGui.QStyledItemDelegate.sizeHint(self, option, index) size = QtGui.QStyledItemDelegate.sizeHint(self, option, index)
if (index.column() == 0): if (index.column() == constants.USERLIST_GUI_USERNAME_COLUMN):
size.setWidth(size.width() + constants.USERLIST_GUI_USERNAME_OFFSET) size.setWidth(size.width() + constants.USERLIST_GUI_USERNAME_OFFSET)
return size return size
def paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex): def paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex):
column = indexQModelIndex.column() column = indexQModelIndex.column()
if column == 0: if column == constants.USERLIST_GUI_USERNAME_COLUMN:
currentQAbstractItemModel = indexQModelIndex.model() currentQAbstractItemModel = indexQModelIndex.model()
itemQModelIndex = currentQAbstractItemModel.index(indexQModelIndex.row(), 0, indexQModelIndex.parent()) itemQModelIndex = currentQAbstractItemModel.index(indexQModelIndex.row(), constants.USERLIST_GUI_USERNAME_COLUMN, indexQModelIndex.parent())
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
resourcespath = utils.findWorkingDir() + "\\resources\\" resourcespath = utils.findWorkingDir() + "\\resources\\"
else: else:
@ -56,6 +59,29 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate):
isUserRow = indexQModelIndex.parent() != indexQModelIndex.parent().parent() isUserRow = indexQModelIndex.parent() != indexQModelIndex.parent().parent()
if isUserRow: if isUserRow:
optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+constants.USERLIST_GUI_USERNAME_OFFSET) optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+constants.USERLIST_GUI_USERNAME_OFFSET)
if column == constants.USERLIST_GUI_FILENAME_COLUMN:
if sys.platform.startswith('win'):
resourcespath = utils.findWorkingDir() + "\\resources\\"
else:
resourcespath = utils.findWorkingDir() + "/resources/"
currentQAbstractItemModel = indexQModelIndex.model()
itemQModelIndex = currentQAbstractItemModel.index(indexQModelIndex.row(), constants.USERLIST_GUI_FILENAME_COLUMN, indexQModelIndex.parent())
fileSwitchRole = currentQAbstractItemModel.data(itemQModelIndex, Qt.UserRole + constants.FILEITEM_SWITCH_ROLE)
if fileSwitchRole == constants.FILEITEM_SWITCH_FILE_SWITCH:
fileSwitchIconQPixmap = QtGui.QPixmap(resourcespath + "film_go.png")
itemQPainter.drawPixmap (
(optionQStyleOptionViewItem.rect.x()),
optionQStyleOptionViewItem.rect.y(),
fileSwitchIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio))
optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+16)
elif fileSwitchRole == constants.FILEITEM_SWITCH_STREAM_SWITCH:
streamSwitchIconQPixmap = QtGui.QPixmap(resourcespath + "world_go.png")
itemQPainter.drawPixmap (
(optionQStyleOptionViewItem.rect.x()),
optionQStyleOptionViewItem.rect.y(),
streamSwitchIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio))
optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+16)
QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex)
class MainWindow(QtGui.QMainWindow): class MainWindow(QtGui.QMainWindow):
@ -182,6 +208,149 @@ class MainWindow(QtGui.QMainWindow):
else: else:
super(MainWindow.PlaylistWidget, self).dropEvent(event) super(MainWindow.PlaylistWidget, self).dropEvent(event)
class FileSwitchManager(object):
def __init__(self):
self.fileSwitchTimer = task.LoopingCall(self.updateInfo)
self.fileSwitchTimer.start(constants.FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL, True)
mediaFilesCache = {}
filenameWatchlist = []
currentDirectory = None
mediaDirectories = None
lock = threading.Lock()
client = None
currentWindow = None
folderSearchEnabled = True
disabledDir = None
newInfo = False
currentlyUpdating = False
@staticmethod
def setWindow(window):
MainWindow.FileSwitchManager.currentWindow = window
@staticmethod
def setClient(newClient):
MainWindow.FileSwitchManager.client = newClient
@staticmethod
def setCurrentDirectory(curDir):
MainWindow.FileSwitchManager.currentDirectory = curDir
MainWindow.FileSwitchManager.updateInfo()
@staticmethod
def setMediaDirectories(mediaDirs):
MainWindow.FileSwitchManager.mediaDirectories = mediaDirs
MainWindow.FileSwitchManager.updateInfo()
@staticmethod
def checkForUpdate(self=None):
if MainWindow.FileSwitchManager.newInfo:
MainWindow.FileSwitchManager.newInfo = False
MainWindow.FileSwitchManager.infoUpdated()
@staticmethod
def updateInfo():
if len(MainWindow.FileSwitchManager.filenameWatchlist) > 0 or len(MainWindow.FileSwitchManager.mediaFilesCache) == 0 and MainWindow.FileSwitchManager.currentlyUpdating == False:
threads.deferToThread(MainWindow.FileSwitchManager._updateInfoThread).addCallback(MainWindow.FileSwitchManager.checkForUpdate)
@staticmethod
def setFilenameWatchlist(unfoundFilenames):
MainWindow.FileSwitchManager.filenameWatchlist = unfoundFilenames
@staticmethod
def _updateInfoThread():
if not MainWindow.FileSwitchManager.folderSearchEnabled:
if MainWindow.FileSwitchManager.areWatchedFilenamesInCurrentDir():
MainWindow.FileSwitchManager.newInfo = True
return
with MainWindow.FileSwitchManager.lock:
try:
MainWindow.FileSwitchManager.currentlyUpdating = True
dirsToSearch = MainWindow.FileSwitchManager.mediaDirectories
if dirsToSearch:
newMediaFilesCache = {}
startTime = time.time()
for directory in dirsToSearch:
for root, dirs, files in os.walk(directory):
newMediaFilesCache[root] = files
if time.time() - startTime > constants.FOLDER_SEARCH_TIMEOUT:
if MainWindow.FileSwitchManager.client is not None and MainWindow.FileSwitchManager.currentWindow is not None:
MainWindow.FileSwitchManager.disabledDir = directory
MainWindow.FileSwitchManager.folderSearchEnabled = False
if MainWindow.FileSwitchManager.areWatchedFilenamesInCurrentDir():
MainWindow.FileSwitchManager.newInfo = True
return
if MainWindow.FileSwitchManager.mediaFilesCache <> newMediaFilesCache:
MainWindow.FileSwitchManager.mediaFilesCache = newMediaFilesCache
MainWindow.FileSwitchManager.newInfo = True
elif MainWindow.FileSwitchManager.areWatchedFilenamesInCurrentDir():
MainWindow.FileSwitchManager.newInfo = True
finally:
MainWindow.FileSwitchManager.currentlyUpdating = False
@staticmethod
def infoUpdated():
if MainWindow.FileSwitchManager.areWatchedFilenamesInCache() or MainWindow.FileSwitchManager.areWatchedFilenamesInCurrentDir():
MainWindow.FileSwitchManager.updateListOfWhoIsPlayingWhat()
@staticmethod
def updateListOfWhoIsPlayingWhat():
if MainWindow.FileSwitchManager.client is not None:
MainWindow.FileSwitchManager.client.showUserList()
@staticmethod
def findFilepath(filename):
if filename is None:
return
if MainWindow.FileSwitchManager.currentDirectory is not None:
candidatePath = os.path.join(MainWindow.FileSwitchManager.currentDirectory,filename)
if os.path.isfile(candidatePath):
return candidatePath
if MainWindow.FileSwitchManager.mediaFilesCache is not None:
for directory in MainWindow.FileSwitchManager.mediaFilesCache:
files = MainWindow.FileSwitchManager.mediaFilesCache[directory]
if len(files) > 0 and filename in files:
filepath = os.path.join(directory, filename)
if os.path.isfile(filepath):
return filepath
@staticmethod
def areWatchedFilenamesInCurrentDir():
if MainWindow.FileSwitchManager.filenameWatchlist is not None and MainWindow.FileSwitchManager.currentDirectory is not None:
for filename in MainWindow.FileSwitchManager.filenameWatchlist:
potentialPath = os.path.join(MainWindow.FileSwitchManager.currentDirectory,filename)
if os.path.isfile(potentialPath):
return True
@staticmethod
def areWatchedFilenamesInCache():
if MainWindow.FileSwitchManager.filenameWatchlist is not None:
for filename in MainWindow.FileSwitchManager.filenameWatchlist:
if MainWindow.FileSwitchManager.isFilenameInCache(filename):
return True
@staticmethod
def isFilenameInCurrentDir(filename):
if filename is not None and MainWindow.FileSwitchManager.currentDirectory is not None:
potentialPath = os.path.join(MainWindow.FileSwitchManager.currentDirectory,filename)
if os.path.isfile(potentialPath):
return True
@staticmethod
def isFilenameInCache(filename):
if filename is not None and MainWindow.FileSwitchManager.mediaFilesCache is not None:
for directory in MainWindow.FileSwitchManager.mediaFilesCache:
files = MainWindow.FileSwitchManager.mediaFilesCache[directory]
if filename in files:
return True
class topSplitter(QtGui.QSplitter): class topSplitter(QtGui.QSplitter):
def createHandle(self): def createHandle(self):
return self.topSplitterHandle(self.orientation(), self) return self.topSplitterHandle(self.orientation(), self)
@ -206,10 +375,12 @@ class MainWindow(QtGui.QMainWindow):
def addClient(self, client): def addClient(self, client):
self._syncplayClient = client self._syncplayClient = client
MainWindow.FileSwitchManager.setClient(client)
self.roomInput.setText(self._syncplayClient.getRoom()) self.roomInput.setText(self._syncplayClient.getRoom())
self.config = self._syncplayClient.getConfig() self.config = self._syncplayClient.getConfig()
try: try:
self.updateReadyState(self.config['readyAtStart']) self.FileSwitchManager.setMediaDirectories(self.config["mediaSearchDirectories"])
self.updateReadyState(self.config['readyAtStart'])
autoplayInitialState = self.config['autoplayInitialState'] autoplayInitialState = self.config['autoplayInitialState']
if autoplayInitialState is not None: if autoplayInitialState is not None:
self.autoplayPushButton.blockSignals(True) self.autoplayPushButton.blockSignals(True)
@ -241,13 +412,31 @@ class MainWindow(QtGui.QMainWindow):
else: else:
self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "<br />") self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "<br />")
def getFileSwitchState(self, filename):
if filename:
if filename == getMessage("nofile-note"):
return constants.FILEITEM_SWITCH_NO_SWITCH
if self._syncplayClient.userlist.currentUser.file and filename == self._syncplayClient.userlist.currentUser.file['name']:
return constants.FILEITEM_SWITCH_NO_SWITCH
if isURL(filename):
return constants.FILEITEM_SWITCH_STREAM_SWITCH
elif filename not in self.newWatchlist:
if MainWindow.FileSwitchManager.findFilepath(filename):
return constants.FILEITEM_SWITCH_FILE_SWITCH
else:
self.newWatchlist.extend([filename])
return constants.FILEITEM_SWITCH_NO_SWITCH
def showUserList(self, currentUser, rooms): def showUserList(self, currentUser, rooms):
self._usertreebuffer = QtGui.QStandardItemModel() self._usertreebuffer = QtGui.QStandardItemModel()
self._usertreebuffer.setHorizontalHeaderLabels( self._usertreebuffer.setHorizontalHeaderLabels(
(getMessage("roomuser-heading-label"), getMessage("size-heading-label"), getMessage("duration-heading-label"), getMessage("filename-heading-label") )) (getMessage("roomuser-heading-label"), getMessage("size-heading-label"), getMessage("duration-heading-label"), getMessage("filename-heading-label") ))
usertreeRoot = self._usertreebuffer.invisibleRootItem() usertreeRoot = self._usertreebuffer.invisibleRootItem()
if self._syncplayClient.userlist.currentUser.file and self._syncplayClient.userlist.currentUser.file and os.path.isfile(self._syncplayClient.userlist.currentUser.file["path"]):
MainWindow.FileSwitchManager.setCurrentDirectory(os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"]))
for room in rooms: for room in rooms:
self.newWatchlist = []
roomitem = QtGui.QStandardItem(room) roomitem = QtGui.QStandardItem(room)
font = QtGui.QFont() font = QtGui.QFont()
font.setItalic(True) font.setItalic(True)
@ -279,7 +468,17 @@ class MainWindow(QtGui.QMainWindow):
if user.file: if user.file:
filesizeitem = QtGui.QStandardItem(formatSize(user.file['size'])) filesizeitem = QtGui.QStandardItem(formatSize(user.file['size']))
filedurationitem = QtGui.QStandardItem("({})".format(formatTime(user.file['duration']))) filedurationitem = QtGui.QStandardItem("({})".format(formatTime(user.file['duration'])))
filenameitem = QtGui.QStandardItem((user.file['name'])) filename = user.file['name']
if isURL(filename):
filename = urllib.unquote(filename)
filenameitem = QtGui.QStandardItem(filename)
fileSwitchState = self.getFileSwitchState(user.file['name']) if room == currentUser.room else None
if fileSwitchState != constants.FILEITEM_SWITCH_NO_SWITCH:
filenameTooltip = getMessage("switch-to-file-tooltip").format(filename)
else:
filenameTooltip = filename
filenameitem.setToolTip(filenameTooltip)
filenameitem.setData(fileSwitchState, Qt.UserRole + constants.FILEITEM_SWITCH_ROLE)
if currentUser.file: if currentUser.file:
sameName = sameFilename(user.file['name'], currentUser.file['name']) sameName = sameFilename(user.file['name'], currentUser.file['name'])
sameSize = sameFilesize(user.file['size'], currentUser.file['size']) sameSize = sameFilesize(user.file['size'], currentUser.file['size'])
@ -323,6 +522,13 @@ class MainWindow(QtGui.QMainWindow):
self.listTreeView.setRootIsDecorated(False) self.listTreeView.setRootIsDecorated(False)
self.listTreeView.expandAll() self.listTreeView.expandAll()
self.updateListGeometry() self.updateListGeometry()
MainWindow.FileSwitchManager.setFilenameWatchlist(self.newWatchlist)
self.checkForDisabledDir()
def checkForDisabledDir(self):
if MainWindow.FileSwitchManager.disabledDir is not None and MainWindow.FileSwitchManager.currentWindow is not None:
self.showErrorMessage(getMessage("folder-search-timeout-error").format(MainWindow.FileSwitchManager.disabledDir))
MainWindow.FileSwitchManager.disabledDir = None
def updateListGeometry(self): def updateListGeometry(self):
try: try:
@ -353,9 +559,25 @@ class MainWindow(QtGui.QMainWindow):
self.updateReadyIcon() self.updateReadyIcon()
def roomClicked(self, item): def roomClicked(self, item):
username = item.sibling(item.row(), 0).data()
filename = item.sibling(item.row(), 3).data()
while item.parent().row() != -1: while item.parent().row() != -1:
item = item.parent() item = item.parent()
self.joinRoom(item.sibling(item.row(), 0).data()) roomToJoin = item.sibling(item.row(), 0).data()
if roomToJoin <> self._syncplayClient.getRoom():
self.joinRoom(item.sibling(item.row(), 0).data())
elif username and filename and username <> self._syncplayClient.userlist.currentUser.username:
if self._syncplayClient.userlist.currentUser.file and filename == self._syncplayClient.userlist.currentUser.file:
return
if isURL(filename):
self._syncplayClient._player.openFile(filename)
else:
pathFound = MainWindow.FileSwitchManager.findFilepath(filename)
if pathFound:
self._syncplayClient._player.openFile(pathFound)
else:
MainWindow.FileSwitchManager.updateInfo()
self.showErrorMessage(getMessage("switch-file-not-found-error").format(filename))
@needsClient @needsClient
def userListChange(self): def userListChange(self):
@ -460,7 +682,13 @@ class MainWindow(QtGui.QMainWindow):
self.loadMediaBrowseSettings() self.loadMediaBrowseSettings()
options = QtGui.QFileDialog.Options() options = QtGui.QFileDialog.Options()
if os.path.isdir(self.mediadirectory): self.mediadirectory = ""
currentdirectory = os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"]) if self._syncplayClient.userlist.currentUser.file else None
if currentdirectory and os.path.isdir(currentdirectory):
defaultdirectory = currentdirectory
elif self.config["mediaSearchDirectories"] and os.path.isdir(self.config["mediaSearchDirectories"][0]):
defaultdirectory = self.config["mediaSearchDirectories"][0]
elif os.path.isdir(self.mediadirectory):
defaultdirectory = self.mediadirectory defaultdirectory = self.mediadirectory
elif os.path.isdir(QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.MoviesLocation)): elif os.path.isdir(QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.MoviesLocation)):
defaultdirectory = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.MoviesLocation) defaultdirectory = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.MoviesLocation)
@ -475,6 +703,7 @@ class MainWindow(QtGui.QMainWindow):
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
fileName = fileName.replace("/", "\\") fileName = fileName.replace("/", "\\")
self.mediadirectory = os.path.dirname(fileName) self.mediadirectory = os.path.dirname(fileName)
self.FileSwitchManager.setCurrentDirectory(self.mediadirectory)
self.saveMediaBrowseSettings() self.saveMediaBrowseSettings()
self._syncplayClient._player.openFile(fileName) self._syncplayClient._player.openFile(fileName)
@ -842,7 +1071,7 @@ class MainWindow(QtGui.QMainWindow):
self._syncplayClient.changeReadyState(self.readyPushButton.isChecked()) self._syncplayClient.changeReadyState(self.readyPushButton.isChecked())
else: else:
self.showDebugMessage("Tried to change ready state too soon.") self.showDebugMessage("Tried to change ready state too soon.")
@needsClient @needsClient
def changeAutoplayThreshold(self, source=None): def changeAutoplayThreshold(self, source=None):
self._syncplayClient.changeAutoPlayThrehsold(self.autoplayThresholdSpinbox.value()) self._syncplayClient.changeAutoPlayThrehsold(self.autoplayThresholdSpinbox.value())
@ -898,7 +1127,8 @@ class MainWindow(QtGui.QMainWindow):
@needsClient @needsClient
def checkForUpdates(self, userInitiated=False): def checkForUpdates(self, userInitiated=False):
self.lastCheckedForUpdates = datetime.utcnow() self.lastCheckedForUpdates = datetime.utcnow()
updateStatus, updateMessage, updateURL = self._syncplayClient.checkForUpdate(userInitiated) updateStatus, updateMessage, updateURL, self.publicServerList = self._syncplayClient.checkForUpdate(userInitiated)
if updateMessage is None: if updateMessage is None:
if updateStatus == "uptodate": if updateStatus == "uptodate":
updateMessage = getMessage("syncplay-uptodate-notification") updateMessage = getMessage("syncplay-uptodate-notification")
@ -975,6 +1205,10 @@ class MainWindow(QtGui.QMainWindow):
settings.beginGroup("Update") settings.beginGroup("Update")
settings.setValue("lastChecked", self.lastCheckedForUpdates) settings.setValue("lastChecked", self.lastCheckedForUpdates)
settings.endGroup() settings.endGroup()
settings.beginGroup("PublicServerList")
if self.publicServerList:
settings.setValue("publicServers", self.publicServerList)
settings.endGroup()
def loadSettings(self): def loadSettings(self):
settings = QSettings("Syncplay", "MainWindow") settings = QSettings("Syncplay", "MainWindow")
@ -997,10 +1231,19 @@ class MainWindow(QtGui.QMainWindow):
settings = QSettings("Syncplay", "Interface") settings = QSettings("Syncplay", "Interface")
settings.beginGroup("Update") settings.beginGroup("Update")
self.lastCheckedForUpdates = settings.value("lastChecked", None) self.lastCheckedForUpdates = settings.value("lastChecked", None)
settings.endGroup()
settings.beginGroup("PublicServerList")
self.publicServerList = settings.value("publicServers", None)
def __init__(self): def __init__(self):
super(MainWindow, self).__init__() super(MainWindow, self).__init__()
FileSwitchManager = self.FileSwitchManager()
FileSwitchManager.setWindow(self)
self.newWatchlist = []
self.publicServerList = []
self.lastCheckedForUpdates = None
self._syncplayClient = None self._syncplayClient = None
self.folderSearchEnabled = True
self.QtGui = QtGui self.QtGui = QtGui
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
self.resourcespath = utils.findWorkingDir() + "\\resources\\" self.resourcespath = utils.findWorkingDir() + "\\resources\\"

View File

@ -9,6 +9,10 @@ import itertools
import hashlib import hashlib
import random import random
import string import string
import urllib
import ast
folderSearchEnabled = True
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None): def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
"""Retry calling the decorated function using an exponential backoff. """Retry calling the decorated function using an exponential backoff.
@ -158,8 +162,11 @@ def blackholeStdoutForFrozenWindow():
# Relate to file hashing / difference checking: # Relate to file hashing / difference checking:
def stripfilename(filename): def stripfilename(filename, stripURL):
if filename: if filename:
filename = urllib.unquote(filename)
if stripURL:
filename = filename.split(u"/")[-1]
return re.sub(constants.FILENAME_STRIP_REGEX, "", filename) return re.sub(constants.FILENAME_STRIP_REGEX, "", filename)
else: else:
return "" return ""
@ -173,8 +180,8 @@ def stripRoomName(RoomName):
else: else:
return "" return ""
def hashFilename(filename): def hashFilename(filename, stripURL = False):
return hashlib.sha256(stripfilename(filename).encode('utf-8')).hexdigest()[:12] return hashlib.sha256(stripfilename(filename, stripURL).encode('utf-8')).hexdigest()[:12]
def hashFilesize(size): def hashFilesize(size):
return hashlib.sha256(str(size)).hexdigest()[:12] return hashlib.sha256(str(size)).hexdigest()[:12]
@ -190,9 +197,10 @@ def sameHashed(string1raw, string1hashed, string2raw, string2hashed):
return True return True
def sameFilename (filename1, filename2): def sameFilename (filename1, filename2):
stripURL = True if isURL(filename1) ^ isURL(filename2) else False
if filename1 == constants.PRIVACY_HIDDENFILENAME or filename2 == constants.PRIVACY_HIDDENFILENAME: if filename1 == constants.PRIVACY_HIDDENFILENAME or filename2 == constants.PRIVACY_HIDDENFILENAME:
return True return True
elif sameHashed(stripfilename(filename1), hashFilename(filename1), stripfilename(filename2), hashFilename(filename2)): elif sameHashed(stripfilename(filename1, stripURL), hashFilename(filename1, stripURL), stripfilename(filename2, stripURL), hashFilename(filename2, stripURL)):
return True return True
else: else:
return False return False
@ -218,6 +226,47 @@ def meetsMinVersion(version, minVersion):
return tuple(map(int, ver.split("."))) return tuple(map(int, ver.split(".")))
return versiontotuple(version) >= versiontotuple(minVersion) return versiontotuple(version) >= versiontotuple(minVersion)
def isURL(path):
if path is None:
return False
elif "://" in path:
return True
else:
return False
def getPlayerArgumentsByPathAsArray(arguments, path):
if arguments and not isinstance(arguments, (str, unicode)) and arguments.has_key(path):
return arguments[path]
else:
return None
def getPlayerArgumentsByPathAsText(arguments, path):
argsToReturn = getPlayerArgumentsByPathAsArray(arguments, path)
return " ".join(argsToReturn) if argsToReturn else ""
def getListAsMultilineString(pathArray):
return u"\n".join(pathArray) if pathArray else ""
def convertMultilineStringToList(multilineString):
return unicode.split(multilineString,u"\n") if multilineString else ""
def getListOfPublicServers():
try:
import urllib, syncplay, sys, messages, json
params = urllib.urlencode({'version': syncplay.version, 'milestone': syncplay.milestone, 'release_number': syncplay.release_number,
'language': messages.messages["CURRENT"]})
f = urllib.urlopen(constants.SYNCPLAY_PUBLIC_SERVER_LIST_URL.format(params))
response = f.read()
response = response.replace("<p>","").replace("</p>","").replace("<br />","").replace("&#8220;","'").replace("&#8221;","'").replace(":&#8217;","'").replace("&#8217;","'").replace("&#8242;","'").replace("\n","").replace("\r","") # Fix Wordpress
response = ast.literal_eval(response)
if response:
return response
else:
raise IOError
except:
raise IOError(getMessage("failed-to-load-server-list-error"))
class RoomPasswordProvider(object): class RoomPasswordProvider(object):
CONTROLLED_ROOM_REGEX = re.compile("^\+(.*):(\w{12})$") CONTROLLED_ROOM_REGEX = re.compile("^\+(.*):(\w{12})$")
PASSWORD_REGEX = re.compile("[A-Z]{2}-\d{3}-\d{3}") PASSWORD_REGEX = re.compile("[A-Z]{2}-\d{3}-\d{3}")
@ -277,4 +326,5 @@ class RandomStringGenerator(object):
return ''.join(random.choice(string.digits) for _ in xrange(quantity)) return ''.join(random.choice(string.digits) for _ in xrange(quantity))
class NotControlledRoom(Exception): class NotControlledRoom(Exception):
pass pass