Merge remote-tracking branch 'github/master' into develop
This commit is contained in:
commit
5dab97b973
26
.github/workflows/build.yml
vendored
26
.github/workflows/build.yml
vendored
@ -42,7 +42,7 @@ jobs:
|
||||
echo $ver
|
||||
echo "VER=$ver" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
python buildPy2exe.py
|
||||
New-Item -Path syncplay_v$ver -Name "syncplay.ini" -Value $null
|
||||
New-Item -Path syncplay_v$ver -Name "syncplay.ini" -Value " "
|
||||
|
||||
- name: Prepare for deployment
|
||||
run: dir
|
||||
@ -71,7 +71,7 @@ jobs:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
python-version: '3.9'
|
||||
|
||||
- name: Check Python install
|
||||
run: |
|
||||
@ -84,8 +84,20 @@ jobs:
|
||||
run: |
|
||||
pip3 install -U setuptools wheel pip
|
||||
pip3 install twisted[tls] appnope requests certifi
|
||||
pip3 install shiboken2==5.13.1 pyside2==5.13.1
|
||||
pip3 install py2app
|
||||
pip3 install shiboken2==5.15.2 pyside2==5.15.2
|
||||
pip3 install altgraph modulegraph macholib
|
||||
|
||||
- name: Install py2app
|
||||
run: |
|
||||
git clone https://github.com/albertosottile/py2app.git
|
||||
cd py2app
|
||||
git checkout stubs
|
||||
cd py2app/apptemplate
|
||||
python3 setup.py build
|
||||
cd ../..
|
||||
python3 setup.py build
|
||||
python3 setup.py install
|
||||
cd ..
|
||||
|
||||
- name: Check Python dependencies
|
||||
run: |
|
||||
@ -96,13 +108,13 @@ jobs:
|
||||
python3 -c "from py2app.recipes import pyside2"
|
||||
echo $DYLD_LIBRARY_PATH
|
||||
echo $DYLD_FRAMEWORK_PATH
|
||||
python3 -c 'from distutils.sysconfig import get_config_var; print(get_config_var("LDLIBRARY"))'
|
||||
# python3 -c 'from distutils.sysconfig import get_config_var; print(get_config_var("LDLIBRARY"))'
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
python3 ci/pyside2_linker.py
|
||||
export LIBPYTHON_FOLDER="$(python3 -c 'from distutils.sysconfig import get_config_var; print(get_config_var("LIBDIR"))')"
|
||||
ln -s $LIBPYTHON_FOLDER/libpython3.7m.dylib $LIBPYTHON_FOLDER/libpython3.7.dylib
|
||||
# export LIBPYTHON_FOLDER="$(python3 -c 'from distutils.sysconfig import get_config_var; print(get_config_var("LIBDIR"))')"
|
||||
# ln -s $LIBPYTHON_FOLDER/libpython3.9m.dylib $LIBPYTHON_FOLDER/libpython3.9.dylib
|
||||
export DYLD_FRAMEWORK_PATH="$(python3 -c 'from PySide2.QtCore import QLibraryInfo; print(QLibraryInfo.location(QLibraryInfo.LibrariesPath))')"
|
||||
export DYLD_LIBRARY_PATH="$(python3 -c 'import os.path, PySide2; print(os.path.dirname(PySide2.__file__))'):$(python3 -c 'import os.path, shiboken2; print(os.path.dirname(shiboken2.__file__))')"
|
||||
python3 buildPy2app.py py2app
|
||||
|
||||
@ -31,7 +31,6 @@ OPTIONS = {
|
||||
'CFBundleIdentifier': 'pl.syncplay.Syncplay',
|
||||
'LSMinimumSystemVersion': '10.12.0',
|
||||
'NSHumanReadableCopyright': 'Copyright © 2019 Syncplay All Rights Reserved',
|
||||
'NSRequiresAquaSystemAppearance': False,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -66,7 +66,9 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
LoadLanguageFile "$${NSISDIR}\Contrib\Language files\PortugueseBR.nlf"
|
||||
LoadLanguageFile "$${NSISDIR}\Contrib\Language files\Portuguese.nlf"
|
||||
LoadLanguageFile "$${NSISDIR}\Contrib\Language files\Turkish.nlf"
|
||||
|
||||
LoadLanguageFile "$${NSISDIR}\Contrib\Language files\French.nlf"
|
||||
LoadLanguageFile "$${NSISDIR}\Contrib\Language files\SimpChinese.nlf"
|
||||
|
||||
Unicode true
|
||||
|
||||
Name "Syncplay $version"
|
||||
@ -118,6 +120,16 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
VIAddVersionKey /LANG=$${LANG_TURKISH} "FileVersion" "$version.0"
|
||||
VIAddVersionKey /LANG=$${LANG_TURKISH} "LegalCopyright" "Syncplay"
|
||||
VIAddVersionKey /LANG=$${LANG_TURKISH} "FileDescription" "Syncplay"
|
||||
|
||||
VIAddVersionKey /LANG=$${LANG_FRENCH} "ProductName" "Syncplay"
|
||||
VIAddVersionKey /LANG=$${LANG_FRENCH} "FileVersion" "$version.0"
|
||||
VIAddVersionKey /LANG=$${LANG_FRENCH} "LegalCopyright" "Syncplay"
|
||||
VIAddVersionKey /LANG=$${LANG_FRENCH} "FileDescription" "Syncplay"
|
||||
|
||||
VIAddVersionKey /LANG=$${LANG_SIMPCHINESE} "ProductName" "Syncplay"
|
||||
VIAddVersionKey /LANG=$${LANG_SIMPCHINESE} "FileVersion" "$version.0"
|
||||
VIAddVersionKey /LANG=$${LANG_SIMPCHINESE} "LegalCopyright" "Syncplay"
|
||||
VIAddVersionKey /LANG=$${LANG_SIMPCHINESE} "FileDescription" "Syncplay"
|
||||
|
||||
LangString ^SyncplayLanguage $${LANG_ENGLISH} "en"
|
||||
LangString ^Associate $${LANG_ENGLISH} "Associate Syncplay with multimedia files."
|
||||
@ -198,6 +210,24 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
LangString ^QuickLaunchBar $${LANG_TURKISH} "Hızlı Başlatma Çubuğu"
|
||||
LangString ^AutomaticUpdates $${LANG_TURKISH} "Güncellemeleri otomatik denetle"
|
||||
LangString ^UninstConfig $${LANG_TURKISH} "Yapılandırma dosyasını silin."
|
||||
|
||||
LangString ^SyncplayLanguage $${LANG_FRENCH} "fr"
|
||||
LangString ^Associate $${LANG_FRENCH} "Associer Syncplay avec les fichiers multimedias."
|
||||
LangString ^Shortcut $${LANG_FRENCH} "Créer Racourcis pour les chemins suivants:"
|
||||
LangString ^StartMenu $${LANG_FRENCH} "Menu Démarrer"
|
||||
LangString ^Desktop $${LANG_FRENCH} "Bureau"
|
||||
LangString ^QuickLaunchBar $${LANG_FRENCH} "Barre de Lancement Rapide"
|
||||
LangString ^AutomaticUpdates $${LANG_FRENCH} "Vérifier automatiquement les mises à jour"
|
||||
LangString ^UninstConfig $${LANG_FRENCH} "Supprimer le fichier de configuration."
|
||||
|
||||
LangString ^SyncplayLanguage $${LANG_SIMPCHINESE} "zh_CN"
|
||||
LangString ^Associate $${LANG_SIMPCHINESE} "将Syncplay与多媒体文件关联。"
|
||||
LangString ^Shortcut $${LANG_SIMPCHINESE} "在以下位置创建快捷方式:"
|
||||
LangString ^StartMenu $${LANG_SIMPCHINESE} "开始菜单"
|
||||
LangString ^Desktop $${LANG_SIMPCHINESE} "桌面"
|
||||
LangString ^QuickLaunchBar $${LANG_SIMPCHINESE} "快速启动栏"
|
||||
LangString ^AutomaticUpdates $${LANG_SIMPCHINESE} "自动检查更新"
|
||||
LangString ^UninstConfig $${LANG_SIMPCHINESE} "删除配置文件"
|
||||
|
||||
; Remove text to save space
|
||||
LangString ^ClickInstall $${LANG_GERMAN} " "
|
||||
@ -286,7 +316,16 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
|
||||
Call GetSize
|
||||
Call DriveSpace
|
||||
Call Language
|
||||
|
||||
$${GetParameters} $$0
|
||||
ClearErrors
|
||||
$${GetOptions} $$0 "/LANG=" $$0
|
||||
$${IfNot} $${Errors}
|
||||
$${AndIf} $$0 <> 0
|
||||
StrCpy $$LANGUAGE $$0
|
||||
$${Else}
|
||||
Call Language
|
||||
$${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
;Language selection dialog
|
||||
@ -310,6 +349,8 @@ NSIS_SCRIPT_TEMPLATE = r"""
|
||||
Push 'Português de Portugal'
|
||||
Push $${LANG_TURKISH}
|
||||
Push 'Türkçe'
|
||||
Push $${LANG_SIMPCHINESE}
|
||||
Push '简体中文'
|
||||
Push A ; A means auto count languages
|
||||
LangDLL::LangDialog "Language Selection" "Please select the language of Syncplay and the installer"
|
||||
Pop $$LANGUAGE
|
||||
|
||||
@ -17,4 +17,4 @@ mkdir dist/Syncplay.app/Contents/Resources/es_419.lproj
|
||||
pip3 install dmgbuild
|
||||
mv syncplay/resources/macOS_readme.pdf syncplay/resources/.macOS_readme.pdf
|
||||
|
||||
dmgbuild -s appdmg.py "Syncplay" dist_actions/Syncplay_${VER}.dmg
|
||||
python3 -m dmgbuild -s appdmg.py "Syncplay" dist_actions/Syncplay_${VER}.dmg
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import zipfile
|
||||
|
||||
from glob import glob
|
||||
|
||||
pyver = platform.python_version_tuple()[0] + platform.python_version_tuple()[1]
|
||||
|
||||
# clean Python library zip archive
|
||||
|
||||
PATH = 'dist/Syncplay.app/Contents/Resources/lib'
|
||||
|
||||
zin = zipfile.ZipFile(f'{PATH}/python37.zip', 'r')
|
||||
zin = zipfile.ZipFile(f'{PATH}/python{pyver}.zip', 'r')
|
||||
tbd = [path for path in zin.namelist() if 'PySide2/Qt/' in path]
|
||||
|
||||
zout = zipfile.ZipFile(f'{PATH}/python37_new.zip', 'w', zipfile.ZIP_DEFLATED)
|
||||
zout = zipfile.ZipFile(f'{PATH}/python{pyver}_new.zip', 'w', zipfile.ZIP_DEFLATED)
|
||||
|
||||
for item in zin.namelist():
|
||||
buffer = zin.read(item)
|
||||
@ -16,5 +24,50 @@ for item in zin.namelist():
|
||||
zout.close()
|
||||
zin.close()
|
||||
|
||||
os.remove(f'{PATH}/python37.zip')
|
||||
os.rename(f'{PATH}/python37_new.zip', f'{PATH}/python37.zip')
|
||||
os.remove(f'{PATH}/python{pyver}.zip')
|
||||
os.rename(f'{PATH}/python{pyver}_new.zip', f'{PATH}/python{pyver}.zip')
|
||||
|
||||
# clean Frameworks folder
|
||||
|
||||
PATH = 'dist/Syncplay.app/Contents/Frameworks'
|
||||
|
||||
to_be_kept = ['QtCore', 'QtDBus', 'QtGui', 'QtNetwork', 'QtPrintSupport', 'QtQml', 'QtWidgets']
|
||||
to_be_deleted = []
|
||||
|
||||
for f in glob(f'{PATH}/Qt*'):
|
||||
if not any({k in f for k in to_be_kept}):
|
||||
to_be_deleted.append(f)
|
||||
|
||||
for p in to_be_deleted:
|
||||
if os.path.isdir(p):
|
||||
shutil.rmtree(p, ignore_errors=True)
|
||||
else:
|
||||
os.remove(p)
|
||||
|
||||
# Clean PySide2 folder
|
||||
|
||||
PATH = 'dist/Syncplay.app/Contents/Resources/lib/python3.9/PySide2'
|
||||
|
||||
shutil.rmtree(f'{PATH}/examples', ignore_errors=True)
|
||||
|
||||
to_be_kept = ['QtCore', 'QtDBus', 'QtGui', 'QtNetwork', 'QtPrintSupport', 'QtQml', 'QtWidgets']
|
||||
to_be_deleted = []
|
||||
|
||||
for f in glob(f'{PATH}/Qt*'):
|
||||
if not any({k in f for k in to_be_kept}):
|
||||
to_be_deleted.append(f)
|
||||
|
||||
for p in to_be_deleted:
|
||||
if os.path.isdir(p):
|
||||
shutil.rmtree(p, ignore_errors=True)
|
||||
else:
|
||||
os.remove(p)
|
||||
|
||||
# move .so from Framework to PySide2 folder
|
||||
|
||||
FROM = 'dist/Syncplay.app/Contents/Frameworks'
|
||||
TO = 'dist/Syncplay.app/Contents/Resources/lib/python3.9/PySide2'
|
||||
|
||||
for f in glob(f'{FROM}/Qt*.so'):
|
||||
fn = os.path.basename(f)
|
||||
shutil.move(f, f'{TO}/{fn}')
|
||||
|
||||
@ -77,6 +77,15 @@ Random string used to generate managed room passwords.
|
||||
.B \-\-motd\-file [file]
|
||||
Path to a file from which motd (Message Of The Day) will be read.
|
||||
|
||||
.TP
|
||||
.B \-\-rooms\-db-file [file]
|
||||
Enables room persistence. Path is to where a database file should be loaded/create where room data will be written to and read from. This will enable rooms to persist without watchers and through restarts. Will not work if using \fB\-\-isolate\-rooms\fP.
|
||||
|
||||
.TP
|
||||
.B \-\-permanent\-rooms-file [file]
|
||||
Specifies a list of rooms that will still be listed even if their playlist is empty. Path is to where a text file with one room per line. This will require persistent rooms to be enabled.
|
||||
|
||||
|
||||
.TP
|
||||
.B \-\-max\-chat\-message\-length [maxChatMessageLength]
|
||||
Maximum number of characters in one chat message (default is 150).
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
version = '1.7.0'
|
||||
revision = ' development'
|
||||
milestone = 'Yoitsu'
|
||||
release_number = '100'
|
||||
release_number = '101'
|
||||
projectURL = 'https://syncplay.pl/'
|
||||
|
||||
@ -9,9 +9,9 @@ import re
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from fnmatch import fnmatch
|
||||
from copy import deepcopy
|
||||
from functools import wraps
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from twisted.application.internet import ClientService
|
||||
from twisted.internet.endpoints import HostnameEndpoint
|
||||
@ -546,30 +546,51 @@ class SyncplayClient(object):
|
||||
if oldRoomList != newRoomList:
|
||||
self._config['roomList'] = newRoomList
|
||||
|
||||
def _isURITrustableAndTrusted(self, URIToTest):
|
||||
"""Returns a tuple of booleans: (trustable, trusted).
|
||||
|
||||
A given URI is "trustable" if it uses HTTP or HTTPS (constants.TRUSTABLE_WEB_PROTOCOLS).
|
||||
A given URI is "trusted" if it matches an entry in the trustedDomains config.
|
||||
Such an entry is considered matching if the domain is the same and the path
|
||||
is a prefix of the given URI's path.
|
||||
A "trustable" URI is always "trusted" if the config onlySwitchToTrustedDomains is false.
|
||||
"""
|
||||
o = urlparse(URIToTest)
|
||||
trustable = o.scheme in constants.TRUSTABLE_WEB_PROTOCOLS
|
||||
if not trustable:
|
||||
# untrustable URIs are never trusted, return early
|
||||
return False, False
|
||||
if not self._config['onlySwitchToTrustedDomains']:
|
||||
# trust all trustable URIs in this case
|
||||
return trustable, True
|
||||
# check for matching trusted domains
|
||||
if self._config['trustedDomains']:
|
||||
for entry in self._config['trustedDomains']:
|
||||
trustedDomain, _, path = entry.partition('/')
|
||||
if o.hostname not in (trustedDomain, "www." + trustedDomain):
|
||||
# domain does not match
|
||||
continue
|
||||
if path and not o.path.startswith('/' + path):
|
||||
# trusted domain has a path component and it does not match
|
||||
continue
|
||||
# match found, trust this domain
|
||||
return trustable, True
|
||||
# no matches found, do not trust this domain
|
||||
return trustable, False
|
||||
|
||||
def isUntrustedTrustableURI(self, URIToTest):
|
||||
if utils.isURL(URIToTest):
|
||||
for trustedProtocol in constants.TRUSTABLE_WEB_PROTOCOLS:
|
||||
if URIToTest.startswith(trustedProtocol) and not self.isURITrusted(URIToTest):
|
||||
return True
|
||||
trustable, trusted = self._isURITrustableAndTrusted(URIToTest)
|
||||
return trustable and not trusted
|
||||
return False
|
||||
|
||||
def isURITrusted(self, URIToTest):
|
||||
URIToTest = URIToTest+"/"
|
||||
for trustedProtocol in constants.TRUSTABLE_WEB_PROTOCOLS:
|
||||
if URIToTest.startswith(trustedProtocol):
|
||||
if self._config['onlySwitchToTrustedDomains']:
|
||||
if self._config['trustedDomains']:
|
||||
for trustedDomain in self._config['trustedDomains']:
|
||||
trustableURI = ''.join([trustedProtocol, trustedDomain, "/*"])
|
||||
if fnmatch(URIToTest, trustableURI):
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
trustable, trusted = self._isURITrustableAndTrusted(URIToTest)
|
||||
return trustable and trusted
|
||||
|
||||
def openFile(self, filePath, resetPosition=False, fromUser=False):
|
||||
if fromUser and filePath.endswith(".txt") or filePath.endswith(".m3u") or filePath.endswith(".m3u8"):
|
||||
if not (filePath.startswith("http://") or filePath.startswith("https://"))\
|
||||
and ((fromUser and filePath.endswith(".txt")) or filePath.endswith(".m3u") or filePath.endswith(".m3u8")):
|
||||
self.playlist.loadPlaylistFromFile(filePath, resetPosition)
|
||||
return
|
||||
|
||||
@ -619,6 +640,7 @@ class SyncplayClient(object):
|
||||
"chat": utils.meetsMinVersion(self.serverVersion, constants.CHAT_MIN_VERSION),
|
||||
"readiness": utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION),
|
||||
"managedRooms": utils.meetsMinVersion(self.serverVersion, constants.CONTROLLED_ROOMS_MIN_VERSION),
|
||||
"persistentRooms": False,
|
||||
"maxChatMessageLength": constants.FALLBACK_MAX_CHAT_MESSAGE_LENGTH,
|
||||
"maxUsernameLength": constants.FALLBACK_MAX_USERNAME_LENGTH,
|
||||
"maxRoomNameLength": constants.FALLBACK_MAX_ROOM_NAME_LENGTH,
|
||||
@ -685,11 +707,13 @@ class SyncplayClient(object):
|
||||
# Can change during runtime:
|
||||
features["sharedPlaylists"] = self.sharedPlaylistIsEnabled() # Can change during runtime
|
||||
features["chat"] = self.chatIsEnabled() # Can change during runtime
|
||||
features["uiMode"] = self.ui.getUIMode()
|
||||
|
||||
# Static for this version/release of Syncplay:
|
||||
features["featureList"] = True
|
||||
features["readiness"] = True
|
||||
features["managedRooms"] = True
|
||||
features["persistentRooms"] = True
|
||||
|
||||
return features
|
||||
|
||||
@ -1572,6 +1596,9 @@ class UiManager(object):
|
||||
self.lastAlertOSDEndTime = None
|
||||
self.lastError = ""
|
||||
|
||||
def getUIMode(self):
|
||||
return self.__ui.uiMode
|
||||
|
||||
def addFileToPlaylist(self, newPlaylistItem):
|
||||
self.__ui.addFileToPlaylist(newPlaylistItem)
|
||||
|
||||
@ -1679,6 +1706,7 @@ class SyncplayPlaylist():
|
||||
self._playlist = []
|
||||
self._playlistIndex = None
|
||||
self.addedChangeListCallback = False
|
||||
self.switchToNewPlaylistItem = False
|
||||
self._lastPlaylistIndexChange = time.time()
|
||||
|
||||
def needsSharedPlaylistsEnabled(f): # @NoSelf
|
||||
@ -1843,6 +1871,10 @@ class SyncplayPlaylist():
|
||||
self._ui.showDebugMessage("Could not change playlist index due to IndexError")
|
||||
|
||||
def _getValidIndexFromNewPlaylist(self, newPlaylist=None):
|
||||
if self.switchToNewPlaylistItem:
|
||||
self.switchToNewPlaylistItem = False
|
||||
return len(self._playlist)
|
||||
|
||||
if self._playlistIndex is None or not newPlaylist or len(newPlaylist) <= 1:
|
||||
return 0
|
||||
|
||||
|
||||
@ -125,6 +125,7 @@ COMMANDS_CREATE = ['c', 'create']
|
||||
COMMANDS_AUTH = ['a', 'auth']
|
||||
COMMANDS_TOGGLE = ['t', 'toggle']
|
||||
COMMANDS_QUEUE = ['queue', 'qa', 'add']
|
||||
COMMANDS_QUEUEANDSELECT = ['queueandselect','qas']
|
||||
COMMANDS_PLAYLIST = ['playlist', 'ql', 'pl']
|
||||
COMMANDS_SELECT = ['select', 'qs']
|
||||
COMMANDS_DELETE = ['delete', 'd', 'qd']
|
||||
@ -172,8 +173,12 @@ MPLAYER_PATHS = ["mplayer2", "mplayer"]
|
||||
MPV_PATHS = ["mpv", "/opt/mpv/mpv", r"c:\program files\mpv\mpv.exe", r"c:\program files\mpv-player\mpv.exe",
|
||||
r"c:\program Files (x86)\mpv\mpv.exe", r"c:\program Files (x86)\mpv-player\mpv.exe",
|
||||
"/Applications/mpv.app/Contents/MacOS/mpv"]
|
||||
MPVNET_PATHS = [r"c:\program files\mpv.net\mpvnet.exe", r"c:\program files\mpv.net\mpvnet.exe",
|
||||
r"c:\program Files (x86)\mpv.net\mpvnet.exe", r"c:\program Files (x86)\mpv.net\mpvnet.exe"]
|
||||
MPVNET_PATHS = [r"c:\program files\mpv.net\mpvnet.exe", r"c:\program Files (x86)\mpv.net\mpvnet.exe"]
|
||||
try:
|
||||
import os
|
||||
MPVNET_PATHS.append(os.path.expandvars(r'%LOCALAPPDATA%\Microsoft\WindowsApps\mpvnet.exe'))
|
||||
except:
|
||||
pass
|
||||
VLC_PATHS = [
|
||||
r"c:\program files (x86)\videolan\vlc\vlc.exe",
|
||||
r"c:\program files\videolan\vlc\vlc.exe",
|
||||
@ -345,6 +350,11 @@ SYNCPLAY_DOWNLOAD_URL = "https://syncplay.pl/download/"
|
||||
SYNCPLAY_PUBLIC_SERVER_LIST_URL = "https://syncplay.pl/listpublicservers?{}" # Params
|
||||
|
||||
DEFAULT_TRUSTED_DOMAINS = ["youtube.com", "youtu.be"]
|
||||
TRUSTABLE_WEB_PROTOCOLS = ["http://www.", "https://www.", "http://", "https://"]
|
||||
TRUSTABLE_WEB_PROTOCOLS = ["http", "https"]
|
||||
|
||||
PRIVATE_FILE_FIELDS = ["path"]
|
||||
|
||||
CONSOLE_UI_MODE = "CLI"
|
||||
GRAPHICAL_UI_MODE = "GUI"
|
||||
UNKNOWN_UI_MODE = "Unknown"
|
||||
FALLBACK_ASSUMED_UI_MODE = GRAPHICAL_UI_MODE
|
||||
|
||||
@ -38,6 +38,8 @@ def main():
|
||||
args.port,
|
||||
args.password,
|
||||
args.motd_file,
|
||||
args.rooms_db_file,
|
||||
args.permanent_rooms_file,
|
||||
args.isolate_rooms,
|
||||
args.salt,
|
||||
args.disable_ready,
|
||||
|
||||
@ -9,17 +9,21 @@ from . import messages_es
|
||||
from . import messages_pt_BR
|
||||
from . import messages_pt_PT
|
||||
from . import messages_tr
|
||||
from . import messages_fr
|
||||
from . import messages_zh_CN
|
||||
import re
|
||||
|
||||
messages = {
|
||||
"de": messages_de.de,
|
||||
"en": messages_en.en,
|
||||
"es": messages_es.es,
|
||||
"fr": messages_fr.fr,
|
||||
"it": messages_it.it,
|
||||
"pt_PT": messages_pt_PT.pt_PT,
|
||||
"pt_BR": messages_pt_BR.pt_BR,
|
||||
"tr": messages_tr.tr,
|
||||
"ru": messages_ru.ru,
|
||||
"zh_CN": messages_zh_CN.zh_CN,
|
||||
"CURRENT": None
|
||||
}
|
||||
|
||||
@ -64,7 +68,7 @@ def getInitialLanguage():
|
||||
try:
|
||||
import sys
|
||||
frozen = getattr(sys, 'frozen', '')
|
||||
if frozen in 'macosx_app':
|
||||
if frozen and frozen in 'macosx_app':
|
||||
from PySide2.QtCore import QLocale
|
||||
initialLanguage = QLocale.system().uiLanguages()[0].split('-')[0]
|
||||
else:
|
||||
|
||||
@ -82,12 +82,14 @@ de = {
|
||||
"commandlist-notification/undo": "\tu - Letzter Zeitsprung rückgängig",
|
||||
"commandlist-notification/pause": "\tp - Pausieren / weiter",
|
||||
"commandlist-notification/seek": "\t[s][+-]Zeit - zu einer bestimmten Zeit spulen, ohne + oder - wird als absolute Zeit gewertet; Angabe in Sekunden oder Minuten:Sekunden",
|
||||
"commandlist-notification/offset": "\to[+-]duration - offset local playback by the given duration (in seconds or min:sec) from the server seek position - this is a deprecated feature",
|
||||
"commandlist-notification/help": "\th - Diese Hilfe",
|
||||
"commandlist-notification/toggle": "\tt - Bereitschaftsanzeige umschalten",
|
||||
"commandlist-notification/create": "\tc [name] - erstelle zentral gesteuerten Raum mit dem aktuellen Raumnamen",
|
||||
"commandlist-notification/auth": "\ta [password] - authentifiziere als Raumleiter mit Passwort",
|
||||
"commandlist-notification/chat": "\tch [message] - Chatnachricht an einem Raum senden",
|
||||
"commandList-notification/queue": "\tqa [file/url] - add file or url to bottom of playlist", # TO DO: Translate
|
||||
"commandList-notification/queueandselect": "\tqas [file/url] - add file or url to bottom of playlist and select it", # TO DO: Translate
|
||||
"commandList-notification/playlist": "\tql - show the current playlist", # TO DO: Translate
|
||||
"commandList-notification/select": "\tqs [index] - select given entry in the playlist", # TO DO: Translate
|
||||
"commandList-notification/delete": "\tqd [index] - delete the given entry from the playlist", # TO DO: Translate
|
||||
@ -174,7 +176,7 @@ de = {
|
||||
"file-argument": 'Abzuspielende Datei',
|
||||
"args-argument": 'Player-Einstellungen; Wenn du Einstellungen, die mit - beginnen, nutzen willst, stelle ein einzelnes \'--\'-Argument davor',
|
||||
"clear-gui-data-argument": 'Setzt die Pfad- und GUI-Fenster-Daten die in den QSettings gespeichert sind zurück',
|
||||
"language-argument": 'Sprache für Syncplay-Nachrichten (de/en/ru/it/es/pt_BR/pt_PT/tr)',
|
||||
"language-argument": 'Sprache für Syncplay-Nachrichten (de/en/ru/it/es/pt_BR/pt_PT/tr/fr/zh_CN)',
|
||||
|
||||
"version-argument": 'gibt die aktuelle Version aus',
|
||||
"version-message": "Du verwendest Syncplay v. {} ({})",
|
||||
@ -286,6 +288,7 @@ de = {
|
||||
"autoplay-menu-label": "Auto-Play-Knopf anzeigen",
|
||||
"autoplay-guipushbuttonlabel": "Automatisch abspielen wenn alle bereit sind",
|
||||
"autoplay-minimum-label": "Minimum an Nutzern:",
|
||||
"hideemptyrooms-menu-label": "Hide empty persistent rooms", # TODO: Translate
|
||||
|
||||
"sendmessage-label": "Senden",
|
||||
|
||||
@ -456,6 +459,7 @@ de = {
|
||||
|
||||
# Server messages to client
|
||||
"new-syncplay-available-motd-message": "Du nutzt Syncplay Version {}, aber es gibt eine neuere Version auf https://syncplay.pl", # ClientVersion
|
||||
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # TO DO: Translate - NOTE: Do not translate the word -temp
|
||||
|
||||
# Server notifications
|
||||
"welcome-server-notification": "Willkommen zum Syncplay-Server, v. {0}", # version
|
||||
@ -472,6 +476,8 @@ de = {
|
||||
"server-salt-argument": "zufällige Zeichenkette, die zur Erstellung von Passwörtern verwendet wird",
|
||||
"server-disable-ready-argument": "Bereitschaftsfeature deaktivieren",
|
||||
"server-motd-argument": "Pfad zur Datei, von der die Nachricht des Tages geladen wird",
|
||||
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
||||
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled", # TODO: Translate
|
||||
"server-chat-argument": "Soll Chat deaktiviert werden?",
|
||||
"server-chat-maxchars-argument": "Maximale Zeichenzahl in einer Chatnachricht (Standard ist {})",
|
||||
"server-maxusernamelength-argument": "Maximale Zeichenzahl in einem Benutzernamen (Standard ist {})",
|
||||
|
||||
14
syncplay/messages_en.py
Executable file → Normal file
14
syncplay/messages_en.py
Executable file → Normal file
@ -35,7 +35,7 @@ en = {
|
||||
"left-notification": "{} has left", # User
|
||||
"left-paused-notification": "{} left, {} paused", # User who left, User who paused
|
||||
"playing-notification": "{} is playing '{}' ({})", # User, file, duration
|
||||
"playing-notification/room-addendum": " in room: '{}'", # Room
|
||||
"playing-notification/room-addendum": " in room: '{}'", # Room
|
||||
|
||||
"not-all-ready": "Not ready: {}", # Usernames
|
||||
"all-users-ready": "Everyone is ready ({} users)", # Number of ready users
|
||||
@ -82,12 +82,14 @@ en = {
|
||||
"commandlist-notification/undo": "\tu - undo last seek",
|
||||
"commandlist-notification/pause": "\tp - toggle pause",
|
||||
"commandlist-notification/seek": "\t[s][+-]time - seek to the given value of time, if + or - is not specified it's absolute time in seconds or min:sec",
|
||||
"commandlist-notification/offset": "\to[+-]duration - offset local playback by the given duration (in seconds or min:sec) from the server seek position - this is a deprecated feature",
|
||||
"commandlist-notification/help": "\th - this help",
|
||||
"commandlist-notification/toggle": "\tt - toggles whether you are ready to watch or not",
|
||||
"commandlist-notification/create": "\tc [name] - create managed room using name of current room",
|
||||
"commandlist-notification/auth": "\ta [password] - authenticate as room operator with operator password",
|
||||
"commandlist-notification/chat": "\tch [message] - send a chat message in a room",
|
||||
"commandList-notification/queue": "\tqa [file/url] - add file or url to bottom of playlist",
|
||||
"commandList-notification/queueandselect": "\tqas [file/url] - add file or url to bottom of playlist and select it",
|
||||
"commandList-notification/playlist": "\tql - show the current playlist",
|
||||
"commandList-notification/select": "\tqs [index] - select given entry in the playlist",
|
||||
"commandList-notification/delete": "\tqd [index] - delete the given entry from the playlist",
|
||||
@ -163,18 +165,18 @@ en = {
|
||||
"argument-description": 'Solution to synchronize playback of multiple media player instances over the network.',
|
||||
"argument-epilog": 'If no options supplied _config values will be used',
|
||||
"nogui-argument": 'show no GUI',
|
||||
"host-argument": 'server\'s address',
|
||||
"host-argument": "server's address",
|
||||
"name-argument": 'desired username',
|
||||
"debug-argument": 'debug mode',
|
||||
"force-gui-prompt-argument": 'make configuration prompt appear',
|
||||
"no-store-argument": 'don\'t store values in .syncplay',
|
||||
"no-store-argument": "don't store values in .syncplay",
|
||||
"room-argument": 'default room',
|
||||
"password-argument": 'server password',
|
||||
"player-path-argument": 'path to your player executable',
|
||||
"file-argument": 'file to play',
|
||||
"args-argument": 'player options, if you need to pass options starting with - prepend them with single \'--\' argument',
|
||||
"clear-gui-data-argument": 'resets path and window state GUI data stored as QSettings',
|
||||
"language-argument": 'language for Syncplay messages (de/en/ru/it/es/pt_BR/pt_PT/tr)',
|
||||
"language-argument": 'language for Syncplay messages (de/en/ru/it/es/pt_BR/pt_PT/tr/fr/zh_CN)',
|
||||
|
||||
"version-argument": 'prints your version',
|
||||
"version-message": "You're using Syncplay version {} ({})",
|
||||
@ -290,6 +292,7 @@ en = {
|
||||
"autoplay-menu-label": "Show auto-play button",
|
||||
"autoplay-guipushbuttonlabel": "Play when all ready",
|
||||
"autoplay-minimum-label": "Min users:",
|
||||
"hideemptyrooms-menu-label": "Hide empty persistent rooms",
|
||||
|
||||
"sendmessage-label": "Send",
|
||||
|
||||
@ -456,6 +459,7 @@ en = {
|
||||
|
||||
# Server messages to client
|
||||
"new-syncplay-available-motd-message": "You are using Syncplay {} but a newer version is available from https://syncplay.pl", # ClientVersion
|
||||
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # NOTE: Do not translate the word -temp
|
||||
|
||||
# Server notifications
|
||||
"welcome-server-notification": "Welcome to Syncplay server, ver. {0}", # version
|
||||
@ -473,6 +477,8 @@ en = {
|
||||
"server-salt-argument": "random string used to generate managed room passwords",
|
||||
"server-disable-ready-argument": "disable readiness feature",
|
||||
"server-motd-argument": "path to file from which motd will be fetched",
|
||||
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts",
|
||||
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled",
|
||||
"server-chat-argument": "Should chat be disabled?",
|
||||
"server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # Default number of characters
|
||||
"server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})",
|
||||
|
||||
@ -82,12 +82,14 @@ es = {
|
||||
"commandlist-notification/undo": "\tu - deshacer última búsqueda",
|
||||
"commandlist-notification/pause": "\tp - activar pausa",
|
||||
"commandlist-notification/seek": "\t[s][+-]tiempo - ir al tiempo definido, si no se especifica + o -, será el tiempo absoluto en segundos o min:sec",
|
||||
"commandlist-notification/offset": "\to[+-]duration - offset local playback by the given duration (in seconds or min:sec) from the server seek position - this is a deprecated feature", # TODO: Translate
|
||||
"commandlist-notification/help": "\th - esta ayuda",
|
||||
"commandlist-notification/toggle": "\tt - activa/inactiva señal que estás listo para ver",
|
||||
"commandlist-notification/create": "\tc [nombre] - crear sala administrada usando el nombre de la sala actual",
|
||||
"commandlist-notification/auth": "\ta [contraseña] - autentificar como operador de la sala con la contraseña de operador",
|
||||
"commandlist-notification/chat": "\tch [mensaje] - enviar un mensaje en la sala",
|
||||
"commandList-notification/queue": "\tqa [file/url] - add file or url to bottom of playlist", # TO DO: Translate
|
||||
"commandList-notification/queueandselect": "\tqas [file/url] - add file or url to bottom of playlist and select it", # TO DO: Translate
|
||||
"commandList-notification/playlist": "\tql - show the current playlist", # TO DO: Translate
|
||||
"commandList-notification/select": "\tqs [index] - select given entry in the playlist", # TO DO: Translate
|
||||
"commandList-notification/delete": "\tqd [index] - delete the given entry from the playlist", # TO DO: Translate
|
||||
@ -174,7 +176,7 @@ es = {
|
||||
"file-argument": 'archivo a reproducir',
|
||||
"args-argument": 'opciones del reproductor, si necesitas pasar opciones que empiezan con -, pásalas utilizando \'--\'',
|
||||
"clear-gui-data-argument": 'restablece ruta y los datos del estado de la ventana GUI almacenados como QSettings',
|
||||
"language-argument": 'lenguaje para los mensajes de Syncplay (de/en/ru/it/es/pt_BR/pt_PT/tr)',
|
||||
"language-argument": 'lenguaje para los mensajes de Syncplay (de/en/ru/it/es/pt_BR/pt_PT/tr/fr/zh_CN)',
|
||||
|
||||
"version-argument": 'imprime tu versión',
|
||||
"version-message": "Estás usando la versión de Syncplay {} ({})",
|
||||
@ -289,6 +291,7 @@ es = {
|
||||
"autoplay-menu-label": "Mostrar botón de auto-reproducción",
|
||||
"autoplay-guipushbuttonlabel": "Reproducir cuando todos estén listos",
|
||||
"autoplay-minimum-label": "Mín. de usuarios:",
|
||||
"hideemptyrooms-menu-label": "Hide empty persistent rooms", # TODO: Translate
|
||||
|
||||
"sendmessage-label": "Enviar",
|
||||
|
||||
@ -455,6 +458,7 @@ es = {
|
||||
|
||||
# Server messages to client
|
||||
"new-syncplay-available-motd-message": "Estás usando Syncplay {} pero hay una versión más nueva disponible en https://syncplay.pl", # ClientVersion
|
||||
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # TO DO: Translate - NOTE: Do not translate the word -temp
|
||||
|
||||
# Server notifications
|
||||
"welcome-server-notification": "Bienvenido al servidor de Syncplay, ver. {0}", # version
|
||||
@ -472,6 +476,8 @@ es = {
|
||||
"server-salt-argument": "cadena aleatoria utilizada para generar contraseñas de salas administradas",
|
||||
"server-disable-ready-argument": "deshabilitar la función de preparación",
|
||||
"server-motd-argument": "ruta al archivo del cual se obtendrá el texto motd",
|
||||
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
||||
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled", # TODO: Translate
|
||||
"server-chat-argument": "¿Debería deshabilitarse el chat?",
|
||||
"server-chat-maxchars-argument": "Número máximo de caracteres en un mensaje de chat (el valor predeterminado es {})", # Default number of characters
|
||||
"server-maxusernamelength-argument": "Número máximo de caracteres para el nombre de usuario (el valor predeterminado es {})",
|
||||
|
||||
527
syncplay/messages_fr.py
Normal file
527
syncplay/messages_fr.py
Normal file
@ -0,0 +1,527 @@
|
||||
# coding:utf8
|
||||
|
||||
"""French dictionary"""
|
||||
|
||||
fr = {
|
||||
"LANGUAGE": "Français",
|
||||
|
||||
# Client notifications
|
||||
"config-cleared-notification": "Paramètres effacés. Les modifications seront enregistrées lorsque vous enregistrez une configuration valide.",
|
||||
|
||||
"relative-config-notification": "Fichiers de configuration relatifs chargés: {}",
|
||||
|
||||
"connection-attempt-notification": "Tentative de connexion à {}:{}", # Port, IP
|
||||
"reconnection-attempt-notification": "Connexion avec le serveur perdue, tentative de reconnexion",
|
||||
"disconnection-notification": "Déconnecté du serveur",
|
||||
"connection-failed-notification": "Échec de la connexion avec le serveur",
|
||||
"connected-successful-notification": "Connexion réussie au serveur",
|
||||
"retrying-notification": "%s, nouvelle tentative dans %d secondes...", # Seconds
|
||||
"reachout-successful-notification": "Vous avez atteint {} ({})",
|
||||
|
||||
"rewind-notification": "Retour en arrière en raison du décalage de temps avec {}", # User
|
||||
"fastforward-notification": "Avance rapide en raison du décalage de temps avec {}", # User
|
||||
"slowdown-notification": "Ralentissement dû au décalage de temps avec {}", # User
|
||||
"revert-notification": "Retour à la vitesse normale",
|
||||
|
||||
"pause-notification": "{} en pause", # User
|
||||
"unpause-notification": "{} non suspendu", # User
|
||||
"seek-notification": "{} est passé de {} à {}", # User, from time, to time
|
||||
|
||||
"current-offset-notification": "Décalage actuel: {}secondes", # Offset
|
||||
|
||||
"media-directory-list-updated-notification": "Les répertoires multimédias Syncplay ont été mis à jour.",
|
||||
|
||||
"room-join-notification": "{} a rejoint la salle: '{}'", # User
|
||||
"left-notification": "{} est parti", # User
|
||||
"left-paused-notification": "{} restants, {} en pause", # User who left, User who paused
|
||||
"playing-notification": "{} est en train de jouer '{}' ({})", # User, file, duration
|
||||
"playing-notification/room-addendum": "dans le salon: '{}'", # Room
|
||||
|
||||
"not-all-ready": "Pas prêt: {}", # Usernames
|
||||
"all-users-ready": "Tout le monde est prêt ({} utilisateurs)", # Number of ready users
|
||||
"ready-to-unpause-notification": "Vous êtes maintenant défini comme prêt - réactivez la pause pour réactiver",
|
||||
"set-as-ready-notification": "Vous êtes maintenant défini comme prêt",
|
||||
"set-as-not-ready-notification": "Vous êtes maintenant défini comme non prêt",
|
||||
"autoplaying-notification": "Lecture automatique dans {}...", # Number of seconds until playback will start
|
||||
|
||||
"identifying-as-controller-notification": "Identification en tant qu'opérateur de salle avec le mot de passe '{}'...",
|
||||
"failed-to-identify-as-controller-notification": "{} n'a pas réussi à s'identifier en tant qu'opérateur de salle.",
|
||||
"authenticated-as-controller-notification": "{} authentifié en tant qu'opérateur de salle",
|
||||
"created-controlled-room-notification": "Salle gérée créée «{}» avec le mot de passe «{}». Veuillez conserver ces informations pour référence future !\n\nDans les salons gérés, tout le monde est synchronisé avec le ou les opérateurs de salon qui sont les seuls à pouvoir mettre en pause, reprendre, se déplacer dans la lecture et modifier la liste de lecture.\n\nVous devez demander aux spectateurs réguliers de rejoindre le salon '{}' mais les opérateurs de salon peuvent rejoindre le salon '{}' pour s'authentifier automatiquement.", # RoomName, operatorPassword, roomName, roomName:operatorPassword
|
||||
|
||||
"file-different-notification": "Le fichier que vous lisez semble être différent de celui de {}", # User
|
||||
"file-differences-notification": "Votre fichier diffère de la (des) manière(s) suivante(s): {}", # Differences
|
||||
"room-file-differences": "Différences de fichiers: {}", # File differences (filename, size, and/or duration)
|
||||
"file-difference-filename": "Nom",
|
||||
"file-difference-filesize": "Taille",
|
||||
"file-difference-duration": "durée",
|
||||
"alone-in-the-room": "Vous êtes seul dans le salon",
|
||||
|
||||
"different-filesize-notification": "(leur taille de fichier est différente de la vôtre!)",
|
||||
"userlist-playing-notification": "{} est en train de jouer:", # Username
|
||||
"file-played-by-notification": "Fichier: {} est lu par:", # File
|
||||
"no-file-played-notification": "{} ne lit pas de fichier", # Username
|
||||
"notplaying-notification": "Les personnes qui ne lisent aucun fichier:",
|
||||
"userlist-room-notification": "Dans la chambre '{}':", # Room
|
||||
"userlist-file-notification": "Fichier",
|
||||
"controller-userlist-userflag": "Opérateur",
|
||||
"ready-userlist-userflag": "Prêt",
|
||||
|
||||
"update-check-failed-notification": "Impossible de vérifier automatiquement si Syncplay {} est à jour. Vous voulez visiter https://syncplay.pl/ pour vérifier manuellement les mises à jour?", # Syncplay version
|
||||
"syncplay-uptodate-notification": "Syncplay est à jour",
|
||||
"syncplay-updateavailable-notification": "Une nouvelle version de Syncplay est disponible. Voulez-vous visiter la page de publication?",
|
||||
|
||||
"mplayer-file-required-notification": "Syncplay à l'aide de mplayer nécessite que vous fournissiez un fichier au démarrage",
|
||||
"mplayer-file-required-notification/example": "Exemple d'utilisation: syncplay [options] [url|chemin/]nom de fichier",
|
||||
"mplayer2-required": "Syncplay est incompatible avec MPlayer 1.x, veuillez utiliser mplayer2 ou mpv",
|
||||
|
||||
"unrecognized-command-notification": "commande non reconnue",
|
||||
"commandlist-notification": "Commandes disponibles:",
|
||||
"commandlist-notification/room": "\tr [nom] - changer de chambre",
|
||||
"commandlist-notification/list": "\tl - afficher la liste des utilisateurs",
|
||||
"commandlist-notification/undo": "\tu - annuler la dernière recherche",
|
||||
"commandlist-notification/pause": "\tp - basculer sur pause",
|
||||
"commandlist-notification/seek": "\t[s][+-]temps - recherche la valeur de temps donnée, si + ou - n'est pas spécifié c'est le temps absolu en secondes ou min:sec",
|
||||
"commandlist-notification/offset": "\to[+-]duration - offset local playback by the given duration (in seconds or min:sec) from the server seek position - this is a deprecated feature", # TODO: Translate
|
||||
"commandlist-notification/help": "\th - cette aide",
|
||||
"commandlist-notification/toggle": "\tt - bascule si vous êtes prêt à regarder ou non",
|
||||
"commandlist-notification/create": "\tc [nom] - crée une salle gérée en utilisant le nom de la salle actuelle",
|
||||
"commandlist-notification/auth": "\tun [mot de passe] - s'authentifier en tant qu'opérateur de salle avec le mot de passe opérateur",
|
||||
"commandlist-notification/chat": "\tch [message] - envoyer un message de chat dans une pièce",
|
||||
"commandList-notification/queue": "\tqa [fichier/url] - ajoute un fichier ou une URL au bas de la liste de lecture",
|
||||
"commandList-notification/playlist": "\tql - afficher la liste de lecture actuelle",
|
||||
"commandList-notification/select": "\tqs [index] - sélectionnez l'entrée donnée dans la liste de lecture",
|
||||
"commandList-notification/delete": "\tqd [index] - supprime l'entrée donnée de la liste de lecture",
|
||||
"syncplay-version-notification": "Version de Syncplay: {}", # syncplay.version
|
||||
"more-info-notification": "Plus d'informations disponibles sur: {}", # projectURL
|
||||
|
||||
"gui-data-cleared-notification": "Syncplay a effacé les données d'état de chemin et de fenêtre utilisées par l'interface graphique.",
|
||||
"language-changed-msgbox-label": "La langue sera modifiée lorsque vous exécuterez Syncplay.",
|
||||
"promptforupdate-label": "Est-ce que Syncplay peut vérifier automatiquement les mises à jour de temps en temps?",
|
||||
|
||||
"media-player-latency-warning": "Avertissement: Le lecteur multimédia a mis {}secondes à répondre. Si vous rencontrez des problèmes de synchronisation, fermez les applications pour libérer des ressources système, et si cela ne fonctionne pas, essayez un autre lecteur multimédia.", # Seconds to respond
|
||||
"mpv-unresponsive-error": "mpv n'a pas répondu pendant {} secondes et semble donc avoir mal fonctionné. Veuillez redémarrer Syncplay.", # Seconds to respond
|
||||
|
||||
# Client prompts
|
||||
"enter-to-exit-prompt": "Appuyez sur entrée pour quitter",
|
||||
|
||||
# Client errors
|
||||
"missing-arguments-error": "Certains arguments nécessaires sont manquants, reportez-vous à --help",
|
||||
"server-timeout-error": "La connexion avec le serveur a expiré",
|
||||
"mpc-slave-error": "Impossible de démarrer MPC en mode esclave!",
|
||||
"mpc-version-insufficient-error": "La version MPC n'est pas suffisante, veuillez utiliser `mpc-hc` >= `{}`",
|
||||
"mpc-be-version-insufficient-error": "La version MPC n'est pas suffisante, veuillez utiliser `mpc-be` >= `{}`",
|
||||
"mpv-version-error": "Syncplay n'est pas compatible avec cette version de mpv. Veuillez utiliser une version différente de mpv (par exemple Git HEAD).",
|
||||
"mpv-failed-advice": "La raison pour laquelle mpv ne peut pas démarrer peut être due à l'utilisation d'arguments de ligne de commande non pris en charge ou à une version non prise en charge de mpv.",
|
||||
"player-file-open-error": "Le lecteur n'a pas réussi à ouvrir le fichier",
|
||||
"player-path-error": "Le chemin du lecteur n'est pas défini correctement. Les lecteurs pris en charge sont : mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2 et IINA",
|
||||
"hostname-empty-error": "Le nom d'hôte ne peut pas être vide",
|
||||
"empty-error": "{} ne peut pas être vide", # Configuration
|
||||
"media-player-error": "Media player error: \"{}\"", # Error line
|
||||
"unable-import-gui-error": "Impossible d'importer les bibliothèques GUI. Si vous n'avez pas installé PySide, vous devrez l'installer pour que l'interface graphique fonctionne.",
|
||||
"unable-import-twisted-error": "Impossible d'importer Twisted. Veuillez installer Twisted v16.4.0 ou une version ultérieure.",
|
||||
|
||||
"arguments-missing-error": "Certains arguments nécessaires sont manquants, reportez-vous à --help",
|
||||
|
||||
"unable-to-start-client-error": "Impossible de démarrer le client",
|
||||
|
||||
"player-path-config-error": "Le chemin du lecteur n'est pas défini correctement. Les lecteurs pris en charge sont : mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2 et IINA.",
|
||||
"no-file-path-config-error": "Le fichier doit être sélectionné avant de démarrer votre lecteur",
|
||||
"no-hostname-config-error": "Le nom d'hôte ne peut pas être vide",
|
||||
"invalid-port-config-error": "Le port doit être valide",
|
||||
"empty-value-config-error": "{} ne peut pas être vide", # Config option
|
||||
|
||||
"not-json-error": "Pas une chaîne encodée en json",
|
||||
"hello-arguments-error": "Pas assez d'arguments pour Hello", # DO NOT TRANSLATE
|
||||
"version-mismatch-error": "Non-concordance entre les versions du client et du serveur",
|
||||
"vlc-failed-connection": "Échec de la connexion à VLC. Si vous n'avez pas installé syncplay.lua et utilisez la dernière version de VLC, veuillez vous référer à https://syncplay.pl/LUA/ pour obtenir des instructions. Syncplay et VLC 4 ne sont actuellement pas compatibles, utilisez donc VLC 3 ou une alternative telle que mpv.",
|
||||
"vlc-failed-noscript": "VLC a signalé que le script d'interface syncplay.lua n'a pas été installé. Veuillez vous référer à https://syncplay.pl/LUA/ pour obtenir des instructions.",
|
||||
"vlc-failed-versioncheck": "Cette version de VLC n'est pas prise en charge par Syncplay.",
|
||||
"vlc-initial-warning": "VLC ne fournit pas toujours des informations de position précises à Syncplay, en particulier pour les fichiers .mp4 et .avi. Si vous rencontrez des problèmes de recherche erronée, essayez un autre lecteur multimédia tel que <a href=\"https://mpv.io/\">mpv</a> (ou <a href=\"https://github.com/stax76/mpv.net/\">mpv.net</a> pour les utilisateurs de Windows).",
|
||||
|
||||
"feature-sharedPlaylists": "listes de lecture partagées", # used for not-supported-by-server-error
|
||||
"feature-chat": "chat", # used for not-supported-by-server-error
|
||||
"feature-readiness": "préparation", # used for not-supported-by-server-error
|
||||
"feature-managedRooms": "salons gérés", # used for not-supported-by-server-error
|
||||
|
||||
"not-supported-by-server-error": "La fonctionnalité {} n'est pas prise en charge par ce serveur.", # feature
|
||||
"shared-playlists-not-supported-by-server-error": "La fonctionnalité de listes de lecture partagées peut ne pas être prise en charge par le serveur. Pour s'assurer qu'il fonctionne correctement, il faut un serveur exécutant Syncplay {}+, mais le serveur exécute Syncplay {}.", # minVersion, serverVersion
|
||||
"shared-playlists-disabled-by-server-error": "La fonctionnalité de liste de lecture partagée a été désactivée dans la configuration du serveur. Pour utiliser cette fonctionnalité, vous devrez vous connecter à un autre serveur.",
|
||||
|
||||
"invalid-seek-value": "Valeur de recherche non valide",
|
||||
"invalid-offset-value": "Valeur de décalage non valide",
|
||||
|
||||
"switch-file-not-found-error": "Impossible de passer au fichier ''. Syncplay recherche dans les répertoires multimédias spécifiés.", # File not found
|
||||
"folder-search-timeout-error": "La recherche de médias dans les répertoires de médias a été abandonnée car la recherche dans '{}' a pris trop de temps. Cela se produira si vous sélectionnez un dossier avec trop de sous-dossiers dans votre liste de dossiers multimédias à parcourir. Pour que le basculement automatique des fichiers fonctionne à nouveau, veuillez sélectionner Fichier->Définir les répertoires multimédias dans la barre de menu et supprimez ce répertoire ou remplacez-le par un sous-dossier approprié. Si le dossier est correct, vous pouvez le réactiver en sélectionnant Fichier->Définir les répertoires multimédias et en appuyant sur «OK».", # Folder
|
||||
"folder-search-first-file-timeout-error": "La recherche de média dans '{}' a été abandonnée car elle a pris trop de temps pour accéder au répertoire. Cela peut arriver s'il s'agit d'un lecteur réseau ou si vous configurez votre lecteur pour qu'il ralentisse après une période d'inactivité. Pour que le basculement automatique des fichiers fonctionne à nouveau, accédez à Fichier-> Définir les répertoires multimédias et supprimez le répertoire ou résolvez le problème (par exemple en modifiant les paramètres d'économie d'énergie).", # Folder
|
||||
"added-file-not-in-media-directory-error": "Vous avez chargé un fichier dans '{}' qui n'est pas un répertoire média connu. Vous pouvez l'ajouter en tant que répertoire multimédia en sélectionnant Fichier->Définir les répertoires multimédias dans la barre de menus.", # Folder
|
||||
"no-media-directories-error": "Aucun répertoire multimédia n'a été défini. Pour que les fonctionnalités de liste de lecture partagée et de changement de fichier fonctionnent correctement, sélectionnez Fichier-> Définir les répertoires multimédias et spécifiez où Syncplay doit rechercher les fichiers multimédias.",
|
||||
"cannot-find-directory-error": "Impossible de trouver le répertoire multimédia '{}'. Pour mettre à jour votre liste de répertoires multimédias, veuillez sélectionner Fichier->Définir les répertoires multimédias dans la barre de menu et spécifiez où Syncplay doit chercher pour trouver les fichiers multimédias.",
|
||||
|
||||
"failed-to-load-server-list-error": "Échec du chargement de la liste des serveurs publics. Veuillez visiter https://www.syncplay.pl/ dans votre navigateur.",
|
||||
|
||||
# Client arguments
|
||||
"argument-description": "Solution pour synchroniser la lecture de plusieurs instances de lecteur multimédia sur le réseau.",
|
||||
"argument-epilog": "Si aucune option n'est fournie, les valeurs _config seront utilisées",
|
||||
"nogui-argument": "masquer l'interface graphique",
|
||||
"host-argument": "adresse du serveur",
|
||||
"name-argument": "nom d'utilisateur souhaité",
|
||||
"debug-argument": "Mode débogage",
|
||||
"force-gui-prompt-argument": "faire apparaître l'invite de configuration",
|
||||
"no-store-argument": "ne pas stocker de valeurs dans .syncplay",
|
||||
"room-argument": "salon par défaut",
|
||||
"password-argument": "Mot de passe du serveur",
|
||||
"player-path-argument": "chemin d'accès à l'exécutable de votre lecteur",
|
||||
"file-argument": "fichier à lire",
|
||||
"args-argument": 'player options, if you need to pass options starting with - prepend them with single \'--\' argument',
|
||||
"clear-gui-data-argument": "réinitialise les données GUI du chemin et de l'état de la fenêtre stockées en tant que QSettings",
|
||||
"language-argument": "langue pour les messages Syncplay (de/en/ru/it/es/pt_BR/pt_PT/tr/fr/zh_CN)",
|
||||
|
||||
"version-argument": "imprime votre version",
|
||||
"version-message": "Vous utilisez Syncplay version {} ({})",
|
||||
|
||||
"load-playlist-from-file-argument": "charge la liste de lecture à partir d'un fichier texte (une entrée par ligne)",
|
||||
|
||||
|
||||
# Client labels
|
||||
"config-window-title": "configuration Syncplay",
|
||||
|
||||
"connection-group-title": "Paramètres de connexion",
|
||||
"host-label": "Adresse du serveur:",
|
||||
"name-label": "Nom d'utilisateur (facultatif):",
|
||||
"password-label": "Mot de passe du serveur (le cas échéant):",
|
||||
"room-label": "Salon par défaut:",
|
||||
"roomlist-msgbox-label": "Modifier la liste des salons (une par ligne)",
|
||||
|
||||
"media-setting-title": "Paramètres du lecteur multimédia",
|
||||
"executable-path-label": "Chemin d'accès au lecteur multimédia:",
|
||||
"media-path-label": "Chemin d'accès à la vidéo (facultatif):",
|
||||
"player-arguments-label": "Arguments du joueur (le cas échéant):",
|
||||
"browse-label": "Parcourir",
|
||||
"update-server-list-label": "Mettre à jour la liste",
|
||||
|
||||
"more-title": "Afficher plus de paramètres",
|
||||
"never-rewind-value": "Jamais",
|
||||
"seconds-suffix": "secs",
|
||||
"privacy-sendraw-option": "Envoyer brut",
|
||||
"privacy-sendhashed-option": "Envoyer haché",
|
||||
"privacy-dontsend-option": "Ne pas envoyer",
|
||||
"filename-privacy-label": "Informations sur le nom de fichier:",
|
||||
"filesize-privacy-label": "Informations sur la taille du fichier:",
|
||||
"checkforupdatesautomatically-label": "Rechercher automatiquement les mises à jour de Syncplay",
|
||||
"autosavejoinstolist-label": "Ajouter les salons que vous rejoignez à la liste des salons",
|
||||
"slowondesync-label": "Ralentissement en cas de désynchronisation mineure (non pris en charge sur MPC-HC/BE)",
|
||||
"rewindondesync-label": "Retour en arrière en cas de désynchronisation majeure (recommandé)",
|
||||
"fastforwardondesync-label": "Avance rapide en cas de retard (recommandé)",
|
||||
"dontslowdownwithme-label": "Ne jamais ralentir ou rembobiner les autres (expérimental)",
|
||||
"pausing-title": "Pause",
|
||||
"pauseonleave-label": "Pause lorsque l'utilisateur quitte (par exemple s'il est déconnecté)",
|
||||
"readiness-title": "État de préparation initial",
|
||||
"readyatstart-label": "Définissez-moi comme «prêt à regarder» par défaut",
|
||||
"forceguiprompt-label": "Ne pas toujours afficher la fenêtre de configuration Syncplay", # (Inverted)
|
||||
"showosd-label": "Activer les Messages OSD",
|
||||
|
||||
"showosdwarnings-label": "Inclure des avertissements (par exemple, lorsque les fichiers sont différents, les utilisateurs ne sont pas prêts)",
|
||||
"showsameroomosd-label": "Inclure des événements dans votre salon",
|
||||
"shownoncontrollerosd-label": "Inclure les événements des non-opérateurs dans les salons gérés",
|
||||
"showdifferentroomosd-label": "Inclure des événements dans d'autres salons",
|
||||
"showslowdownosd-label": "Inclure les notifications de ralentissement/annulation",
|
||||
"language-label": "Langue:",
|
||||
"automatic-language": "Défaut ({})", # Default language
|
||||
"showdurationnotification-label": "Avertir des incohérences de durée de média",
|
||||
"basics-label": "Réglages de base",
|
||||
"readiness-label": "Jouer pause",
|
||||
"misc-label": "Divers",
|
||||
"core-behaviour-title": "Comportement du salon principal",
|
||||
"syncplay-internals-title": "procédures internes",
|
||||
"syncplay-mediasearchdirectories-title": "Répertoires pour rechercher des médias",
|
||||
"syncplay-mediasearchdirectories-label": "Répertoires pour rechercher des médias (un chemin par ligne)",
|
||||
"sync-label": "Synchroniser",
|
||||
"sync-otherslagging-title": "Si d'autres sont à la traîne...",
|
||||
"sync-youlaggging-title": "Si vous êtes à la traîne...",
|
||||
"messages-label": "Messages",
|
||||
"messages-osd-title": "Paramètres d'affichage à l'écran",
|
||||
"messages-other-title": "Autres paramètres d'affichage",
|
||||
"chat-label": "Chat",
|
||||
"privacy-label": "Sécurité données", # Currently unused, but will be brought back if more space is needed in Misc tab
|
||||
"privacy-title": "Paramètres de confidentialité",
|
||||
"unpause-title": "Si vous appuyez sur play, définissez comme prêt et:",
|
||||
"unpause-ifalreadyready-option": "Annuler la pause si déjà défini comme prêt",
|
||||
"unpause-ifothersready-option": "Reprendre la pause si déjà prêt ou si d'autres personnes dans la pièce sont prêtes (par défaut)",
|
||||
"unpause-ifminusersready-option": "Annuler la pause si déjà prêt ou si tous les autres sont prêts et utilisateurs minimum prêts",
|
||||
"unpause-always": "Toujours reprendre",
|
||||
"syncplay-trusteddomains-title": "Domaines de confiance (pour les services de streaming et le contenu hébergé)",
|
||||
|
||||
"chat-title": "Saisie du message de discussion",
|
||||
"chatinputenabled-label": "Activer la saisie de discussion via mpv",
|
||||
"chatdirectinput-label": "Autoriser la saisie de discussion instantanée (éviter d'avoir à appuyer sur la touche Entrée pour discuter)",
|
||||
"chatinputfont-label": "Police de caractères pour la saisie sur le Chat ",
|
||||
"chatfont-label": "Définir la fonte",
|
||||
"chatcolour-label": "Définir la couleur",
|
||||
"chatinputposition-label": "Position de la zone de saisie des messages dans mpv",
|
||||
"chat-top-option": "Haut",
|
||||
"chat-middle-option": "Milieu",
|
||||
"chat-bottom-option": "Bas",
|
||||
"chatoutputheader-label": "Sortie du message de discussion",
|
||||
"chatoutputfont-label": "Police de sortie du chat",
|
||||
"chatoutputenabled-label": "Activer la sortie du chat dans le lecteur multimédia (mpv uniquement pour l'instant)",
|
||||
"chatoutputposition-label": "Mode de sortie",
|
||||
"chat-chatroom-option": "Style de salon de discussion",
|
||||
"chat-scrolling-option": "Style de défilement",
|
||||
|
||||
"mpv-key-tab-hint": "[TAB] pour basculer l'accès aux raccourcis des touches de la ligne alphabétique.",
|
||||
"mpv-key-hint": "[ENTER] pour envoyer un message. [ESC] pour quitter le mode chat.",
|
||||
"alphakey-mode-warning-first-line": "Vous pouvez temporairement utiliser les anciennes liaisons mpv avec les touches az.",
|
||||
"alphakey-mode-warning-second-line": "Appuyez sur [TAB] pour revenir au mode de discussion Syncplay.",
|
||||
|
||||
"help-label": "Aider",
|
||||
"reset-label": "Réinitialiser",
|
||||
"run-label": "Exécuter Syncplay",
|
||||
"storeandrun-label": "Stocker la configuration et exécuter Syncplay",
|
||||
|
||||
"contact-label": "Feel free to e-mail <a href=\"mailto:dev@syncplay.pl\"><nobr>dev@syncplay.pl</nobr></a>, <a href=\"https://github.com/Syncplay/syncplay/issues\"><nobr>create an issue</nobr></a> to report a bug/problem via GitHub, <a href=\"https://github.com/Syncplay/syncplay/discussions\"><nobr>start a discussion</nobr></a> to make a suggestion or ask a question via GitHub, <a href=\"https://www.facebook.com/SyncplaySoftware\"><nobr>like us on Facebook</nobr></a>, <a href=\"https://twitter.com/Syncplay/\"><nobr>follow us on Twitter</nobr></a>, or visit <a href=\"https://syncplay.pl/\"><nobr>https://syncplay.pl/</nobr></a>. Do not use Syncplay to send sensitive information.",
|
||||
|
||||
"joinroom-label": "Rejoindre la salle",
|
||||
"joinroom-menu-label": "Rejoindre la salle {}",
|
||||
"seektime-menu-label": "Chercher le temps",
|
||||
"undoseek-menu-label": "Annuler la recherche",
|
||||
"play-menu-label": "Jouer",
|
||||
"pause-menu-label": "Pause",
|
||||
"playbackbuttons-menu-label": "Afficher les boutons de lecture",
|
||||
"autoplay-menu-label": "Afficher le bouton de lecture automatique",
|
||||
"autoplay-guipushbuttonlabel": "Jouer quand tout est prêt",
|
||||
"autoplay-minimum-label": "Utilisateurs minimum:",
|
||||
"hideemptyrooms-menu-label": "Hide empty persistent rooms", # TODO: Translate
|
||||
|
||||
"sendmessage-label": "Envoyer",
|
||||
|
||||
"ready-guipushbuttonlabel": "Je suis prêt à regarder !",
|
||||
|
||||
"roomuser-heading-label": "Salon / Utilisateur",
|
||||
"size-heading-label": "Taille",
|
||||
"duration-heading-label": "Durée",
|
||||
"filename-heading-label": "Nom de fichier",
|
||||
"notifications-heading-label": "Notifications",
|
||||
"userlist-heading-label": "Liste de qui joue quoi",
|
||||
|
||||
"browseformedia-label": "Parcourir les fichiers multimédias",
|
||||
|
||||
"file-menu-label": "&Fichier", # & precedes shortcut key
|
||||
"openmedia-menu-label": "&Ouvrir le fichier multimédia",
|
||||
"openstreamurl-menu-label": "Ouvrir l'URL du &flux multimédia",
|
||||
"setmediadirectories-menu-label": "Définir les &répertoires multimédias",
|
||||
"loadplaylistfromfile-menu-label": "&Charger la liste de lecture à partir du fichier",
|
||||
"saveplaylisttofile-menu-label": "&Enregistrer la liste de lecture dans un fichier",
|
||||
"exit-menu-label": "Sortir",
|
||||
"advanced-menu-label": "&Avancée",
|
||||
"window-menu-label": "&Fenêtre",
|
||||
"setoffset-menu-label": "Définir &décalage",
|
||||
"createcontrolledroom-menu-label": "&Créer une salon à gérer",
|
||||
"identifyascontroller-menu-label": "&Identifier en tant qu'opérateur de salon",
|
||||
"settrusteddomains-menu-label": "Définir des &domaines de confiance",
|
||||
"addtrusteddomain-menu-label": "Ajouter {} comme domaine de confiance", # Domain
|
||||
|
||||
"edit-menu-label": "&Éditer",
|
||||
"cut-menu-label": "Couper",
|
||||
"copy-menu-label": "&Copier",
|
||||
"paste-menu-label": "&Coller",
|
||||
"selectall-menu-label": "&Tout sélectionner",
|
||||
|
||||
"playback-menu-label": "&Relecture",
|
||||
|
||||
"help-menu-label": "&Aide",
|
||||
"userguide-menu-label": "Ouvrir le &guide de l'utilisateur",
|
||||
"update-menu-label": "Rechercher et mettre à jour",
|
||||
|
||||
"startTLS-initiated": "Tentative de connexion sécurisée",
|
||||
"startTLS-secure-connection-ok": "Connexion sécurisée établie ({})",
|
||||
"startTLS-server-certificate-invalid": "Échec de la Connexion Sécurisée. Le serveur utilise un certificat de sécurité non valide. Cette communication pourrait être interceptée par un tiers. Pour plus de détails et de dépannage, voir <a href=\"https://syncplay.pl/trouble\">ici</a> .",
|
||||
"startTLS-server-certificate-invalid-DNS-ID": "Syncplay ne fait pas confiance à ce serveur car il utilise un certificat qui n'est pas valide pour son nom d'hôte.",
|
||||
"startTLS-not-supported-client": "Ce client ne prend pas en charge TLS",
|
||||
"startTLS-not-supported-server": "Ce serveur ne prend pas en charge TLS",
|
||||
|
||||
# TLS certificate dialog
|
||||
"tls-information-title": "Détails du certificat",
|
||||
"tls-dialog-status-label": "<strong>Syncplay utilise une connexion cryptée à {}.</strong>",
|
||||
"tls-dialog-desc-label": "Le cryptage avec un certificat numérique préserve la confidentialité des informations lorsqu'elles sont envoyées vers ou depuis le serveur {}.",
|
||||
"tls-dialog-connection-label": "Informations chiffrées à l'aide de Transport Layer Security (TLS), version {} avec la suite de chiffrement: {}.",
|
||||
"tls-dialog-certificate-label": "Certificat délivré par {} valable jusqu'au {}.",
|
||||
|
||||
# About dialog
|
||||
"about-menu-label": "&À propos de la lecture synchronisée",
|
||||
"about-dialog-title": "À propos de Syncplay",
|
||||
"about-dialog-release": "Version {} release {}",
|
||||
"about-dialog-license-text": "Sous licence Apache, version 2.0",
|
||||
"about-dialog-license-button": "Licence",
|
||||
"about-dialog-dependencies": "Dépendances",
|
||||
|
||||
"setoffset-msgbox-label": "Définir le décalage",
|
||||
"offsetinfo-msgbox-label": "Offset (voir https://syncplay.pl/guide/ pour les instructions d'utilisation):",
|
||||
|
||||
"promptforstreamurl-msgbox-label": "Ouvrir l'URL du flux multimédia",
|
||||
"promptforstreamurlinfo-msgbox-label": "URL de diffusion",
|
||||
|
||||
"addfolder-label": "Ajouter le dossier",
|
||||
|
||||
"adduris-msgbox-label": "Ajouter des URL à la liste de lecture (une par ligne)",
|
||||
"editplaylist-msgbox-label": "Définir la liste de lecture (une par ligne)",
|
||||
"trusteddomains-msgbox-label": "Domaines vers lesquels vous pouvez basculer automatiquement (un par ligne)",
|
||||
|
||||
"createcontrolledroom-msgbox-label": "Créer un salon à gérer",
|
||||
"controlledroominfo-msgbox-label": "Enter name of managed room\r\n(see https://syncplay.pl/guide/ for usage instructions):",
|
||||
|
||||
"identifyascontroller-msgbox-label": "S'identifier en tant qu'opérateur de salon",
|
||||
"identifyinfo-msgbox-label": "Enter operator password for this room\r\n(see https://syncplay.pl/guide/ for usage instructions):",
|
||||
|
||||
"public-server-msgbox-label": "Sélectionnez le serveur public pour cette session de visualisation",
|
||||
|
||||
"megabyte-suffix": "Mo",
|
||||
|
||||
# Tooltips
|
||||
|
||||
"host-tooltip": "Nom d'hôte ou IP auquel se connecter, incluant éventuellement le port (par exemple syncplay.pl:8999). Uniquement synchronisé avec des personnes sur le même serveur/port.",
|
||||
"name-tooltip": "Surnom sous lequel vous serez connu. Pas d'inscription, donc peut facilement changer plus tard. Nom aléatoire généré si aucun n'est spécifié.",
|
||||
"password-tooltip": "Les mots de passe ne sont nécessaires que pour se connecter à des serveurs privés.",
|
||||
"room-tooltip": "Le salon à rejoindre lors de la connexion peut être presque n'importe quoi, mais vous ne serez synchronisé qu'avec des personnes dans le même salon.",
|
||||
|
||||
"edit-rooms-tooltip": "Modifier la liste des salons.",
|
||||
|
||||
"executable-path-tooltip": "Emplacement du lecteur multimédia pris en charge que vous avez choisi (mpv, mpv.net, VLC, MPC-HC/BE, mplayer2 ou IINA).",
|
||||
"media-path-tooltip": "Emplacement de la vidéo ou du flux à ouvrir. Nécessaire pour mplayer2.",
|
||||
"player-arguments-tooltip": "Arguments/commutateurs de ligne de commande supplémentaires à transmettre à ce lecteur multimédia.",
|
||||
"mediasearcdirectories-arguments-tooltip": "Répertoires dans lesquels Syncplay recherchera les fichiers multimédias, par exemple lorsque vous utilisez la fonctionalité cliquer pour basculer. Syncplay recherchera récursivement dans les sous-dossiers.",
|
||||
|
||||
"more-tooltip": "Afficher les paramètres moins fréquemment utilisés.",
|
||||
"filename-privacy-tooltip": "Mode de confidentialité pour l'envoi du nom de fichier en cours de lecture au serveur.",
|
||||
"filesize-privacy-tooltip": "Mode de confidentialité pour l'envoi de la taille du fichier en cours de lecture au serveur.",
|
||||
"privacy-sendraw-tooltip": "Envoyez ces informations sans brouillage. Il s'agit de l'option par défaut avec la plupart des fonctionnalités.",
|
||||
"privacy-sendhashed-tooltip": "Envoyez une version hachée des informations, les rendant moins visibles pour les autres clients.",
|
||||
"privacy-dontsend-tooltip": "N'envoyez pas ces informations au serveur. Cela garantit une confidentialité maximale.",
|
||||
"checkforupdatesautomatically-tooltip": "Vérifiez régulièrement sur le site Web de Syncplay si une nouvelle version de Syncplay est disponible.",
|
||||
"autosavejoinstolist-tooltip": "Lorsque vous rejoignez un salon sur un serveur, mémorisez automatiquement le nom de la salle dans la liste des salons à rejoindre.",
|
||||
"slowondesync-tooltip": "Réduisez temporairement le taux de lecture si nécessaire pour vous synchroniser avec les autres téléspectateurs. Non pris en charge sur MPC-HC/BE.",
|
||||
"dontslowdownwithme-tooltip": "Cela signifie que les autres ne sont pas ralentis ou rembobinés si votre lecture est en retard. Utile pour les opérateurs de salon.",
|
||||
"pauseonleave-tooltip": "Mettez la lecture en pause si vous êtes déconnecté ou si quelqu'un quitte votre salon.",
|
||||
"readyatstart-tooltip": "Définissez-vous comme «prêt» au début (sinon, vous êtes défini comme «pas prêt» jusqu'à ce que vous changiez votre état de préparation)",
|
||||
"forceguiprompt-tooltip": "La boîte de dialogue de configuration ne s'affiche pas lors de l'ouverture d'un fichier avec Syncplay.", # (Inverted)
|
||||
"nostore-tooltip": "Exécutez Syncplay avec la configuration donnée, mais ne stockez pas les modifications de manière permanente.", # (Inverted)
|
||||
"rewindondesync-tooltip": "Revenez en arrière au besoin pour vous synchroniser. La désactivation de cette option peut entraîner des désynchronisations majeures!",
|
||||
"fastforwardondesync-tooltip": "Avancez en cas de désynchronisation avec l'opérateur de la salle (ou votre position factice si l'option «Ne jamais ralentir ou rembobiner les autres» est activée).",
|
||||
"showosd-tooltip": "Envoie des messages Syncplay à l'OSD du lecteur multimédia.",
|
||||
"showosdwarnings-tooltip": "Afficher des avertissements en cas de lecture d'un fichier différent, seul dans la pièce, utilisateurs non prêts, etc.",
|
||||
"showsameroomosd-tooltip": "Afficher les notifications OSD pour les événements liés à l'utilisateur du salon.",
|
||||
"shownoncontrollerosd-tooltip": "Afficher les notifications OSD pour les événements relatifs aux non-opérateurs qui se trouvent dans les salles gérées.",
|
||||
"showdifferentroomosd-tooltip": "Afficher les notifications OSD pour les événements liés à l'absence de l'utilisateur du salon.",
|
||||
"showslowdownosd-tooltip": "Afficher les notifications de ralentissement / de retour au décalage temps.",
|
||||
"showdurationnotification-tooltip": "Utile lorsqu'un segment dans un fichier en plusieurs parties est manquant, mais peut entraîner des faux positifs.",
|
||||
"language-tooltip": "Langue à utiliser par Syncplay.",
|
||||
"unpause-always-tooltip": "Si vous appuyez sur unpause, cela vous définit toujours comme prêt et non-pause, plutôt que de simplement vous définir comme prêt.",
|
||||
"unpause-ifalreadyready-tooltip": "Si vous appuyez sur unpause lorsque vous n'êtes pas prêt, cela vous mettra comme prêt - appuyez à nouveau sur unpause pour reprendre la pause.",
|
||||
"unpause-ifothersready-tooltip": "Si vous appuyez sur unpause lorsque vous n'êtes pas prêt, il ne reprendra la pause que si d'autres sont prêts.",
|
||||
"unpause-ifminusersready-tooltip": "Si vous appuyez sur annuler la pause lorsqu'il n'est pas prêt, il ne s'arrêtera que si d'autres personnes sont prêtes et que le seuil minimal d'utilisateurs est atteint.",
|
||||
"trusteddomains-arguments-tooltip": "Domaines vers lesquels Syncplay peut basculer automatiquement lorsque les listes de lecture partagées sont activées.",
|
||||
|
||||
"chatinputenabled-tooltip": "Activer la saisie du chat dans mpv (appuyez sur Entrée pour discuter, Entrée pour envoyer, Échap pour annuler)",
|
||||
"chatdirectinput-tooltip": "Évitez d'avoir à appuyer sur «enter» pour passer en mode de saisie de discussion dans mpv. Appuyez sur TAB dans mpv pour désactiver temporairement cette fonctionnalité.",
|
||||
"font-label-tooltip": "Police utilisée lors de la saisie de messages de discussion dans mpv. Côté client uniquement, n'affecte donc pas ce que les autres voient.",
|
||||
"set-input-font-tooltip": "Famille de polices utilisée lors de la saisie de messages de discussion dans mpv. Côté client uniquement, n'affecte donc pas ce que les autres voient.",
|
||||
"set-input-colour-tooltip": "Couleur de police utilisée lors de la saisie de messages de discussion dans mpv. Côté client uniquement, n'affecte donc pas ce que les autres voient.",
|
||||
"chatinputposition-tooltip": "Emplacement dans mpv où le texte d'entrée de discussion apparaîtra lorsque vous appuyez sur Entrée et tapez.",
|
||||
"chatinputposition-top-tooltip": "Placez l'entrée de discussion en haut de la fenêtre mpv.",
|
||||
"chatinputposition-middle-tooltip": "Placez l'entrée de discussion au point mort de la fenêtre mpv.",
|
||||
"chatinputposition-bottom-tooltip": "Placez l'entrée de discussion en bas de la fenêtre mpv.",
|
||||
"chatoutputenabled-tooltip": "Afficher les messages de discussion dans l'OSD (si pris en charge par le lecteur multimédia).",
|
||||
"font-output-label-tooltip": "Police de sortie du chat.",
|
||||
"set-output-font-tooltip": "Police utilisée pour l'affichage des messages de discussion.",
|
||||
"chatoutputmode-tooltip": "Comment les messages de chat sont affichés.",
|
||||
"chatoutputmode-chatroom-tooltip": "Affichez les nouvelles lignes de discussion directement sous la ligne précédente.",
|
||||
"chatoutputmode-scrolling-tooltip": "Faites défiler le texte du chat de droite à gauche.",
|
||||
|
||||
"help-tooltip": "Ouvre le guide de l'utilisateur de Syncplay.pl.",
|
||||
"reset-tooltip": "Réinitialisez tous les paramètres à la configuration par défaut.",
|
||||
"update-server-list-tooltip": "Connectez-vous à syncplay.pl pour mettre à jour la liste des serveurs publics.",
|
||||
|
||||
"sslconnection-tooltip": "Connecté en toute sécurité au serveur. Cliquez pour obtenir les détails du certificat.",
|
||||
|
||||
"joinroom-tooltip": "Quitter la salle actuelle et rejoindre le salon spécifié.",
|
||||
"seektime-msgbox-label": "Aller au temps spécifié (en secondes / min:sec). Utilisez +/- pour la recherche relative.",
|
||||
"ready-tooltip": "Indique si vous êtes prêt à regarder.",
|
||||
"autoplay-tooltip": "Lecture automatique lorsque tous les utilisateurs qui ont un indicateur de disponibilité sont prêts et que le seuil d'utilisateur minimum est atteint.",
|
||||
"switch-to-file-tooltip": "Double-cliquez pour passer à {}", # Filename
|
||||
"sendmessage-tooltip": "Envoyer un message au salon",
|
||||
|
||||
# In-userlist notes (GUI)
|
||||
"differentsize-note": "Différentes tailles!",
|
||||
"differentsizeandduration-note": "Taille et durée différentes !",
|
||||
"differentduration-note": "Durée différente !",
|
||||
"nofile-note": "(Aucun fichier en cours de lecture)",
|
||||
|
||||
# Server messages to client
|
||||
"new-syncplay-available-motd-message": "Vous utilisez Syncplay {} mais une version plus récente est disponible sur https://syncplay.pl", # ClientVersion
|
||||
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # NOTE: Do not translate the word -temp # TODO: Translate
|
||||
|
||||
# Server notifications
|
||||
"welcome-server-notification": "Bienvenue sur le serveur Syncplay, ver.", # version
|
||||
"client-connected-room-server-notification": "({2}) connecté à la salle '{1}'", # username, host, room
|
||||
"client-left-server-notification": "a quitté le serveur", # name
|
||||
"no-salt-notification": "VEUILLEZ NOTER: Pour permettre aux mots de passe d'opérateur de salle générés par cette instance de serveur de fonctionner lorsque le serveur est redémarré, veuillez ajouter l'argument de ligne de commande suivant lors de l'exécution du serveur Syncplay à l'avenir: --salt {}", # Salt
|
||||
|
||||
|
||||
# Server arguments
|
||||
"server-argument-description": "Solution pour synchroniser la lecture de plusieurs instances de lecteur multimédia sur le réseau. Instance de serveur",
|
||||
"server-argument-epilog": "Si aucune option n'est fournie, les valeurs _config seront utilisées",
|
||||
"server-port-argument": "port TCP du serveur",
|
||||
"server-password-argument": "Mot de passe du serveur",
|
||||
"server-isolate-room-argument": "faut-il isoler les salons ?",
|
||||
"server-salt-argument": "chaîne aléatoire utilisée pour générer les mots de passe des salons gérés",
|
||||
"server-disable-ready-argument": "désactiver la fonction de préparation",
|
||||
"server-motd-argument": "chemin vers le fichier à partir duquel motd sera récupéré",
|
||||
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
||||
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled", # TODO: Translate
|
||||
"server-chat-argument": "Le chat doit-il être désactivé?",
|
||||
"server-chat-maxchars-argument": "Nombre maximum de caractères dans un message de discussion (la valeur par défaut est {})", # Default number of characters
|
||||
"server-maxusernamelength-argument": "Nombre maximum de caractères dans un nom d'utilisateur (la valeur par défaut est {})",
|
||||
"server-stats-db-file-argument": "Activer les statistiques du serveur à l'aide du fichier db SQLite fourni",
|
||||
"server-startTLS-argument": "Activer les connexions TLS à l'aide des fichiers de certificat dans le chemin fourni",
|
||||
"server-messed-up-motd-unescaped-placeholders": "Le message du jour a des espaces réservés non échappés. Tous les signes $ doivent être doublés ($$).",
|
||||
"server-messed-up-motd-too-long": "Le message du jour est trop long: {}caractères maximum, {} donnés.",
|
||||
|
||||
# Server errors
|
||||
"unknown-command-server-error": "Commande inconnue {}", # message
|
||||
"not-json-server-error": "Pas une chaîne encodée json {}", # message
|
||||
"line-decode-server-error": "Pas une chaîne utf-8",
|
||||
"not-known-server-error": "Vous devez être connu du serveur avant d'envoyer cette commande",
|
||||
"client-drop-server-error": "Client drop: {} -- {}", # host, error
|
||||
"password-required-server-error": "Mot de passe requis",
|
||||
"wrong-password-server-error": "Mauvais mot de passe fourni",
|
||||
"hello-server-error": "Pas assez d'arguments pour Hello", # DO NOT TRANSLATE
|
||||
|
||||
# Playlists
|
||||
"playlist-selection-changed-notification": "{} a modifié la sélection de la liste de lecture", # Username
|
||||
"playlist-contents-changed-notification": "{} a mis à jour la liste de lecture", # Username
|
||||
"cannot-find-file-for-playlist-switch-error": "Impossible de trouver le fichier {} dans les répertoires multimédias pour le changement de liste de lecture!", # Filename
|
||||
"cannot-add-duplicate-error": "Impossible d'ajouter la deuxième entrée pour '{}' à la liste de lecture car aucun doublon n'est autorisé.", # Filename
|
||||
"cannot-add-unsafe-path-error": "Impossible de charger automatiquement {}, car il ne se trouve pas sur un domaine approuvé. Vous pouvez basculer manuellement vers l'URL en double-cliquant dessus dans la liste de lecture et ajouter des domaines de confiance via Fichier->Avancé->Définir les domaines de confiance. Si vous faites un clic droit sur une URL, vous pouvez ajouter son domaine en tant que domaine de confiance via le menu contextuel.", # Filename
|
||||
"sharedplaylistenabled-label": "Activer les listes de lecture partagées",
|
||||
"removefromplaylist-menu-label": "Supprimer de la liste de lecture",
|
||||
"shuffleremainingplaylist-menu-label": "Mélanger la liste de lecture restante",
|
||||
"shuffleentireplaylist-menu-label": "Mélanger toute la liste de lecture",
|
||||
"undoplaylist-menu-label": "Annuler la dernière modification de la liste de lecture",
|
||||
"addfilestoplaylist-menu-label": "Ajouter des fichiers au bas de la liste de lecture",
|
||||
"addurlstoplaylist-menu-label": "Ajouter des URL au bas de la liste de lecture",
|
||||
"editplaylist-menu-label": "Modifier la liste de lecture",
|
||||
|
||||
"open-containing-folder": "Ouvrir le dossier contenant ce fichier",
|
||||
"addyourfiletoplaylist-menu-label": "Ajoutez votre fichier à la liste de lecture",
|
||||
"addotherusersfiletoplaylist-menu-label": "Ajouter le fichier de {} à la liste de lecture", # [Username]
|
||||
"addyourstreamstoplaylist-menu-label": "Ajoutez votre flux à la liste de lecture",
|
||||
"addotherusersstreamstoplaylist-menu-label": "Ajouter {}' stream à la playlist", # [Username]
|
||||
"openusersstream-menu-label": "Ouvrir le flux de {}", # [username]'s
|
||||
"openusersfile-menu-label": "Ouvrir le fichier de {}", # [username]'s
|
||||
|
||||
"playlist-instruction-item-message": "Faites glisser le fichier ici pour l'ajouter à la liste de lecture partagée.",
|
||||
"sharedplaylistenabled-tooltip": "Les opérateurs de salle peuvent ajouter des fichiers à une liste de lecture synchronisée pour permettre à tout le monde de regarder facilement la même chose. Configurez les répertoires multimédias sous «Divers».",
|
||||
|
||||
"playlist-empty-error": "La liste de lecture est actuellement vide.",
|
||||
"playlist-invalid-index-error": "Index de liste de lecture non valide",
|
||||
}
|
||||
@ -82,12 +82,14 @@ it = {
|
||||
"commandlist-notification/undo": "\tu - annulla l'ultima ricerca",
|
||||
"commandlist-notification/pause": "\tp - attiva o disattiva la pausa",
|
||||
"commandlist-notification/seek": "\t[s][+-]tempo - salta all'istante di tempo dato, se + o - non è specificato si considera il tempo assoluto in secondi o min:sec",
|
||||
"commandlist-notification/offset": "\to[+-]duration - offset local playback by the given duration (in seconds or min:sec) from the server seek position - this is a deprecated feature", # TODO: Translate
|
||||
"commandlist-notification/help": "\th - mostra questo help",
|
||||
"commandlist-notification/toggle": "\tt - attiva o disattiva la funzionalità \"pronto\"",
|
||||
"commandlist-notification/create": "\tc [nome] - crea una stanza gestita usando il nome della stanza attuale",
|
||||
"commandlist-notification/auth": "\ta [password] - autentica come gestore della stanza, utilizzando la password del gestore",
|
||||
"commandlist-notification/chat": "\tch [message] - invia un messaggio nella chat della stanza",
|
||||
"commandList-notification/queue": "\tqa [file/url] - add file or url to bottom of playlist", # TO DO: Translate
|
||||
"commandList-notification/queueandselect": "\tqas [file/url] - add file or url to bottom of playlist and select it", # TO DO: Translate
|
||||
"commandList-notification/playlist": "\tql - show the current playlist", # TO DO: Translate
|
||||
"commandList-notification/select": "\tqs [index] - select given entry in the playlist", # TO DO: Translate
|
||||
"commandList-notification/delete": "\tqd [index] - delete the given entry from the playlist", # TO DO: Translate
|
||||
@ -174,7 +176,7 @@ it = {
|
||||
"file-argument": 'file da riprodurre',
|
||||
"args-argument": 'opzioni del player, se hai bisogno di utilizzare opzioni che iniziano con - anteponi un singolo \'--\'',
|
||||
"clear-gui-data-argument": 'ripristina il percorso e i dati impostati tramite interfaccia grafica e salvati come QSettings',
|
||||
"language-argument": 'lingua per i messaggi di Syncplay (de/en/ru/it/es/pt_BR/pt_PT/tr)',
|
||||
"language-argument": 'lingua per i messaggi di Syncplay (de/en/ru/it/es/pt_BR/pt_PT/tr/fr/zh_CN)',
|
||||
|
||||
"version-argument": 'mostra la tua versione',
|
||||
"version-message": "Stai usando la versione di Syncplay {} ({})",
|
||||
@ -289,6 +291,7 @@ it = {
|
||||
"autoplay-menu-label": "Mostra il tasto di riproduzione automatica",
|
||||
"autoplay-guipushbuttonlabel": "Riproduci quando tutti sono pronti",
|
||||
"autoplay-minimum-label": "Minimo utenti pronti:",
|
||||
"hideemptyrooms-menu-label": "Hide empty persistent rooms", # TODO: Translate
|
||||
|
||||
"sendmessage-label": "Invia",
|
||||
|
||||
@ -455,6 +458,7 @@ it = {
|
||||
|
||||
# Server messages to client
|
||||
"new-syncplay-available-motd-message": "Stai usando Syncplay {} ma una nuova versione è disponibile presso https://syncplay.pl", # ClientVersion
|
||||
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # TO DO: Translate - NOTE: Do not translate the word -temp to create a room where information is not saved then put -temp at the end of the room name.", # TO DO: Translate
|
||||
|
||||
# Server notifications
|
||||
"welcome-server-notification": "Benvenuto nel server Syncplay, ver. {0}", # version
|
||||
@ -472,6 +476,8 @@ it = {
|
||||
"server-salt-argument": "usare stringhe casuali per generare le password delle stanze gestite",
|
||||
"server-disable-ready-argument": "disabilita la funzionalità \"pronto\"",
|
||||
"server-motd-argument": "percorso del file da cui verrà letto il messaggio del giorno",
|
||||
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
||||
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled", # TODO: Translate
|
||||
"server-chat-argument": "abilita o disabilita la chat",
|
||||
"server-chat-maxchars-argument": "Numero massimo di caratteri in un messaggio di chat (default è {})", # Default number of characters
|
||||
"server-maxusernamelength-argument": "Numero massimo di caratteri in un nome utente (default è {})",
|
||||
|
||||
@ -58,7 +58,7 @@ pt_BR = {
|
||||
"file-difference-duration": "duração",
|
||||
"alone-in-the-room": "Você está sozinho na sala",
|
||||
|
||||
"different-filesize-notification": " (o tamanho do arquivo deles é diferente do seu!)",
|
||||
"different-filesize-notification": " (o tamanho do arquivo dele(s) é diferente do seu!)",
|
||||
"userlist-playing-notification": "{} está tocando:", # Username
|
||||
"file-played-by-notification": "Arquivo: {} está sendo tocado por:", # File
|
||||
"no-file-played-notification": "{} não está tocando um arquivo", # Username
|
||||
@ -68,7 +68,7 @@ pt_BR = {
|
||||
"controller-userlist-userflag": "Operador",
|
||||
"ready-userlist-userflag": "Pronto",
|
||||
|
||||
"update-check-failed-notification": "Não foi possível verificar automaticamente se o Syncplay {} é a versão mais recente. Deseja visitar https://syncplay.pl/ para checar manualmente por atualizações?", # Syncplay version
|
||||
"update-check-failed-notification": "Não foi possível verificar automaticamente se o Syncplay {} é a versão mais recente. Deseja visitar https://syncplay.pl/ para checar manualmente por atualizações?", # Syncplay version
|
||||
"syncplay-uptodate-notification": "O Syncplay está atualizado",
|
||||
"syncplay-updateavailable-notification": "Uma nova versão do Syncplay está disponível. Deseja visitar a página de lançamentos?",
|
||||
|
||||
@ -82,22 +82,24 @@ pt_BR = {
|
||||
"commandlist-notification/list": "\tl - mostra lista de usuários",
|
||||
"commandlist-notification/undo": "\tu - desfaz último salto",
|
||||
"commandlist-notification/pause": "\tp - alterna pausa",
|
||||
"commandlist-notification/seek": "\t[s][+-]time - salta para o valor de tempo dado, se + ou - não forem especificados, será o tempo absoluto em segundos ou minutos:segundos",
|
||||
"commandlist-notification/seek": "\t[s][+-]tempo - salta para o valor de tempo fornecido, se + ou - não forem especificados, será o tempo absoluto em segundos ou minutos:segundos",
|
||||
"commandlist-notification/offset": "\to[+-]duração - Desloca a reprodução local pelo valor de duração fornecido (em segundos ou min:seg) em relação à posição de tepmo do servidor - essa é uma função descontinuada",
|
||||
"commandlist-notification/help": "\th - esta mensagem de ajuda",
|
||||
"commandlist-notification/toggle": "\tt - alterna o seu status de prontidão para assistir",
|
||||
"commandlist-notification/create": "\tc [nome] - cria sala gerenciado usando o nome da sala atual",
|
||||
"commandlist-notification/create": "\tc [nome] - cria uma sala gerenciada usando o nome da sala atual",
|
||||
"commandlist-notification/auth": "\ta [senha] - autentica-se como operador da sala com a senha",
|
||||
"commandlist-notification/chat": "\tch [mensagem] - envia uma mensagem no chat da sala",
|
||||
"commandList-notification/queue": "\tqa [file/url] - adiciona arquivo ou URL para o final da playlist",
|
||||
"commandList-notification/queue": "\tqa [arquivo/url] - adiciona um arquivo ou URL para o final da playlist",
|
||||
"commandList-notification/queueandselect": "\tqas [arquivo/url] - adiciona um arquivo ou URL para o final da playlist e o seleciona",
|
||||
"commandList-notification/playlist": "\tql - mostra a playlist atual",
|
||||
"commandList-notification/select": "\tqs [index] - seleciona uma dada entrada na playlist",
|
||||
"commandList-notification/delete": "\tqd [index] - deleta uma dada entrada na playlist",
|
||||
"commandList-notification/select": "\tqs [índice] - seleciona um item da playlist",
|
||||
"commandList-notification/delete": "\tqd [index] - remove um item da playlist",
|
||||
"syncplay-version-notification": "Versão do Syncplay: {}", # syncplay.version
|
||||
"more-info-notification": "Mais informações disponíveis em: {}", # projectURL
|
||||
|
||||
"gui-data-cleared-notification": "O Syncplay limpou o caminho e o estado de dados da janela usados pela GUI.",
|
||||
"language-changed-msgbox-label": "O idioma será alterado quando você salvar as mudanças e abrir o Syncplay novamente.",
|
||||
"promptforupdate-label": "O Syncplay pode verificar automaticamente por atualizações de tempos em tempos?",
|
||||
"promptforupdate-label": "Permite o Syncplay verificar automaticamente por atualizações de tempos em tempos?",
|
||||
|
||||
"media-player-latency-warning": "Aviso: O reprodutor de mídia demorou {} para responder. Se você tiver problemas de sincronização, feche outros programas para liberar recursos do sistema e, se isso não funcionar, tente outro reprodutor de mídia.", # Seconds to respond
|
||||
"mpv-unresponsive-error": "O mpv não respondeu por {} segundos, portanto parece que não está funcionando. Por favor, reinicie o Syncplay.", # Seconds to respond
|
||||
@ -115,29 +117,29 @@ pt_BR = {
|
||||
"mpv-failed-advice": "O motivo pelo qual o mpv não pode ser iniciado pode ser devido ao uso de argumentos da linha de comando não suportados ou a uma versão não suportada do mpv.",
|
||||
"player-file-open-error": "O reprodutor falhou ao abrir o arquivo",
|
||||
"player-path-error": "O caminho até o arquivo executável do reprodutor não está configurado corretamente. Os reprodutores suportados são: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2 e IINA",
|
||||
"hostname-empty-error": "O endereço do servidor não pode ser vazio",
|
||||
"empty-error": "{} não pode ser vazio", # Configuration
|
||||
"hostname-empty-error": "O endereço do servidor não pode estar vazio",
|
||||
"empty-error": "{} não pode estar vazio", # Configuration
|
||||
"media-player-error": "Erro do reprodutor de mídia: \"{}\"", # Error line
|
||||
"unable-import-gui-error": "Não foi possível importar bibliotecas da GUI. Se você não possuir o PySide instalado, instale-o para que a GUI funcione.",
|
||||
"unable-import-twisted-error": "Não foi possível importar o Twisted. Por favor, instale o Twisted v16.4.0 ou superior.",
|
||||
|
||||
"arguments-missing-error": "Alguns argumentos necessários estão faltando, por favor reveja --help",
|
||||
|
||||
"unable-to-start-client-error": "Não foi possível iniciar o client",
|
||||
"unable-to-start-client-error": "Não foi possível iniciar o cliente",
|
||||
|
||||
"player-path-config-error": "O caminho até o arquivo executável do reprodutor não está configurado corretamente. Os reprodutores suportados são: mpv, mpv.net, VLC, MPC-HC, MPC-BE, mplayer2 e IINA.",
|
||||
"no-file-path-config-error": "O arquivo deve ser selecionado antes de iniciar seu reprodutor",
|
||||
"no-hostname-config-error": "O endereço do servidor não pode ser vazio",
|
||||
"no-hostname-config-error": "O endereço do servidor não pode estar vazio",
|
||||
"invalid-port-config-error": "A porta deve ser válida",
|
||||
"empty-value-config-error": "{} não pode ser vazio", # Config option
|
||||
"empty-value-config-error": "{} não pode estar vazio", # Config option
|
||||
|
||||
"not-json-error": "Não é uma string codificada como JSON\n",
|
||||
"hello-arguments-error": "Not enough Hello arguments\n", # DO NOT TRANSLATE
|
||||
"version-mismatch-error": "Discrepância entre versões do client e do servidor\n",
|
||||
"vlc-failed-connection": "Falha ao conectar ao VLC. Se você não instalou o syncplay.lua e está usando a versão mais recente do VLC, por favor veja https://syncplay.pl/LUA/ para mais instruções. Syncplay e VLC 4 são atualmente incompatíveis, portanto ou use VLC 3 ou use outro reprodutor, como o mpv.",
|
||||
"vlc-failed-connection": "Falha ao conectar ao VLC. Se você não instalou o syncplay.lua e está usando a versão mais recente do VLC, por favor veja https://syncplay.pl/LUA/ para mais instruções. Syncplay e VLC 4 são atualmente incompatíveis, portanto use VLC 3 ou use outro reprodutor, como o mpv.",
|
||||
"vlc-failed-noscript": "O VLC reportou que a interface de script do syncplay.lua não foi instalada. Por favor, veja https://syncplay.pl/LUA/ para mais instruções.",
|
||||
"vlc-failed-versioncheck": "Esta versão do VLC não é suportada pelo Syncplay.",
|
||||
"vlc-initial-warning": 'O VLC nem sempre fornece informações precisas de posição para o Syncplay, especialmente para arquivos .mp4 ou .avi. Se você experienciar problemas com busca (seeking) incorreta, por favor, tente um reprodutor de mídia alternativo, como o <a href="https://mpv.io/">mpv</a> (ou <a href="https://github.com/stax76/mpv.net/">mpv.net</a>, uma alternativa mais simples do mpv a usuários Windows).',
|
||||
"vlc-initial-warning": 'O VLC nem sempre fornece informações precisas de posição para o Syncplay, especialmente para arquivos .mp4 ou .avi. Se você experienciar problemas com busca (seeking) incorreta, por favor, tente um reprodutor de mídia alternativo, como o <a href="https://mpv.io/">mpv</a> (ou <a href="https://github.com/stax76/mpv.net/">mpv.net</a>, uma alternativa mais simples do mpv a usuários de Windows).',
|
||||
|
||||
"feature-sharedPlaylists": "playlists compartilhadas", # used for not-supported-by-server-error
|
||||
"feature-chat": "chat", # used for not-supported-by-server-error
|
||||
@ -175,7 +177,7 @@ pt_BR = {
|
||||
"file-argument": 'arquivo a ser tocado',
|
||||
"args-argument": 'opções do reprodutor; se você precisar passar opções começando com -, as preceda com um único argumento \'--\'',
|
||||
"clear-gui-data-argument": 'redefine o caminho e o estado de dados da janela da GUI para as de QSettings',
|
||||
"language-argument": 'idioma para mensagens do Syncplay (de/en/ru/it/es/pt_BR/pt_PT/tr)',
|
||||
"language-argument": 'idioma para mensagens do Syncplay (de/en/ru/it/es/pt_BR/pt_PT/tr/fr/zh_CN)',
|
||||
|
||||
"version-argument": 'exibe sua versão',
|
||||
"version-message": "Você está usando o Syncplay versão {} ({})",
|
||||
@ -208,7 +210,7 @@ pt_BR = {
|
||||
"filename-privacy-label": "Informação do nome do arquivo:",
|
||||
"filesize-privacy-label": "Informação do tamanho do arquivo:",
|
||||
"checkforupdatesautomatically-label": "Verificar atualizações do Syncplay automaticamente",
|
||||
"autosavejoinstolist-label": "Adicionar salas que você entra para a lista de salas",
|
||||
"autosavejoinstolist-label": "Adicionar salas que você visitou para a sua lista de salas",
|
||||
"slowondesync-label": "Diminuir velocidade em dessincronizações menores (não suportado pelo MPC-HC/BE)",
|
||||
"rewindondesync-label": "Retroceder em dessincronização maiores (recomendado)",
|
||||
"fastforwardondesync-label": "Avançar se estiver ficando para trás (recomendado)",
|
||||
@ -229,13 +231,13 @@ pt_BR = {
|
||||
"automatic-language": "Padrão ({})", # Default language
|
||||
"showdurationnotification-label": "Avisar sobre discrepância nas durações dos arquivos de mídia",
|
||||
"basics-label": "Básicos",
|
||||
"readiness-label": "Play/Pause",
|
||||
"readiness-label": "Tocar/Pausar",
|
||||
"misc-label": "Miscelânea",
|
||||
"core-behaviour-title": "Comportamento da sala padrão",
|
||||
"core-behaviour-title": "Comportamento principal da sala",
|
||||
"syncplay-internals-title": "Configurações internas do Syncplay",
|
||||
"syncplay-mediasearchdirectories-title": "Diretórios a buscar por mídias",
|
||||
"syncplay-mediasearchdirectories-label": "Diretórios a buscar por mídias (um caminho por linha)",
|
||||
"sync-label": "Sincronizar",
|
||||
"sync-label": "Sincronização",
|
||||
"sync-otherslagging-title": "Se outros estiverem ficando pra trás...",
|
||||
"sync-youlaggging-title": "Se você estiver ficando pra trás...",
|
||||
"messages-label": "Mensagens",
|
||||
@ -251,21 +253,21 @@ pt_BR = {
|
||||
"unpause-always": "Sempre despausar",
|
||||
"syncplay-trusteddomains-title": "Domínios confiáveis (para serviços de streaming e conteúdo hospedado)",
|
||||
|
||||
"chat-title": "Entrada de mensagem do chat",
|
||||
"chatinputenabled-label": "Habilitar entrada de chat via mpv",
|
||||
"chatdirectinput-label": "Permitir entrada instantânea de chat (evita ter de apertar Enter para abrir o chat)",
|
||||
"chatinputfont-label": "Fonte da entrada de chat",
|
||||
"chat-title": "Digitação de mensagem no chat",
|
||||
"chatinputenabled-label": "Habilitar digitação no chat via mpv",
|
||||
"chatdirectinput-label": "Permitir digitação instantânea no chat (evita ter de apertar Enter para abrir o chat)",
|
||||
"chatinputfont-label": "Fonte da digitação no chat",
|
||||
"chatfont-label": "Definir fonte",
|
||||
"chatcolour-label": "Definir cor",
|
||||
"chatinputposition-label": "Posição da área de entrada de mensagens no mpv",
|
||||
"chatinputposition-label": "Posição da área de digitação de mensagens no mpv",
|
||||
"chat-top-option": "Topo",
|
||||
"chat-middle-option": "Meio",
|
||||
"chat-bottom-option": "Fundo",
|
||||
"chat-bottom-option": "Baixo",
|
||||
"chatoutputheader-label": "Saída de mensagem do chat",
|
||||
"chatoutputfont-label": "Fonte da saída de chat",
|
||||
"chatoutputenabled-label": "Habilitar saída de chat no reprodutor de mídia (apenas mpv por enquanto)",
|
||||
"chatoutputenabled-label": "Habilitar saída de chat no reprodutor de mídia (apenas no mpv por enquanto)",
|
||||
"chatoutputposition-label": "Modo de saída",
|
||||
"chat-chatroom-option": "Estilo sala de bate-papo",
|
||||
"chat-chatroom-option": "Estilo da sala de bate-papo",
|
||||
"chat-scrolling-option": "Estilo de rolagem",
|
||||
|
||||
"mpv-key-tab-hint": "[TAB] para alternar acesso instantâneo ao chat.",
|
||||
@ -278,18 +280,19 @@ pt_BR = {
|
||||
"run-label": "Começar Syncplay",
|
||||
"storeandrun-label": "Salvar mudanças e começar Syncplay",
|
||||
|
||||
"contact-label": "Sinta-se livre para mandar um e-mail para <a href=\"mailto:dev@syncplay.pl\"><nobr>dev@syncplay.pl</nobr></a>, <a href=\"https://github.com/Syncplay/syncplay/issues\"><nobr>abrir uma issue</nobr></a> pelo GitHub / <a href=\"https://github.com/Syncplay/syncplay/discussions\"><nobr>start a discussion</nobr></a> to make a suggestion or ask a question via GitHub,, <a href=\"https://www.facebook.com/SyncplaySoftware\"><nobr>curtir nossa página no Facebook</nobr></a>, <a href=\"https://twitter.com/Syncplay/\"><nobr>nos seguir no Twitter</nobr></a> ou visitar <a href=\"https://syncplay.pl/\"><nobr>https://syncplay.pl/</nobr></a>. Não use o Syncplay para mandar informações sensíveis/confidenciais.", # TODO: Update translation
|
||||
|
||||
"contact-label": "Sinta-se livre para mandar um e-mail para <a href=\"mailto:dev@syncplay.pl\"><nobr>dev@syncplay.pl</nobr></a>, <a href=\"https://github.com/Syncplay/syncplay/issues\"><nobr>abrir uma issue</nobr></a> para reportar um bug/problema via GitHub, <a href=\"https://github.com/Syncplay/syncplay/discussions\"><nobr>começar uma discussão</nobr></a> para fazer uma sugestão ou uma pergunta via GitHub, <a href=\"https://www.facebook.com/SyncplaySoftware\"><nobr>curtir nossa página no Facebook</nobr></a>, <a href=\"https://twitter.com/Syncplay/\"><nobr>nos seguir no Twitter</nobr></a>, ou visitar <a href=\"https://syncplay.pl/\"><nobr>https://syncplay.pl/</nobr></a>. Não use o Syncplay para mandar informações sensíveis/confidenciais.",
|
||||
|
||||
"joinroom-label": "Juntar-se a uma sala",
|
||||
"joinroom-menu-label": "Juntar-se à sala {}",
|
||||
"seektime-menu-label": "Saltar para o tempo",
|
||||
"undoseek-menu-label": "Desfazer salto",
|
||||
"play-menu-label": "Play",
|
||||
"pause-menu-label": "Pause",
|
||||
"play-menu-label": "Tocar",
|
||||
"pause-menu-label": "Pausar",
|
||||
"playbackbuttons-menu-label": "Mostrar botões de reprodução",
|
||||
"autoplay-menu-label": "Mostrar botão de reprodução automática",
|
||||
"autoplay-guipushbuttonlabel": "Tocar quando todos estiverem prontos",
|
||||
"autoplay-minimum-label": "Mín. de usuários:",
|
||||
"hideemptyrooms-menu-label": "Esconder salas persistentes vazias",
|
||||
|
||||
"sendmessage-label": "Enviar",
|
||||
|
||||
@ -357,7 +360,7 @@ pt_BR = {
|
||||
"offsetinfo-msgbox-label": "Deslocamento (veja https://syncplay.pl/guide/ para instruções de uso):",
|
||||
|
||||
"promptforstreamurl-msgbox-label": "Abrir transmissão de mídia via URL",
|
||||
"promptforstreamurlinfo-msgbox-label": "Transmitir URL",
|
||||
"promptforstreamurlinfo-msgbox-label": "URL da transmissão",
|
||||
|
||||
"addfolder-label": "Adicionar pasta",
|
||||
|
||||
@ -435,7 +438,7 @@ pt_BR = {
|
||||
"chatoutputmode-chatroom-tooltip": "Exibe novas linhas de chat diretamente abaixo da linha anterior.",
|
||||
"chatoutputmode-scrolling-tooltip": "Exibe novas linhas de chat rolando-as da direita pra esquerda.",
|
||||
|
||||
"help-tooltip": "Abre o guia de usuário do Syncplay.pl.",
|
||||
"help-tooltip": "Abre o guia de usuário no Syncplay.pl.",
|
||||
"reset-tooltip": "Redefine todas as configurações para seus respectivos padrões.",
|
||||
"update-server-list-tooltip": "Conecta ao syncplay.pl para atualizar a lista de servidores públicos.",
|
||||
|
||||
@ -456,6 +459,7 @@ pt_BR = {
|
||||
|
||||
# Server messages to client
|
||||
"new-syncplay-available-motd-message": "Você está usando o Syncplay {}, mas uma versão mais nova está disponível em https://syncplay.pl", # ClientVersion
|
||||
"persistent-rooms-notice": "AVISO: Este servidor usa salas persistentes, o que significa que as informações da playlist são salvas entre sessões de reprodução. Se você quer criar uma sala onde a informação não será salva, coloque -temp no final do nome da sala.", # NOTE: Do not translate the word -temp
|
||||
|
||||
# Server notifications
|
||||
"welcome-server-notification": "Seja bem-vindo ao servidor de Syncplay, versão {0}", # version
|
||||
@ -465,7 +469,7 @@ pt_BR = {
|
||||
|
||||
|
||||
# Server arguments
|
||||
"server-argument-description": 'Solução para sincronizar a reprodução de múltiplas instâncias de MPlayer e MPC-HC/BE pela rede. Instância de servidor',
|
||||
"server-argument-description": 'Solução para sincronizar a reprodução de múltiplas instâncias de players de mídia pela rede. Instância de servidor',
|
||||
"server-argument-epilog": 'Se nenhuma opção for fornecida, os valores de _config serão utilizados',
|
||||
"server-port-argument": 'porta TCP do servidor',
|
||||
"server-password-argument": 'senha do servidor',
|
||||
@ -473,6 +477,8 @@ pt_BR = {
|
||||
"server-salt-argument": "string aleatória utilizada para gerar senhas de salas gerenciadas",
|
||||
"server-disable-ready-argument": "desativar recurso de prontidão",
|
||||
"server-motd-argument": "caminho para o arquivo o qual o motd será obtido",
|
||||
"server-rooms-argument": "caminho para o arquivo de banco de dádos a ser usado e/ou criado para salvar dados de salas persistentes. Permite que salas persistam sem espectadores e através de reinicializações.",
|
||||
"server-permanent-rooms-argument": "caminho para o arquivo que lista salas permanentes que serão listadas mesmo que a sala esteja vazia (na forma de um arquivo de texto que lista uma sala por linha) - requer salas persistentes ativada",
|
||||
"server-chat-argument": "O chat deve ser desativado?",
|
||||
"server-chat-maxchars-argument": "Número máximo de caracteres numa mensagem do chat (o padrão é {})", # Default number of characters
|
||||
"server-maxusernamelength-argument": "Número máximos de caracteres num nome de usuário (o padrão é {})",
|
||||
@ -486,7 +492,7 @@ pt_BR = {
|
||||
"not-json-server-error": "Não é uma string codificada como json: {}", # message
|
||||
"line-decode-server-error": "Não é uma string UTF-8",
|
||||
"not-known-server-error": "Você deve ser conhecido pelo servidor antes de mandar este comando",
|
||||
"client-drop-server-error": "Drop do client: {} -- {}", # host, error
|
||||
"client-drop-server-error": "Cliente desconectado: {} -- {}", # host, error
|
||||
"password-required-server-error": "Senha necessária",
|
||||
"wrong-password-server-error": "Senha incorreta fornecida",
|
||||
"hello-server-error": "Not enough Hello arguments", # DO NOT TRANSLATE
|
||||
|
||||
@ -82,12 +82,14 @@ pt_PT = {
|
||||
"commandlist-notification/undo": "\tu - desfaz último salto",
|
||||
"commandlist-notification/pause": "\tp - alterna pausa",
|
||||
"commandlist-notification/seek": "\t[s][+-]time - salta para o valor de tempo dado, se + ou - não forem especificados, será o tempo absoluto em segundos ou minutos:segundos",
|
||||
"commandlist-notification/offset": "\to[+-]duration - offset local playback by the given duration (in seconds or min:sec) from the server seek position - this is a deprecated feature", # TODO: Translate
|
||||
"commandlist-notification/help": "\th - esta mensagem de ajuda",
|
||||
"commandlist-notification/toggle": "\tt - alterna o seu status de prontidão para assistir",
|
||||
"commandlist-notification/create": "\tc [nome] - cria sala gerenciada usando o nome da sala atual",
|
||||
"commandlist-notification/auth": "\ta [senha] - autentica-se como administrador da sala com a senha",
|
||||
"commandlist-notification/chat": "\tch [mensagem] - envia uma mensagem no chat da sala",
|
||||
"commandList-notification/queue": "\tqa [file/url] - add file or url to bottom of playlist", # TO DO: Translate
|
||||
"commandList-notification/queueandselect": "\tqas [file/url] - add file or url to bottom of playlist and select it", # TO DO: Translate
|
||||
"commandList-notification/playlist": "\tql - show the current playlist", # TO DO: Translate
|
||||
"commandList-notification/select": "\tqs [index] - select given entry in the playlist", # TO DO: Translate
|
||||
"commandList-notification/delete": "\tqd [index] - delete the given entry from the playlist", # TO DO: Translate
|
||||
@ -174,7 +176,7 @@ pt_PT = {
|
||||
"file-argument": 'ficheiro a ser tocado',
|
||||
"args-argument": 'opções do reprodutor; se você precisar passar opções começando com -, as preceda com um único argumento \'--\'',
|
||||
"clear-gui-data-argument": 'redefine o caminho e o estado de dados da janela da GUI para as de QSettings',
|
||||
"language-argument": 'idioma para mensagens do Syncplay (de/en/ru/it/es/pt_BR/pt_PT/tr)',
|
||||
"language-argument": 'idioma para mensagens do Syncplay (de/en/ru/it/es/pt_BR/pt_PT/tr/fr/zh_CN)',
|
||||
|
||||
"version-argument": 'exibe sua versão',
|
||||
"version-message": "Você está usando o Syncplay versão {} ({})",
|
||||
@ -288,6 +290,7 @@ pt_PT = {
|
||||
"autoplay-menu-label": "Mostrar botão de reprodução automática",
|
||||
"autoplay-guipushbuttonlabel": "Tocar quando todos estiverem prontos",
|
||||
"autoplay-minimum-label": "Mín. de utilizadores:",
|
||||
"hideemptyrooms-menu-label": "Hide empty persistent rooms", # TODO: Translate
|
||||
|
||||
"sendmessage-label": "Enviar",
|
||||
|
||||
@ -455,6 +458,7 @@ pt_PT = {
|
||||
|
||||
# Server messages to client
|
||||
"new-syncplay-available-motd-message": "Você está usando o Syncplay {}, mas uma versão mais nova está disponível em https://syncplay.pl", # ClientVersion
|
||||
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # TO DO: Translate - NOTE: Do not translate the word -temp
|
||||
|
||||
# Server notifications
|
||||
"welcome-server-notification": "Seja bem-vindo ao servidor de Syncplay, versão {0}", # version
|
||||
@ -472,6 +476,8 @@ pt_PT = {
|
||||
"server-salt-argument": "string aleatória utilizada para gerar senhas de salas gerenciadas",
|
||||
"server-disable-ready-argument": "desativar recurso de prontidão",
|
||||
"server-motd-argument": "caminho para o arquivo o qual o motd será obtido",
|
||||
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
||||
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled", # TODO: Translate
|
||||
"server-chat-argument": "O chat deve ser desativado?",
|
||||
"server-chat-maxchars-argument": "Número máximo de caracteres numa mensagem do chat (o padrão é {})", # Default number of characters
|
||||
"server-maxusernamelength-argument": "Número máximos de caracteres num nome de utilizador (o padrão é {})",
|
||||
|
||||
@ -81,12 +81,14 @@ ru = {
|
||||
"commandlist-notification/undo": "\tu - отменить последнюю перемотку",
|
||||
"commandlist-notification/pause": "\tp - вкл./выкл. паузу",
|
||||
"commandlist-notification/seek": "\t[s][+-]time - перемотать к заданному моменту времени, если не указан + или -, то время считается абсолютным (от начала файла) в секундах или мин:сек",
|
||||
"commandlist-notification/offset": "\to[+-]duration - offset local playback by the given duration (in seconds or min:sec) from the server seek position - this is a deprecated feature", # TODO: Translate
|
||||
"commandlist-notification/help": "\th - помощь",
|
||||
"commandlist-notification/toggle": "\tt - переключить статус готов/не готов к просмотру",
|
||||
"commandlist-notification/create": "\tc [имя комнаты] - создать управляемую комнату с таким же именем, как у текущей",
|
||||
"commandlist-notification/auth": "\ta [пароль] - авторизоваться как оператор комнаты с помощью пароля",
|
||||
"commandlist-notification/chat": "\tch [сообщение] - выслать сообщение в комнату",
|
||||
"commandList-notification/queue": "\tqa [файл/URL] - добавить файл или URL в конец списка воспроизведения",
|
||||
"commandList-notification/queueandselect": "\tqas [file/url] - add file or url to bottom of playlist and select it", # TO DO: Translate
|
||||
"commandList-notification/playlist": "\tql - показать текущий список воспроизведения",
|
||||
"commandList-notification/select": "\tqs [индекс] - выделить указанный пункт в списке воспроизведения",
|
||||
"commandList-notification/delete": "\tqd [индекс] - удалить указанный пункт из списка воспроизведения",
|
||||
@ -173,7 +175,7 @@ ru = {
|
||||
"file-argument": 'воспроизводимый файл',
|
||||
"args-argument": 'параметры проигрывателя; если нужно передать параметры, начинающиеся с - , то сначала пишите \'--\'',
|
||||
"clear-gui-data-argument": 'сбрасывает путь и данные о состоянии окна GUI, хранимые как QSettings',
|
||||
"language-argument": 'язык сообщений Syncplay (de/en/ru/it/es/pt_BR/pt_PT/tr)',
|
||||
"language-argument": 'язык сообщений Syncplay (de/en/ru/it/es/pt_BR/pt_PT/tr/fr/zh_CN)',
|
||||
|
||||
"version-argument": 'выводит номер версии',
|
||||
"version-message": "Вы используете Syncplay версии {} ({})",
|
||||
@ -289,6 +291,7 @@ ru = {
|
||||
"autoplay-menu-label": "Показывать кнопку &автовоспроизведения",
|
||||
"autoplay-guipushbuttonlabel": "Стартовать, когда все будут готовы",
|
||||
"autoplay-minimum-label": "Минимум зрителей:",
|
||||
"hideemptyrooms-menu-label": "Hide empty persistent rooms", # TODO: Translate
|
||||
"sendmessage-label": "Выслать",
|
||||
|
||||
"ready-guipushbuttonlabel": "Я готов",
|
||||
@ -453,6 +456,7 @@ ru = {
|
||||
|
||||
# Server messages to client
|
||||
"new-syncplay-available-motd-message": "Вы используете Syncplay версии {}. Доступна более новая версия на https://syncplay.pl/", # ClientVersion
|
||||
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # TO DO: Translate - NOTE: Do not translate the word -temp
|
||||
|
||||
# Server notifications
|
||||
"welcome-server-notification": "Добро пожаловать на сервер Syncplay версии {0}", # version
|
||||
@ -469,6 +473,8 @@ ru = {
|
||||
"server-salt-argument": "генерировать пароли к управляемым комнатам на основании указанной строки (соли)",
|
||||
"server-disable-ready-argument": "отключить статусы готов/не готов",
|
||||
"server-motd-argument": "путь к файлу, из которого будет извлекаться MOTD-сообщение",
|
||||
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
||||
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled", # TODO: Translate
|
||||
"server-chat-argument": "Должен ли чат быть отключён?",
|
||||
"server-chat-maxchars-argument": "Максимальное число символов в сообщениях в чате (по умолчанию {})",
|
||||
"server-maxusernamelength-argument": "Максимальное число символов в именах пользователей (по умолчанию {})",
|
||||
|
||||
@ -82,12 +82,14 @@ tr = {
|
||||
"commandlist-notification/undo": "\tu - son isteği geri alır",
|
||||
"commandlist-notification/pause": "\tp - duraklatmayı değiştirir",
|
||||
"commandlist-notification/seek": "\t[s][+-]time - verilen zaman değerine atlar, eğer + veya - belirtilmezse, saniye:dakika cinsinden mutlak zamandır.",
|
||||
"commandlist-notification/offset": "\to[+-]duration - offset local playback by the given duration (in seconds or min:sec) from the server seek position - this is a deprecated feature", # TODO: Translate
|
||||
"commandlist-notification/help": "\th - yardım",
|
||||
"commandlist-notification/toggle": "\tt - izlemeye hazır olup olmadığınızı değiştirir",
|
||||
"commandlist-notification/create": "\tc [name] - mevcut odanın adını kullanarak yönetilen oda oluştur",
|
||||
"commandlist-notification/auth": "\ta [password] - operatör şifresi ile oda operatörü olarak kimlik doğrular",
|
||||
"commandlist-notification/chat": "\tch [message] - bir odaya sohbet mesajı gönderir",
|
||||
"commandList-notification/queue": "\tqa [file/url] - oynatma listesinin altına dosya veya bağlantı ekler",
|
||||
"commandList-notification/queueandselect": "\tqas [file/url] - add file or url to bottom of playlist and select it", # TO DO: Translate
|
||||
"commandList-notification/playlist": "\tql - mevcut oynatma listesini gösterir",
|
||||
"commandList-notification/select": "\tqs [index] - oynatma listesinde verilen girişi seçer",
|
||||
"commandList-notification/delete": "\tqd [index] - verilen girişi oynatma listesinden siler",
|
||||
@ -174,7 +176,7 @@ tr = {
|
||||
"file-argument": 'oynatmak için dosya',
|
||||
"args-argument": 'oynatıcı seçenekleri, ile başlayan seçenekleri iletmeniz gerekiyorsa - bunların başına tek \'--\' argümanı ekleyin',
|
||||
"clear-gui-data-argument": 'QSettings olarak depolanan yol ve pencere durumu GUI verilerini sıfırlar',
|
||||
"language-argument": 'Syncplay mesajları için dil (de/en/ru/it/es/pt_BR/pt_PT/tr)',
|
||||
"language-argument": 'Syncplay mesajları için dil (de/en/ru/it/es/pt_BR/pt_PT/tr/fr/zh_CN)',
|
||||
|
||||
"version-argument": 'versiyonunuzu yazdırır',
|
||||
"version-message": "Syncplay sürümünü kullanıyorsunuz {} ({})",
|
||||
@ -290,6 +292,7 @@ tr = {
|
||||
"autoplay-menu-label": "Otomatik oynat düğmesini göster",
|
||||
"autoplay-guipushbuttonlabel": "Her şey hazır olduğunda oynat",
|
||||
"autoplay-minimum-label": "Asgari kullanıcı:",
|
||||
"hideemptyrooms-menu-label": "Hide empty persistent rooms", # TODO: Translate
|
||||
|
||||
"sendmessage-label": "Gönder",
|
||||
|
||||
@ -456,6 +459,7 @@ tr = {
|
||||
|
||||
# Server messages to client
|
||||
"new-syncplay-available-motd-message": "Syncplay {} kullanıyorsunuz ancak daha yeni bir sürüm https://syncplay.pl adresinde mevcut", # ClientVersion
|
||||
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # TO DO: Translate - NOTE: Do not translate the word -temp
|
||||
|
||||
# Server notifications
|
||||
"welcome-server-notification": "Syncplay sunucusuna hoş geldiniz, ver. {0}", # version
|
||||
@ -473,6 +477,8 @@ tr = {
|
||||
"server-salt-argument": "yönetilen oda şifreleri oluşturmak için kullanılan rastgele dize",
|
||||
"server-disable-ready-argument": "hazır olma özelliğini devre dışı bırak",
|
||||
"server-motd-argument": "motd alınacak dosyanın yolu",
|
||||
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
||||
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled", # TODO: Translate
|
||||
"server-chat-argument": "Sohbet devre dışı bırakılmalı mı?",
|
||||
"server-chat-maxchars-argument": "Bir sohbet mesajındaki maksimum karakter sayısı (varsayılan: {})", # Default number of characters
|
||||
"server-maxusernamelength-argument": "Bir kullanıcı adındaki maksimum karakter sayısı (varsayılan {})",
|
||||
|
||||
528
syncplay/messages_zh_CN.py
Normal file
528
syncplay/messages_zh_CN.py
Normal file
@ -0,0 +1,528 @@
|
||||
# coding:utf8
|
||||
|
||||
"""Simplified Chinese dictionary"""
|
||||
|
||||
zh_CN = {
|
||||
"LANGUAGE": "简体中文",
|
||||
|
||||
# Client notifications
|
||||
"config-cleared-notification": "设置已清除。 当你存储一个有效的配置时,更改将被保存。",
|
||||
|
||||
"relative-config-notification": "已加载的相对配置文件:{}",
|
||||
|
||||
"connection-attempt-notification": "正在尝试连接{}:{}", # Port, IP
|
||||
"reconnection-attempt-notification": "服务器连接中断,正在尝试重连",
|
||||
"disconnection-notification": "与服务器连接中断",
|
||||
"connection-failed-notification": "服务器连接失败",
|
||||
"connected-successful-notification": "服务器连接成功",
|
||||
"retrying-notification": "%s,%d秒内将重试...", # Seconds
|
||||
"reachout-successful-notification": "成功连入{}({})",
|
||||
|
||||
"rewind-notification": "与{}不同步,已回放", # User
|
||||
"fastforward-notification": "与{}不同步,已快进", # User
|
||||
"slowdown-notification": "与{}不同步,正在慢速播放", # User
|
||||
"revert-notification": "播放速度恢复一倍速",
|
||||
|
||||
"pause-notification": "{}暂停了", # User
|
||||
"unpause-notification": "{}重新开始播放", # User
|
||||
"seek-notification": "{}从{}跳转到{}", # User, from time, to time
|
||||
|
||||
"current-offset-notification": "当前偏移量:{}秒", # Offset
|
||||
|
||||
"media-directory-list-updated-notification": "Syncplay的媒体文件目录已更新。",
|
||||
|
||||
"room-join-notification": "{}加入了'{}'房间", # User
|
||||
"left-notification": "{}离开了房间", # User
|
||||
"left-paused-notification": "{}离开了房间,{}暂停了", # User who left, User who paused
|
||||
"playing-notification": "{}正在播放'{}' (时长{})", # User, file, duration
|
||||
"playing-notification/room-addendum": "正在'{}'房间", # Room
|
||||
|
||||
"not-all-ready": "{}还未准备", # Usernames
|
||||
"all-users-ready": "所有人已准备(共{}人)", # Number of ready users
|
||||
"ready-to-unpause-notification": "您已准备,按下播放键可继续播放",
|
||||
"set-as-ready-notification": "您已准备",
|
||||
"set-as-not-ready-notification": "您已取消准备",
|
||||
"autoplaying-notification": "{}秒后将自动开始...", # Number of seconds until playback will start
|
||||
|
||||
"identifying-as-controller-notification": "用密码'{}'认证为管理员...",
|
||||
"failed-to-identify-as-controller-notification": "{}未能成为管理员",
|
||||
"authenticated-as-controller-notification": "{}成为了管理员",
|
||||
"created-controlled-room-notification": "已创建房间:'{}' ,密码是'{}'。请保存好以上信息以备用!\n\n只有房主或管理员可以暂停,继续播放,跳转播放,改变播放列表。每个人都与房主或管理员的播放进度同步。\n\n最近观看过的人都可加入'{}'房间,但只有房主或管理员在加入'{}'房间时才会自动认证为管理员。", # RoomName, operatorPassword, roomName, roomName:operatorPassword
|
||||
|
||||
"file-different-notification": "您正在播放的视频与{}不一致", # User
|
||||
"file-differences-notification": "你们播放的视频文件{}不一致", # Differences
|
||||
"room-file-differences": "播放文件不同点:{}", # File differences (filename, size, and/or duration)
|
||||
"file-difference-filename": "名称",
|
||||
"file-difference-filesize": "大小",
|
||||
"file-difference-duration": "时长",
|
||||
"alone-in-the-room": "您正在一人观看",
|
||||
|
||||
"different-filesize-notification": "(其他人的视频文件大小和您的不一致!)",
|
||||
"userlist-playing-notification": "{}正在播放:", # Username
|
||||
"file-played-by-notification": "{}由以下用户播放:", # File
|
||||
"no-file-played-notification": "{}无视频供开始播放", # Username
|
||||
"notplaying-notification": "以下用户没有视频供开始播放:",
|
||||
"userlist-room-notification": "房间'{}'里面有:", # Room
|
||||
"userlist-file-notification": "视频文件",
|
||||
"controller-userlist-userflag": "管理员",
|
||||
"ready-userlist-userflag": "已准备",
|
||||
|
||||
"update-check-failed-notification": "无法自动检测Syncplay{}版本是否最新。是否前往https://syncplay.pl/手动更新?", # Syncplay version
|
||||
"syncplay-uptodate-notification": "Syncplay版本已最新",
|
||||
"syncplay-updateavailable-notification": "Syncplay已有更新版本。是否前往最新版本页面?",
|
||||
|
||||
"mplayer-file-required-notification": "如使用mplayer播放器,您需要在开始前手动加入视频文件。",
|
||||
"mplayer-file-required-notification/example": "使用示例:syncplay [options] [url|path/]filename",
|
||||
"mplayer2-required": "Syncplay与MPlayer 1.x版本不兼容,请使用mplayer2或mpv(推荐)播放器",
|
||||
|
||||
"unrecognized-command-notification": "未知命令",
|
||||
"commandlist-notification": "您可使用如下命令:",
|
||||
"commandlist-notification/room": "\tr [name] - 切换房间",
|
||||
"commandlist-notification/list": "\tl - 显示房间所有用户",
|
||||
"commandlist-notification/undo": "\tu - 撤销上次跳转",
|
||||
"commandlist-notification/pause": "\tp - 暂停/继续播放",
|
||||
"commandlist-notification/seek": "\t[s][+-]time - 跳转指定时长,如果未输入 + 或 - ,将会跳转到输入的时间节点(时间节点输入格式可为秒或分:秒)",
|
||||
"commandlist-notification/offset": "\to[+-]duration - 将本地的播放偏移量设置为给定时长(输入格式可为秒或分:秒),您的本地播放进度将与服务器不一致,因此极不推荐使用该设置",
|
||||
"commandlist-notification/help": "\th - 帮助菜单",
|
||||
"commandlist-notification/toggle": "\tt - 准备/取消准备",
|
||||
"commandlist-notification/create": "\tc [name] - 以给定房间名创建一个由您管理的房间",
|
||||
"commandlist-notification/auth": "\ta [password] - 用给定密码来认证为管理员",
|
||||
"commandlist-notification/chat": "\tch [message] - 发送聊天信息",
|
||||
"commandList-notification/queue": "\tqa [file/url] - 在播放列表最后添加文件或URL",
|
||||
"commandList-notification/queueandselect": "\tqas [file/url] - 在播放列表最后添加文件或URL并选中",
|
||||
"commandList-notification/playlist": "\tql - 显示当前播放列表",
|
||||
"commandList-notification/select": "\tqs [index] - 选中播放列表中指定序号的视频",
|
||||
"commandList-notification/delete": "\tqd [index] - 删除播放列表中指定序号的视频",
|
||||
"syncplay-version-notification": "当前Syncplay版本为{}", # syncplay.version
|
||||
"more-info-notification": "更多信息请参照{}", # projectURL
|
||||
|
||||
"gui-data-cleared-notification": "Syncplay已经清除了GUI使用的路径和窗口状态数据。",
|
||||
"language-changed-msgbox-label": "当您运行Syncplay时,界面将切换为中文",
|
||||
"promptforupdate-label": "是否让Syncplay自动检查更新?",
|
||||
|
||||
"media-player-latency-warning": "警告:播放器用了{}秒响应。如果您遇到播放不同步问题,请关闭其他应用以清理内存。如果仍无法解决问题,建议您更换其他播放器。", # Seconds to respond
|
||||
"mpv-unresponsive-error": "mpv播放器在{}秒之内无反应,可能出现故障。请尝试重启Syncplay。", # Seconds to respond
|
||||
|
||||
# Client prompts
|
||||
"enter-to-exit-prompt": "Press enter to exit\n",
|
||||
|
||||
# Client errors
|
||||
"missing-arguments-error": "缺少一些必要的参数,使用--help命令查看详细信息",
|
||||
"server-timeout-error": "与服务器的连接超时",
|
||||
"mpc-slave-error": "无法在从属模式下启动MPC!",
|
||||
"mpc-version-insufficient-error": "MPC版本过低,请使用mpc-hc{}以上版本",
|
||||
"mpc-be-version-insufficient-error": "MPC版本过低,请使用mpc-be{}以上版本",
|
||||
"mpv-version-error": "Syncplay与此版本的mpv不兼容。请使用其他版本的mpv(例如:Git HEAD)。",
|
||||
"mpv-failed-advice": "mpv不能启动的原因可能是由于使用了不支持的命令行参数或不支持的mpv版本。",
|
||||
"player-file-open-error": "播放器打开文件失败",
|
||||
"player-path-error": "播放器路径设置不正确。支持的播放器有:mpv、mpv.net、VLC、MPC-HC、MPC-BE、mplayer2和IINA。",
|
||||
"hostname-empty-error": "主机名不能是空的",
|
||||
"empty-error": "{}不能是空的", # Configuration
|
||||
"media-player-error": "媒体播放器错误:\"{}\"", # Error line
|
||||
"unable-import-gui-error": "无法导入GUI库。如果你没有安装PySide,GUI则无法工作,请安装PySide。",
|
||||
"unable-import-twisted-error": "无法导入Twisted。请安装Twisted v16.4.0或更高版本。",
|
||||
|
||||
"arguments-missing-error": "缺少一些必要的参数,使用--help命令查看详细信息",
|
||||
|
||||
"unable-to-start-client-error": "无法启动客户端",
|
||||
|
||||
"player-path-config-error": "播放器路径设置不正确。支持的播放器有:mpv、mpv.net、VLC、MPC-HC、MPC-BE、mplayer2和IINA。",
|
||||
"no-file-path-config-error": "在启动播放器之前,必须选择要播放的文件",
|
||||
"no-hostname-config-error": "主机名不能是空的",
|
||||
"invalid-port-config-error": "端口无效",
|
||||
"empty-value-config-error": "{}不能是空的", # Config option
|
||||
|
||||
"not-json-error": "不是一个json编码的字符串\n",
|
||||
"hello-arguments-error": "Not enough Hello arguments\n", # DO NOT TRANSLATE
|
||||
"version-mismatch-error": "客户端和服务器的版本不匹配\n",
|
||||
"vlc-failed-connection": "连接VLC失败。如果你没有安装syncplay.lua并且使用最新版本的VLC,那么请参考https://syncplay.pl/LUA/。Syncplay和VLC 4目前并不兼容,所以请使用VLC 3或mpv等替代软件。",
|
||||
"vlc-failed-noscript": "VLC报告没有安装syncplay.lua接口脚本。请参考https://syncplay.pl/LUA/获得指导。",
|
||||
"vlc-failed-versioncheck": "Syncplay不支持这个版本的VLC。",
|
||||
"vlc-initial-warning": 'VLC无法给Syncplay提供准确进度信息,尤其是在播放.mp4和.avi文件时。如果您遇到了跳转错误的问题,请使用其他播放器例如<a href="https://mpv.io/">mpv</a>(或者为Windows用户提供的<a href="https://github.com/stax76/mpv.net/">mpv.net</a>)。',
|
||||
|
||||
"feature-sharedPlaylists": "共享播放列表", # used for not-supported-by-server-error
|
||||
"feature-chat": "聊天", # used for not-supported-by-server-error
|
||||
"feature-readiness": "准备状态", # used for not-supported-by-server-error
|
||||
"feature-managedRooms": "房间管理", # used for not-supported-by-server-error
|
||||
|
||||
"not-supported-by-server-error": "该服务器不支持{}功能", # feature
|
||||
"shared-playlists-not-supported-by-server-error": "服务器可能不支持共享播放列表的功能。为确保其正常运行,需要一个运行Syncplay {}以上版本的服务器,但服务器是运行Syncplay {}的。", # minVersion, serverVersion
|
||||
"shared-playlists-disabled-by-server-error": "在服务器配置中,共享播放列表功能已被禁用。要使用这一功能,你需要连接到其他服务器。",
|
||||
|
||||
"invalid-seek-value": "无效的跳转值",
|
||||
"invalid-offset-value": "无效的偏移值",
|
||||
|
||||
"switch-file-not-found-error": "无法切换到视频文件'{0}'。Syncplay只在指定的媒体目录中查找文件。", # File not found
|
||||
"folder-search-timeout-error": "在媒体目录中搜索媒体文件时,由于在'{}'中搜索时间过长而中止。如果你在要搜索的媒体文件夹列表中选择了一个子文件夹过多的文件夹,就会出现这种情况。为了使自动文件切换再次工作,请在菜单栏中选择\"文件->设置媒体目录\",并删除这个目录或用一个适当的子文件夹替换它。如果这个文件夹实际上是适当的,那么你可以通过选择\"文件->设置媒体目录\"并按\"确定\"来重新启用它。", # Folder
|
||||
"folder-search-first-file-timeout-error": "搜索'{}'中的媒体文件时,由于访问该目录的时间太长而被中止。如果该目录位于网络驱动器,或者如果你配置了你的驱动器在一段时间不活动后停止工作,就会发生这种情况。为了能够自动切换播放文件,请进入\"文件->设置媒体目录\",并删除该目录或解决这个问题(还可尝试其他解决方式,例如,改变省电设置)。", # Folder
|
||||
"added-file-not-in-media-directory-error": "你在'{}'中加载了一个文件,该媒体目录尚未保存。你可以通过选择菜单栏中的\"文件->设置媒体目录\"将其保存为指定媒体目录。", # Folder
|
||||
"no-media-directories-error": "没有设置媒体目录。为了使共享播放列表和文件切换功能正常工作,请选择\"文件->设置媒体目录\",并指定Syncplay应该在哪里寻找媒体文件。",
|
||||
"cannot-find-directory-error": "无法找到媒体目录'{}'。要更新您的媒体目录列表,请从菜单栏中选择\"文件->设置媒体目录\",并指定Syncplay应该在哪里寻找媒体文件。",
|
||||
|
||||
"failed-to-load-server-list-error": "载入公共服务器列表失败。请在您的浏览器中访问https://www.syncplay.pl/以获得更多信息。",
|
||||
|
||||
# Client arguments
|
||||
"argument-description": '通过网络来同步多个媒体播放器的解决方案',
|
||||
"argument-epilog": '如果没有提供可选值,将使用配置文件中的值',
|
||||
"nogui-argument": '不显示GUI',
|
||||
"host-argument": "服务器地址",
|
||||
"name-argument": '用户名',
|
||||
"debug-argument": '调试模式',
|
||||
"force-gui-prompt-argument": '显示配置提示信息',
|
||||
"no-store-argument": "不要将值保存在.syncplay",
|
||||
"room-argument": '默认房间',
|
||||
"password-argument": '服务器密码',
|
||||
"player-path-argument": '播放器路径',
|
||||
"file-argument": '待播放文件',
|
||||
"args-argument": '播放器选项,如果你需要传递以-开头的选项,请在它们前面加上一个\'--\'开头',
|
||||
"clear-gui-data-argument": '重置存储为QSettings的路径和窗口状态GUI数据。',
|
||||
"language-argument": 'Syncplay消息的语言 (de/en/ru/it/es/pt_BR/pt_PT/tr/fr/zh_CN)',
|
||||
|
||||
"version-argument": '显示版本信息',
|
||||
"version-message": "您正在使用Syncplay version {} ({})",
|
||||
|
||||
"load-playlist-from-file-argument": "从文本文件中加载播放列表(每一行代表一个文件)",
|
||||
|
||||
|
||||
# Client labels
|
||||
"config-window-title": "Syncplay设置",
|
||||
|
||||
"connection-group-title": "连接设置",
|
||||
"host-label": "服务器地址: ",
|
||||
"name-label": "用户名(可选):",
|
||||
"password-label": "服务器密码(若需要):",
|
||||
"room-label": "默认房间: ",
|
||||
"roomlist-msgbox-label": "编辑房间列表(每行一个房间)",
|
||||
|
||||
"media-setting-title": "播放器设置",
|
||||
"executable-path-label": "播放器路径:",
|
||||
"media-path-label": "视频文件路径(可选):",
|
||||
"player-arguments-label": "播放器参数(若需要):",
|
||||
"browse-label": "选择...",
|
||||
"update-server-list-label": "更新列表",
|
||||
|
||||
"more-title": "高级设置",
|
||||
"never-rewind-value": "从不",
|
||||
"seconds-suffix": " 秒",
|
||||
"privacy-sendraw-option": "原始数据",
|
||||
"privacy-sendhashed-option": "哈希加密",
|
||||
"privacy-dontsend-option": "不共享",
|
||||
"filename-privacy-label": "是否共享文件名:",
|
||||
"filesize-privacy-label": "是否共享文件大小:",
|
||||
"checkforupdatesautomatically-label": "自动为我检查Syncplay更新信息",
|
||||
"autosavejoinstolist-label": "将加入的房间添加到房间列表中",
|
||||
"slowondesync-label": "进度过快时慢速播放(MPC-HC/BE播放器不支持)",
|
||||
"rewindondesync-label": "进度过快时直接跳转(推荐)",
|
||||
"fastforwardondesync-label": "进度过慢时直接跳转(推荐)",
|
||||
"dontslowdownwithme-label": "进度过慢时不用让其他人与我同步(试验功能)",
|
||||
"pausing-title": "暂停设置",
|
||||
"pauseonleave-label": "当其他人离开时暂停(例如其他人掉线)",
|
||||
"readiness-title": "初始准备状态",
|
||||
"readyatstart-label": "自动将我设置为\"已准备\"状态",
|
||||
"forceguiprompt-label": "不要总是打开Syncplay设置窗口", # (Inverted)
|
||||
"showosd-label": "启用OSD信息",
|
||||
|
||||
"showosdwarnings-label": "包括警告信息(例如,当文件不同时,用户没有准备好)",
|
||||
"showsameroomosd-label": "包括在你的房间中的活动",
|
||||
"shownoncontrollerosd-label": "包括在管理的房间中的非管理员的活动",
|
||||
"showdifferentroomosd-label": "包括其他房间的活动",
|
||||
"showslowdownosd-label": "包括慢速播放/恢复一倍速的通知",
|
||||
"language-label": "语言设置",
|
||||
"automatic-language": "默认({})", # Default language
|
||||
"showdurationnotification-label": "时长不匹配时警告",
|
||||
"basics-label": "基本设置",
|
||||
"readiness-label": "播放/暂停设置",
|
||||
"misc-label": "其他设置",
|
||||
"core-behaviour-title": "主房间行为设置",
|
||||
"syncplay-internals-title": "Syncplay内部设置",
|
||||
"syncplay-mediasearchdirectories-title": "检索媒体文件的目录",
|
||||
"syncplay-mediasearchdirectories-label": "检索媒体文件的目录(每行一个目录)",
|
||||
"sync-label": "同步设置",
|
||||
"sync-otherslagging-title": "如果您播放进度过快...",
|
||||
"sync-youlaggging-title": "如果您播放进度过慢...",
|
||||
"messages-label": "消息设置",
|
||||
"messages-osd-title": "屏幕显示消息设置",
|
||||
"messages-other-title": "其他显示消息设置",
|
||||
"chat-label": "聊天设置",
|
||||
"privacy-label": "隐私设置", # Currently unused, but will be brought back if more space is needed in Misc tab
|
||||
"privacy-title": "隐私设定",
|
||||
"unpause-title": "如果您点击播放按钮,将切换为已准备状态并且:",
|
||||
"unpause-ifalreadyready-option": "继续播放,如果已准备",
|
||||
"unpause-ifothersready-option": "继续播放,如果已准备或者房间中其他人已准备(默认)",
|
||||
"unpause-ifminusersready-option": "继续播放,如果已准备或房间中其他人都已准备且准备人数达到最小开始人数",
|
||||
"unpause-always": "总是继续播放",
|
||||
"syncplay-trusteddomains-title": "受信任的网站(用于流媒体服务和托管内容)",
|
||||
|
||||
"chat-title": "聊天信息输入设置",
|
||||
"chatinputenabled-label": "允许通过mpv播放器进行聊天",
|
||||
"chatdirectinput-label": "允许即时聊天输入(不需要按Enter键发送)",
|
||||
"chatinputfont-label": "聊天输入字体设置",
|
||||
"chatfont-label": "聊天输入字体",
|
||||
"chatcolour-label": "设置字体颜色",
|
||||
"chatinputposition-label": "mpv播放器中聊天输入窗口位置",
|
||||
"chat-top-option": "顶部",
|
||||
"chat-middle-option": "中间",
|
||||
"chat-bottom-option": "底部",
|
||||
"chatoutputheader-label": "聊天显示设置",
|
||||
"chatoutputfont-label": "聊天显示字体",
|
||||
"chatoutputenabled-label": "启用媒体播放器中的聊天显示(目前仅支持mpv播放器)",
|
||||
"chatoutputposition-label": "现实模式",
|
||||
"chat-chatroom-option": "聊天室模式",
|
||||
"chat-scrolling-option": "弹幕模式",
|
||||
|
||||
"mpv-key-tab-hint": "按下[TAB]来切换为字母快捷键模式。",
|
||||
"mpv-key-hint": "按下[ENTER]来发送聊天信息。按下[ESC]来退出聊天模式。",
|
||||
"alphakey-mode-warning-first-line": "你可以暂时使用旧版本mpv绑定的a-z键。",
|
||||
"alphakey-mode-warning-second-line": "按[TAB]返回到Syncplay聊天模式。",
|
||||
|
||||
"help-label": "帮助",
|
||||
"reset-label": "恢复默认设置",
|
||||
"run-label": "运行Syncplay",
|
||||
"storeandrun-label": "保存设置并运行Syncplay",
|
||||
|
||||
"contact-label": "欢迎发送邮件至 <a href=\"mailto:dev@syncplay.pl\"><nobr>dev@syncplay.pl</nobr></a>, 在GitHub上<a href=\"https://github.com/Syncplay/syncplay/issues\"><nobr>创建issue</nobr></a>来反馈漏洞或问题,在GitHub上<a href=\"https://github.com/Syncplay/syncplay/discussions\"><nobr>创建discussion</nobr></a>来提出建议或问题, <a href=\"https://www.facebook.com/SyncplaySoftware\"><nobr>在Facebook上喜欢我们</nobr></a>, <a href=\"https://twitter.com/Syncplay/\"><nobr>在Twitter上关注我们</nobr></a>,或者访问<a href=\"https://syncplay.pl/\"><nobr>https://syncplay.pl/</nobr></a>。请勿使用Syncplay发送敏感信息。",
|
||||
|
||||
"joinroom-label": "加入房间",
|
||||
"joinroom-menu-label": "加入房间 {}",
|
||||
"seektime-menu-label": "跳转到",
|
||||
"undoseek-menu-label": "撤销跳转",
|
||||
"play-menu-label": "播放",
|
||||
"pause-menu-label": "暂停",
|
||||
"playbackbuttons-menu-label": "显示播放控制按钮",
|
||||
"autoplay-menu-label": "显示自动播放按钮",
|
||||
"autoplay-guipushbuttonlabel": "当所有人准备好时开始播放",
|
||||
"autoplay-minimum-label": "最小开始人数:",
|
||||
"hideemptyrooms-menu-label": "隐藏空房间",
|
||||
|
||||
"sendmessage-label": "发送",
|
||||
|
||||
"ready-guipushbuttonlabel": "我已经准备好了!",
|
||||
|
||||
"roomuser-heading-label": "房间 / 用户",
|
||||
"size-heading-label": "大小",
|
||||
"duration-heading-label": "时长",
|
||||
"filename-heading-label": "名称",
|
||||
"notifications-heading-label": "消息通知",
|
||||
"userlist-heading-label": "正在播放列表",
|
||||
|
||||
"browseformedia-label": "浏览媒体文件",
|
||||
|
||||
"file-menu-label": "&文件", # & precedes shortcut key
|
||||
"openmedia-menu-label": "&打开媒体文件",
|
||||
"openstreamurl-menu-label": "打开 &媒体流URL",
|
||||
"setmediadirectories-menu-label": "设置媒体 &目录",
|
||||
"loadplaylistfromfile-menu-label": "&从文件加载播放列表",
|
||||
"saveplaylisttofile-menu-label": "&保存播放列表到文件",
|
||||
"exit-menu-label": "退&出",
|
||||
"advanced-menu-label": "&高级",
|
||||
"window-menu-label": "&窗口",
|
||||
"setoffset-menu-label": "设置&偏移量",
|
||||
"createcontrolledroom-menu-label": "&创建管理的房间",
|
||||
"identifyascontroller-menu-label": "&认证为管理员",
|
||||
"settrusteddomains-menu-label": "设置&受信任的网站",
|
||||
"addtrusteddomain-menu-label": "添加{}为受信任的网站", # Domain
|
||||
|
||||
"edit-menu-label": "&编辑",
|
||||
"cut-menu-label": "剪&切",
|
||||
"copy-menu-label": "&复制",
|
||||
"paste-menu-label": "&粘贴",
|
||||
"selectall-menu-label": "&全选",
|
||||
|
||||
"playback-menu-label": "&播放控制",
|
||||
|
||||
"help-menu-label": "&帮助",
|
||||
"userguide-menu-label": "打开用户&指南",
|
||||
"update-menu-label": "检查&更新",
|
||||
|
||||
"startTLS-initiated": "尝试安全连接",
|
||||
"startTLS-secure-connection-ok": "安全连接已建立({})",
|
||||
"startTLS-server-certificate-invalid": '安全连接建立失败。该服务器使用了无效的证书。这次通信可能被第三方截获。更多详情和故障排除请参见<a href="https://syncplay.pl/trouble">这里</a>。',
|
||||
"startTLS-server-certificate-invalid-DNS-ID": "Syncplay不信任这个服务器,因为它使用的是对其主机名无效的证书。",
|
||||
"startTLS-not-supported-client": "客户端不支持TLS加密安全连接",
|
||||
"startTLS-not-supported-server": "服务器不支持TLS加密安全连接",
|
||||
|
||||
# TLS certificate dialog
|
||||
"tls-information-title": "证书详情",
|
||||
"tls-dialog-status-label": "<strong>Syncplay正使用加密的连接到{}.</strong>",
|
||||
"tls-dialog-desc-label": "用数字证书进行加密,使信息在发送到或来自<br/>服务器{}时保持隐私。",
|
||||
"tls-dialog-connection-label": "使用Transport Layer Security (TLS)对信息进行加密。版本为{},<br/>加密套件{}。",
|
||||
"tls-dialog-certificate-label": "由{}颁发的证书有效期至{}。",
|
||||
|
||||
# About dialog
|
||||
"about-menu-label": "&关于Syncplay",
|
||||
"about-dialog-title": "关于Syncplay",
|
||||
"about-dialog-release": "Version {} release {}",
|
||||
"about-dialog-license-text": "Licensed under the Apache License, Version 2.0",
|
||||
"about-dialog-license-button": "License",
|
||||
"about-dialog-dependencies": "Dependencies",
|
||||
|
||||
"setoffset-msgbox-label": "设置偏移量",
|
||||
"offsetinfo-msgbox-label": "偏移量(使用指南参见https://syncplay.pl/guide/):",
|
||||
|
||||
"promptforstreamurl-msgbox-label": "打开媒体流URL",
|
||||
"promptforstreamurlinfo-msgbox-label": "媒体流URL",
|
||||
|
||||
"addfolder-label": "添加文件夹",
|
||||
|
||||
"adduris-msgbox-label": "添加URL到播放列表(每行一个)",
|
||||
"editplaylist-msgbox-label": "设置播放列表(每行一个)",
|
||||
"trusteddomains-msgbox-label": "可以自动切换到的网站(每行一个)",
|
||||
|
||||
"createcontrolledroom-msgbox-label": "创建管理的房间",
|
||||
"controlledroominfo-msgbox-label": "输入房间名称\r\n(使用指南参见https://syncplay.pl/guide/):",
|
||||
|
||||
"identifyascontroller-msgbox-label": "认证为房间管理员",
|
||||
"identifyinfo-msgbox-label": "输入房间密码\r\n(使用指南参见https://syncplay.pl/guide/):",
|
||||
|
||||
"public-server-msgbox-label": "为当前会话选择公共服务器",
|
||||
|
||||
"megabyte-suffix": " MB",
|
||||
|
||||
# Tooltips
|
||||
|
||||
"host-tooltip": "要连接的主机名或IP,可包括端口(例如:syncplay.pl:8999)。只能与同一服务器/端口上的人进行同步播放。",
|
||||
"name-tooltip": "您的昵称。无需注册,之后可以任意改变。如果没有指定,则生成随机昵称。",
|
||||
"password-tooltip": "只有在连接到私人服务器时才需要密码。",
|
||||
"room-tooltip": "连接后加入的房间,可以是任意名称,但你只能与同一房间的人进行同步播放。",
|
||||
|
||||
"edit-rooms-tooltip": "编辑房间列表。",
|
||||
|
||||
"executable-path-tooltip": "您选择的播放器的位置(目前仅支持mpv、mpv.net、VLC、MPC-HC/BE、mplayer2或IINA)。",
|
||||
"media-path-tooltip": "要打开的视频或媒体流的位置。如果您使用mplayer2,该项为必填项。",
|
||||
"player-arguments-tooltip": "额外的命令行参数/开关,以传递给该媒体播放器。",
|
||||
"mediasearcdirectories-arguments-tooltip": "Syncplay将搜索媒体文件的目录,例如,当你使用点击切换功能时。Syncplay将通过子文件夹进行递归搜索。",
|
||||
|
||||
"more-tooltip": "显示不常用的设置。",
|
||||
"filename-privacy-tooltip": "向服务器发送当前播放文件名的隐私模式。",
|
||||
"filesize-privacy-tooltip": "向服务器发送当前播放文件的大小的隐私模式。",
|
||||
"privacy-sendraw-tooltip": "发送该信息而不进行混淆处理。这是大多数功能的默认选项。",
|
||||
"privacy-sendhashed-tooltip": "发送信息的哈希版本,使其对其他客户不太可见。",
|
||||
"privacy-dontsend-tooltip": "不要向服务器发送这些信息。这提供了最大的隐私保护。",
|
||||
"checkforupdatesautomatically-tooltip": "定期到Syncplay网站查看是否有新版本的Syncplay。",
|
||||
"autosavejoinstolist-tooltip": "当你加入一个服务器中的房间时,在要加入的房间列表中自动记住房间名称。",
|
||||
"slowondesync-tooltip": "在需要时暂时降低播放速率,使您与其他观众同步。在MPC-HC/BE上不支持。",
|
||||
"dontslowdownwithme-tooltip": "意味着如果你的播放滞后,其他人不会被减慢或倒退。这对房间管理员很有用。",
|
||||
"pauseonleave-tooltip": "如果你被断开连接或有人从你的房间离开,则暂停播放。",
|
||||
"readyatstart-tooltip": "在开始时将自己设定为\"准备好了\"(否则你将被设定为\"没有准备好\",直到你改变你的准备状态。)",
|
||||
"forceguiprompt-tooltip": "当用Syncplay打开一个文件时,不显示设置窗口。", # (Inverted)
|
||||
"nostore-tooltip": "用给定的配置运行Syncplay,但不永久存储这些变化。", # (Inverted)
|
||||
"rewindondesync-tooltip": "为了达到同步在需要时跳转当前播放进度。禁用这个选项可能会导致严重的不同步现象!",
|
||||
"fastforwardondesync-tooltip": "当与房间管理员不同步时,会向前跳转播放进度(如果 \"进度过慢时不用让其他人与我同步\"被启用,则是跳转到你应当的进度)。",
|
||||
"showosd-tooltip": "向媒体播放器OSD发送Syncplay信息。",
|
||||
"showosdwarnings-tooltip": "如果出现播放不同的文件,独自在房间里,用户没有准备好等情况,则显示警告。",
|
||||
"showsameroomosd-tooltip": "显示与用户所在房间有关的活动的OSD通知。",
|
||||
"shownoncontrollerosd-tooltip": "显示OSD对与在管理的房间的非管理员有关的活动的通知。",
|
||||
"showdifferentroomosd-tooltip": "显示与用户不在的房间有关的活动的OSD通知。",
|
||||
"showslowdownosd-tooltip": "显示因进度不同步而进行慢速播放/恢复一倍速的通知。",
|
||||
"showdurationnotification-tooltip": "当一个多部分文件中的一个片段丢失时,这很有用,但可能会导致误报。",
|
||||
"language-tooltip": "Syncplay中使用的语言。",
|
||||
"unpause-always-tooltip": "如果你按下继续播放,会总是将你设置为已准备并继续播放,而不是只将你设置为已准备。",
|
||||
"unpause-ifalreadyready-tooltip": "如果你在没有准备好的情况下按下继续播放,它将把你设定为已准备的状态--再按下继续播放就可以继续播放。",
|
||||
"unpause-ifothersready-tooltip": "如果你在没有准备好时按下继续播放,只有在其他人准备好时才会继续播放。",
|
||||
"unpause-ifminusersready-tooltip": "如果你在没有准备好时按下继续播放,那么只有在其他人准备好并且达到最小开始人数时,它才会取消暂停。",
|
||||
"trusteddomains-arguments-tooltip": "启用共享播放列表时,可以让Syncplay自动切换到的网站。",
|
||||
|
||||
"chatinputenabled-tooltip": "在mpv中启用聊天输入(按回车键聊天,按回车键发送,按Esc键取消)。",
|
||||
"chatdirectinput-tooltip": "在mpv中无需按回车键即可进入聊天输入模式。在mpv中按TAB键可以暂时禁用这个功能。",
|
||||
"font-label-tooltip": "在mpv中输入聊天信息时使用的字体。只在客户端使用,所以不影响其他人看到的内容。",
|
||||
"set-input-font-tooltip": "在mpv中输入聊天信息时使用的字体。只在客户端使用,所以不影响其他人看到的内容。",
|
||||
"set-input-colour-tooltip": "在mpv中输入聊天信息时使用的字体颜色。只在客户端使用,所以不影响其他人看到的内容。",
|
||||
"chatinputposition-tooltip": "聊天输入文本在mpv中的位置,当你按下回车键进行打字输入时,会出现在mpv中。",
|
||||
"chatinputposition-top-tooltip": "将聊天输入放在mpv窗口的顶部。",
|
||||
"chatinputposition-middle-tooltip": "将聊天输入放在mpv窗口的中间。",
|
||||
"chatinputposition-bottom-tooltip": "将聊天输入放在mpv窗口的底部。",
|
||||
"chatoutputenabled-tooltip": "在OSD中显示聊天信息(如果媒体播放器支持)。",
|
||||
"font-output-label-tooltip": "聊天显示字体。",
|
||||
"set-output-font-tooltip": "显示聊天信息时使用的字体。",
|
||||
"chatoutputmode-tooltip": "聊天信息的显示方式。",
|
||||
"chatoutputmode-chatroom-tooltip": "在前一行的正下方显示新的聊天行。",
|
||||
"chatoutputmode-scrolling-tooltip": "像弹幕一样从左到右滚动显示聊天信息。",
|
||||
|
||||
"help-tooltip": "打开Syncplay.pl网站上的用户指南。",
|
||||
"reset-tooltip": "将所有设置重置为默认配置。",
|
||||
"update-server-list-tooltip": "连接到syncplay.pl来更新公共服务器的列表。",
|
||||
|
||||
"sslconnection-tooltip": "安全地连接到服务器。点击查看证书详情。",
|
||||
|
||||
"joinroom-tooltip": "离开当前房间,加入指定房间。",
|
||||
"seektime-msgbox-label": "跳到指定的时间(格式:秒/分:秒)。使用+/-进行相对跳转。",
|
||||
"ready-tooltip": "表示你是否准备好观看。",
|
||||
"autoplay-tooltip": "当所有拥有准备就绪指示器的用户都准备好了,并且满足最小开始人数时,自动播放。",
|
||||
"switch-to-file-tooltip": "双击即可切换到{}", # Filename
|
||||
"sendmessage-tooltip": "发送消息到房间中。",
|
||||
|
||||
# In-userlist notes (GUI)
|
||||
"differentsize-note": "大小不同!",
|
||||
"differentsizeandduration-note": "大小和时长不同!",
|
||||
"differentduration-note": "时长不同!",
|
||||
"nofile-note": "(没有正在播放的视频)",
|
||||
|
||||
# Server messages to client
|
||||
"new-syncplay-available-motd-message": "您正在使用Syncplay {}版本,但已有较新的版本可从https://syncplay.pl下载。", # ClientVersion
|
||||
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # NOTE: Do not translate the word -temp
|
||||
|
||||
# Server notifications
|
||||
"welcome-server-notification": "欢迎使用Syncplay服务端,当前版本{0}", # version
|
||||
"client-connected-room-server-notification": "{0}({2})连接到了房间'{1}'", # username, host, room
|
||||
"client-left-server-notification": "{0}断开了服务器", # name
|
||||
"no-salt-notification": "请注意:为了使该服务器实例生成的房间管理员密码在服务器重新启动时仍然有效,请在今后运行Syncplay服务器时添加以下命令行参数:--salt {}", # Salt
|
||||
|
||||
|
||||
# Server arguments
|
||||
"server-argument-description": '在网络上同步播放多个媒体播放器实例的解决方案。服务器实例',
|
||||
"server-argument-epilog": '如果没有提供选项,将使用_config的值',
|
||||
"server-port-argument": '服务器TCP端口',
|
||||
"server-password-argument": '服务器密码',
|
||||
"server-isolate-room-argument": '房间是否相互隔离?',
|
||||
"server-salt-argument": "用于生成管理的房间密码的随机字符串",
|
||||
"server-disable-ready-argument": "停用准备就绪功能",
|
||||
"server-motd-argument": "取出Motd的文件的路径",
|
||||
"server-rooms-argument": "使用和/或创建数据库文件的路径,以存储持久的房间数据。使房间能够在没有观察者的情况下持续存在,并通过重新启动来实现。",
|
||||
"server-permanent-rooms-argument": "列出永久房间的文件的路径,即使房间是空的也会被列出(以文本文件的形式,每行列出一个房间)--要求启用永久房间。",
|
||||
"server-chat-argument": "是否禁用聊天功能?",
|
||||
"server-chat-maxchars-argument": "聊天信息中的最大字符数(默认为{})", # Default number of characters
|
||||
"server-maxusernamelength-argument": "用户名中的最大字符数(默认为{})。",
|
||||
"server-stats-db-file-argument": "使用提供的SQLite db文件启用服务器统计功能",
|
||||
"server-startTLS-argument": "使用提供的路径中的证书文件启用TLS连接",
|
||||
"server-messed-up-motd-unescaped-placeholders": "每日信息中有未转义的占位符。所有 $ 字符应当重复两遍 ($$).",
|
||||
"server-messed-up-motd-too-long": "每日信息过长 - 最大{}个chars, 给出的长度{}",
|
||||
|
||||
# Server errors
|
||||
"unknown-command-server-error": "未知命令 {}", # message
|
||||
"not-json-server-error": "非json格式字符串 {}", # message
|
||||
"line-decode-server-error": "非utf-8编码字符串",
|
||||
"not-known-server-error": "在发送这条命令之前,你必须被服务器认证",
|
||||
"client-drop-server-error": "客户端错误消息: {} -- {}", # host, error
|
||||
"password-required-server-error": "请输入密码",
|
||||
"wrong-password-server-error": "密码不正确",
|
||||
"hello-server-error": "Not enough Hello arguments", # DO NOT TRANSLATE
|
||||
|
||||
# Playlists
|
||||
"playlist-selection-changed-notification": "{}换碟了", # Username
|
||||
"playlist-contents-changed-notification": "{}更新了播放列表", # Username
|
||||
"cannot-find-file-for-playlist-switch-error": "切换播放内容时,在媒体目录中找不到{}!", # Filename
|
||||
"cannot-add-duplicate-error": "无法将'{}'第二次添加到播放列表中,因为不允许重复。", # Filename
|
||||
"cannot-add-unsafe-path-error": "不能自动加载{},因为它不在受信任的网站上。你可以在播放列表中双击该URL,手动切换到该URL,并通过文件->高级->设置受信任网站添加受信任网站。如果你鼠标右键一个URL,那么你可以通过右键菜单将其网站添加为受信任网站。", # Filename
|
||||
"sharedplaylistenabled-label": "启用共享播放列表",
|
||||
"removefromplaylist-menu-label": "从播放列表中删除",
|
||||
"shuffleremainingplaylist-menu-label": "随机排列未完成的播放列表",
|
||||
"shuffleentireplaylist-menu-label": "随机排列整个播放列表",
|
||||
"undoplaylist-menu-label": "撤销对播放列表的上一次修改",
|
||||
"addfilestoplaylist-menu-label": "将文件添加到播放列表的底部",
|
||||
"addurlstoplaylist-menu-label": "将URL添加到播放列表的底部",
|
||||
"editplaylist-menu-label": "编辑播放列表",
|
||||
|
||||
"open-containing-folder": "打开包含该文件的文件夹",
|
||||
"addyourfiletoplaylist-menu-label": "将文件添加到播放列表",
|
||||
"addotherusersfiletoplaylist-menu-label": "添加{}的文件到播放列表", # [Username]
|
||||
"addyourstreamstoplaylist-menu-label": "将媒体流添加到播放列表",
|
||||
"addotherusersstreamstoplaylist-menu-label": "添加{}的媒体流流到播放列表", # [Username]
|
||||
"openusersstream-menu-label": "打开{}的媒体流", # [username]'s
|
||||
"openusersfile-menu-label": "打开{}的文件", # [username]'s
|
||||
|
||||
"playlist-instruction-item-message": "把文件拖到这里,就可以把它添加到共享播放列表中。",
|
||||
"sharedplaylistenabled-tooltip": "房间管理员可以将文件添加到共享播放列表中,以方便大家观看同样的东西。可以在其他设置中配置媒体目录。",
|
||||
|
||||
"playlist-empty-error": "播放列表目前是空的。",
|
||||
"playlist-invalid-index-error": "无效的播放列表索引",
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
import os
|
||||
from syncplay import constants
|
||||
from syncplay.players.mpv import MpvPlayer
|
||||
from syncplay.utils import playerPathExists
|
||||
|
||||
class MpvnetPlayer(MpvPlayer):
|
||||
|
||||
@ -30,11 +31,11 @@ class MpvnetPlayer(MpvPlayer):
|
||||
|
||||
@staticmethod
|
||||
def getExpandedPath(playerPath):
|
||||
if not os.path.isfile(playerPath):
|
||||
if os.path.isfile(playerPath + "mpvnet.exe"):
|
||||
if not playerPathExists(playerPath):
|
||||
if playerPathExists(playerPath + "mpvnet.exe"):
|
||||
playerPath += "mpvnet.exe"
|
||||
return playerPath
|
||||
elif os.path.isfile(playerPath + "\\mpvnet.exe"):
|
||||
elif playerPathExists(playerPath + "\\mpvnet.exe"):
|
||||
playerPath += "\\mpvnet.exe"
|
||||
return playerPath
|
||||
if os.access(playerPath, os.X_OK):
|
||||
|
||||
@ -11,7 +11,7 @@ from twisted.python.versions import Version
|
||||
from zope.interface.declarations import implementer
|
||||
|
||||
import syncplay
|
||||
from syncplay.constants import PING_MOVING_AVERAGE_WEIGHT, CONTROLLED_ROOMS_MIN_VERSION, USER_READY_MIN_VERSION, SHARED_PLAYLIST_MIN_VERSION, CHAT_MIN_VERSION
|
||||
from syncplay.constants import PING_MOVING_AVERAGE_WEIGHT, CONTROLLED_ROOMS_MIN_VERSION, USER_READY_MIN_VERSION, SHARED_PLAYLIST_MIN_VERSION, CHAT_MIN_VERSION, UNKNOWN_UI_MODE
|
||||
from syncplay.messages import getMessage
|
||||
from syncplay.utils import meetsMinVersion
|
||||
|
||||
@ -131,6 +131,10 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
self._client.setUsername(username)
|
||||
self._client.setRoom(roomName)
|
||||
self.logged = True
|
||||
if self.persistentRoomWarning(featureList):
|
||||
if len(motd) > 0:
|
||||
motd += "\n\n"
|
||||
motd += getMessage("persistent-rooms-notice")
|
||||
if motd:
|
||||
self._client.ui.showMessage(motd, True, True)
|
||||
self._client.ui.showMessage(getMessage("connected-successful-notification"))
|
||||
@ -138,6 +142,9 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
self._client.sendFile()
|
||||
self._client.setServerVersion(version, featureList)
|
||||
|
||||
def persistentRoomWarning(self, serverFeatures):
|
||||
return serverFeatures["persistentRooms"] if "persistentRooms" in serverFeatures else False
|
||||
|
||||
def sendHello(self):
|
||||
hello = {}
|
||||
hello["username"] = self._client.getUsername()
|
||||
@ -441,6 +448,8 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
self._features["featureList"] = False
|
||||
self._features["readiness"] = meetsMinVersion(self._version, USER_READY_MIN_VERSION)
|
||||
self._features["managedRooms"] = meetsMinVersion(self._version, CONTROLLED_ROOMS_MIN_VERSION)
|
||||
self._features["persistentRooms"] = False
|
||||
self._features["uiMode"] = UNKNOWN_UI_MODE
|
||||
return self._features
|
||||
|
||||
def isLogged(self):
|
||||
@ -496,6 +505,11 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
self._logged = True
|
||||
self.sendHello(version)
|
||||
|
||||
def persistentRoomWarning(self, clientFeatures, serverFeatures):
|
||||
serverPersistentRooms = serverFeatures["persistentRooms"]
|
||||
clientPersistentRooms = clientFeatures["persistentRooms"] if "persistentRooms" in clientFeatures else False
|
||||
return serverPersistentRooms and not clientPersistentRooms
|
||||
|
||||
@requireLogged
|
||||
def handleChat(self, chatMessage):
|
||||
if not self._factory.disableChat:
|
||||
@ -520,8 +534,12 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
hello["room"] = {"name": room.getName()}
|
||||
hello["version"] = clientVersion # Used so 1.2.X client works on newer server
|
||||
hello["realversion"] = syncplay.version
|
||||
hello["motd"] = self._factory.getMotd(userIp, username, room, clientVersion)
|
||||
hello["features"] = self._factory.getFeatures()
|
||||
hello["motd"] = self._factory.getMotd(userIp, username, room, clientVersion)
|
||||
if self.persistentRoomWarning(clientFeatures=self._features, serverFeatures=hello["features"]):
|
||||
if len(hello["motd"]) > 0:
|
||||
hello["motd"] += "\n\n"
|
||||
hello["motd"] += getMessage("persistent-rooms-notice")
|
||||
self.sendMessage({"Hello": hello})
|
||||
|
||||
@requireLogged
|
||||
@ -617,11 +635,28 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
}
|
||||
userlist[room.getName()][watcher.getName()] = userFile
|
||||
|
||||
def _addDummyUserOnList(self, userlist, dummyRoom,dummyCount):
|
||||
if dummyRoom not in userlist:
|
||||
userlist[dummyRoom] = {}
|
||||
dummyFile = {
|
||||
"position": 0,
|
||||
"file": {},
|
||||
"controller": False,
|
||||
"isReady": True,
|
||||
"features": []
|
||||
}
|
||||
userlist[dummyRoom][" " * dummyCount] = dummyFile
|
||||
|
||||
def sendList(self):
|
||||
userlist = {}
|
||||
watchers = self._factory.getAllWatchersForUser(self._watcher)
|
||||
dummyCount = 0
|
||||
for watcher in watchers:
|
||||
self._addUserOnList(userlist, watcher)
|
||||
if self._watcher.isGUIUser(self.getFeatures()):
|
||||
for emptyRoom in self._factory.getEmptyPersistentRooms():
|
||||
dummyCount += 1
|
||||
self._addDummyUserOnList(userlist, emptyRoom, dummyCount)
|
||||
self.sendMessage({"List": userlist})
|
||||
|
||||
@requireLogged
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
[Desktop Entry]
|
||||
Encoding=UTF-8
|
||||
Name=Syncplay server
|
||||
Comment=Synchronize video playback over network
|
||||
Exec=syncplay-server %u
|
||||
@ -7,3 +6,4 @@ Terminal=true
|
||||
Type=Application
|
||||
Icon=syncplay
|
||||
Categories=AudioVideo;Audio;Video;
|
||||
Keywords=syncplay;server;sync;
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
[Desktop Entry]
|
||||
Encoding=UTF-8
|
||||
Name=Syncplay
|
||||
Comment=Synchronize video playback over network
|
||||
Exec=syncplay %u
|
||||
@ -7,4 +6,4 @@ Type=Application
|
||||
Icon=syncplay
|
||||
Categories=AudioVideo;Audio;Video;
|
||||
MimeType=audio/ac3;audio/mp4;audio/mpeg;audio/vnd.rn-realaudio;audio/vorbis;audio/x-adpcm;audio/x-matroska;audio/x-mp2;audio/x-mp3;audio/x-ms-wma;audio/x-vorbis;audio/x-wav;audio/mpegurl;audio/x-mpegurl;audio/x-pn-realaudio;audio/x-scpls;video/avi;video/mp4;video/flv;video/mpeg;video/quicktime;video/vnd.rn-realvideo;video/x-matroska;video/x-ms-asf;video/x-msvideo;video/x-ms-wmv;video/x-ogm;video/x-theora;
|
||||
|
||||
Keywords=syncplay;media;video;sync;
|
||||
|
||||
@ -4,6 +4,7 @@ import hashlib
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
import json
|
||||
from string import Template
|
||||
|
||||
from twisted.enterprise import adbapi
|
||||
@ -21,14 +22,14 @@ import syncplay
|
||||
from syncplay import constants
|
||||
from syncplay.messages import getMessage
|
||||
from syncplay.protocols import SyncServerProtocol
|
||||
from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion, playlistIsValid, truncateText
|
||||
|
||||
from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion, playlistIsValid, truncateText, getListAsMultilineString, convertMultilineStringToList
|
||||
|
||||
class SyncFactory(Factory):
|
||||
def __init__(self, port='', password='', motdFilePath=None, isolateRooms=False, salt=None,
|
||||
def __init__(self, port='', password='', motdFilePath=None, roomsDbFile=None, permanentRoomsFile=None, isolateRooms=False, salt=None,
|
||||
disableReady=False, disableChat=False, maxChatMessageLength=constants.MAX_CHAT_MESSAGE_LENGTH,
|
||||
maxUsernameLength=constants.MAX_USERNAME_LENGTH, statsDbFile=None, tlsCertPath=None):
|
||||
self.isolateRooms = isolateRooms
|
||||
syncplay.messages.setLanguage(syncplay.messages.getInitialLanguage())
|
||||
print(getMessage("welcome-server-notification").format(syncplay.version))
|
||||
self.port = port
|
||||
if password:
|
||||
@ -40,16 +41,19 @@ class SyncFactory(Factory):
|
||||
print(getMessage("no-salt-notification").format(salt))
|
||||
self._salt = salt
|
||||
self._motdFilePath = motdFilePath
|
||||
self.roomsDbFile = roomsDbFile
|
||||
self.disableReady = disableReady
|
||||
self.disableChat = disableChat
|
||||
self.maxChatMessageLength = maxChatMessageLength if maxChatMessageLength is not None else constants.MAX_CHAT_MESSAGE_LENGTH
|
||||
self.maxUsernameLength = maxUsernameLength if maxUsernameLength is not None else constants.MAX_USERNAME_LENGTH
|
||||
self.permanentRoomsFile = permanentRoomsFile if permanentRoomsFile is not None and os.path.isfile(permanentRoomsFile) else None
|
||||
self.permanentRooms = self.loadListFromMultilineTextFile(self.permanentRoomsFile) if self.permanentRoomsFile is not None else []
|
||||
if not isolateRooms:
|
||||
self._roomManager = RoomManager()
|
||||
self._roomManager = RoomManager(self.roomsDbFile, self.permanentRooms)
|
||||
else:
|
||||
self._roomManager = PublicRoomManager()
|
||||
if statsDbFile is not None:
|
||||
self._statsDbHandle = DBManager(statsDbFile)
|
||||
self._statsDbHandle = StatsDBManager(statsDbFile)
|
||||
self._statsRecorder = StatsRecorder(self._statsDbHandle, self._roomManager)
|
||||
statsDelay = 5*(int(self.port)%10 + 1)
|
||||
self._statsRecorder.startRecorder(statsDelay)
|
||||
@ -64,6 +68,16 @@ class SyncFactory(Factory):
|
||||
self.options = None
|
||||
self.serverAcceptsTLS = False
|
||||
|
||||
def loadListFromMultilineTextFile(self, path):
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
with open(path) as f:
|
||||
multiline = f.read().splitlines()
|
||||
return multiline
|
||||
|
||||
def loadRoom(self):
|
||||
rooms = self._roomsDbHandle.loadRooms()
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
return SyncServerProtocol(self)
|
||||
|
||||
@ -79,6 +93,7 @@ class SyncFactory(Factory):
|
||||
features["isolateRooms"] = self.isolateRooms
|
||||
features["readiness"] = not self.disableReady
|
||||
features["managedRooms"] = True
|
||||
features["persistentRooms"] = self.roomsDbFile is not None
|
||||
features["chat"] = not self.disableChat
|
||||
features["maxChatMessageLength"] = self.maxChatMessageLength
|
||||
features["maxUsernameLength"] = self.maxUsernameLength
|
||||
@ -110,7 +125,7 @@ class SyncFactory(Factory):
|
||||
|
||||
def addWatcher(self, watcherProtocol, username, roomName):
|
||||
roomName = truncateText(roomName, constants.MAX_ROOM_NAME_LENGTH)
|
||||
username = self._roomManager.findFreeUsername(username)
|
||||
username = self._roomManager.findFreeUsername(username, self.maxUsernameLength)
|
||||
watcher = Watcher(self, watcherProtocol, username)
|
||||
self.setWatcherRoom(watcher, roomName, asJoin=True)
|
||||
|
||||
@ -134,11 +149,17 @@ class SyncFactory(Factory):
|
||||
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, None)
|
||||
self._roomManager.broadcast(watcher, l)
|
||||
self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False))
|
||||
if self.roomsDbFile:
|
||||
l = lambda w: w.sendList(toGUIOnly=True)
|
||||
self._roomManager.broadcast(watcher, l)
|
||||
|
||||
def removeWatcher(self, watcher):
|
||||
if watcher and watcher.getRoom():
|
||||
self.sendLeftMessage(watcher)
|
||||
self._roomManager.removeWatcher(watcher)
|
||||
if self.roomsDbFile:
|
||||
l = lambda w: w.sendList(toGUIOnly=True)
|
||||
self._roomManager.broadcast(watcher, l)
|
||||
|
||||
def sendLeftMessage(self, watcher):
|
||||
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"left": True})
|
||||
@ -148,6 +169,9 @@ class SyncFactory(Factory):
|
||||
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"joined": True, "version": watcher.getVersion(), "features": watcher.getFeatures()}) if w != watcher else None
|
||||
self._roomManager.broadcast(watcher, l)
|
||||
self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False))
|
||||
if self.roomsDbFile:
|
||||
l = lambda w: w.sendList(toGUIOnly=True)
|
||||
self._roomManager.broadcast(watcher, l)
|
||||
|
||||
def sendFileUpdate(self, watcher):
|
||||
if watcher.getFile():
|
||||
@ -169,6 +193,9 @@ class SyncFactory(Factory):
|
||||
def getAllWatchersForUser(self, forUser):
|
||||
return self._roomManager.getAllWatchersForUser(forUser)
|
||||
|
||||
def getEmptyPersistentRooms(self):
|
||||
return self._roomManager.getEmptyPersistentRooms()
|
||||
|
||||
def authRoomController(self, watcher, password, roomBaseName=None):
|
||||
room = watcher.getRoom()
|
||||
roomName = roomBaseName if roomBaseName else room.getName()
|
||||
@ -290,8 +317,33 @@ class StatsRecorder(object):
|
||||
except:
|
||||
pass
|
||||
|
||||
class RoomsRecorder(StatsRecorder):
|
||||
def __init__(self, dbHandle, roomManager):
|
||||
self._dbHandle = dbHandle
|
||||
self._roomManagerHandle = roomManager
|
||||
|
||||
class DBManager(object):
|
||||
def startRecorder(self, delay):
|
||||
try:
|
||||
self._dbHandle.connect()
|
||||
reactor.callLater(delay, self._scheduleClientSnapshot) # TODO: FIX THIS!
|
||||
except:
|
||||
print("--- Error in initializing the stats database. Server Stats not enabled. ---")
|
||||
|
||||
def _scheduleClientSnapshot(self):
|
||||
self._clientSnapshotTimer = task.LoopingCall(self._runClientSnapshot)
|
||||
self._clientSnapshotTimer.start(constants.SERVER_STATS_SNAPSHOT_INTERVAL)
|
||||
|
||||
def _runClientSnapshot(self):
|
||||
try:
|
||||
snapshotTime = int(time.time())
|
||||
rooms = self._roomManagerHandle.exportRooms()
|
||||
for room in rooms.values():
|
||||
for watcher in room.getWatchers():
|
||||
self._dbHandle.addVersionLog(snapshotTime, watcher.getVersion())
|
||||
except:
|
||||
pass
|
||||
|
||||
class StatsDBManager(object):
|
||||
def __init__(self, dbpath):
|
||||
self._dbPath = dbpath
|
||||
self._connection = None
|
||||
@ -305,17 +357,74 @@ class DBManager(object):
|
||||
self._createSchema()
|
||||
|
||||
def _createSchema(self):
|
||||
initQuery = 'create table if not exists clients_snapshots (snapshot_time integer, version string)'
|
||||
self._connection.runQuery(initQuery)
|
||||
initQuery = 'create table if not exists clients_snapshots (snapshot_time INTEGER, version STRING)'
|
||||
return self._connection.runQuery(initQuery)
|
||||
|
||||
def addVersionLog(self, timestamp, version):
|
||||
content = (timestamp, version, )
|
||||
self._connection.runQuery("INSERT INTO clients_snapshots VALUES (?, ?)", content)
|
||||
|
||||
class RoomDBManager(object):
|
||||
def __init__(self, dbpath, loadroomscallback):
|
||||
self._dbPath = dbpath
|
||||
self._connection = None
|
||||
self._loadRoomsCallback = loadroomscallback
|
||||
|
||||
def __del__(self):
|
||||
if self._connection is not None:
|
||||
self._connection.close()
|
||||
|
||||
def connect(self):
|
||||
self._connection = adbapi.ConnectionPool("sqlite3", self._dbPath, check_same_thread=False)
|
||||
self._createSchema().addCallback(self.loadRooms)
|
||||
|
||||
def _createSchema(self):
|
||||
initQuery = 'create table if not exists persistent_rooms (name STRING PRIMARY KEY, playlist STRING, playlistIndex INTEGER, position REAL, lastSavedUpdate INTEGER)'
|
||||
return self._connection.runQuery(initQuery)
|
||||
|
||||
def saveRoom(self, name, playlist, playlistIndex, position, lastUpdate):
|
||||
content = (name, playlist, playlistIndex, position, lastUpdate)
|
||||
self._connection.runQuery("INSERT OR REPLACE INTO persistent_rooms VALUES (?, ?, ?, ?, ?)", content)
|
||||
|
||||
def deleteRoom(self, name):
|
||||
self._connection.runQuery("DELETE FROM persistent_rooms where name = ?", [name])
|
||||
|
||||
def loadRooms(self, result=None):
|
||||
roomsQuery = "SELECT * FROM persistent_rooms"
|
||||
rooms = self._connection.runQuery(roomsQuery)
|
||||
rooms.addCallback(self.loadedRooms)
|
||||
|
||||
def loadedRooms(self, rooms):
|
||||
self._loadRoomsCallback(rooms)
|
||||
|
||||
class RoomManager(object):
|
||||
def __init__(self):
|
||||
def __init__(self, roomsdbfile=None, permanentRooms=[]):
|
||||
self._roomsDbFile = roomsdbfile
|
||||
self._rooms = {}
|
||||
self._permanentRooms = permanentRooms
|
||||
if self._roomsDbFile is not None:
|
||||
self._roomsDbHandle = RoomDBManager(self._roomsDbFile, self.loadRooms)
|
||||
self._roomsDbHandle.connect()
|
||||
else:
|
||||
self._roomsDbHandle = None
|
||||
|
||||
def loadRooms(self, rooms):
|
||||
roomsLoaded = []
|
||||
for roomDetails in rooms:
|
||||
roomName = truncateText(roomDetails[0], constants.MAX_ROOM_NAME_LENGTH)
|
||||
room = Room(roomDetails[0], self._roomsDbHandle)
|
||||
room.loadRoom(roomDetails)
|
||||
if roomName in self._permanentRooms:
|
||||
room.setPermanent(True)
|
||||
self._rooms[roomName] = room
|
||||
roomsLoaded.append(roomName)
|
||||
for roomName in self._permanentRooms:
|
||||
if roomName not in roomsLoaded:
|
||||
roomDetails = (roomName, "", 0, 0, 0)
|
||||
room = Room(roomName, self._roomsDbHandle)
|
||||
room.loadRoom(roomDetails)
|
||||
room.setPermanent(True)
|
||||
self._rooms[roomName] = room
|
||||
|
||||
def broadcastRoom(self, sender, whatLambda):
|
||||
room = sender.getRoom()
|
||||
@ -335,6 +444,20 @@ class RoomManager(object):
|
||||
watchers.append(watcher)
|
||||
return watchers
|
||||
|
||||
def getPersistentRooms(self, sender):
|
||||
persistentRooms = []
|
||||
for room in self._rooms.values():
|
||||
if room.isPersistent():
|
||||
persistentRooms.append(room.getName())
|
||||
return persistentRooms
|
||||
|
||||
def getEmptyPersistentRooms(self):
|
||||
emptyPersistentRooms = []
|
||||
for room in self._rooms.values():
|
||||
if len(room.getWatchers()) == 0:
|
||||
emptyPersistentRooms.append(room.getName())
|
||||
return emptyPersistentRooms
|
||||
|
||||
def moveWatcher(self, watcher, roomName):
|
||||
roomName = truncateText(roomName, constants.MAX_ROOM_NAME_LENGTH)
|
||||
self.removeWatcher(watcher)
|
||||
@ -352,19 +475,24 @@ class RoomManager(object):
|
||||
return self._rooms[roomName]
|
||||
else:
|
||||
if RoomPasswordProvider.isControlledRoom(roomName):
|
||||
room = ControlledRoom(roomName)
|
||||
room = ControlledRoom(roomName, self._roomsDbHandle)
|
||||
else:
|
||||
room = Room(roomName)
|
||||
if roomName in self._rooms:
|
||||
self._deleteRoomIfEmpty(self._rooms[roomName])
|
||||
room = Room(roomName, self._roomsDbHandle)
|
||||
self._rooms[roomName] = room
|
||||
return room
|
||||
|
||||
def _deleteRoomIfEmpty(self, room):
|
||||
#if room.isEmpty() and room.getName() in self._rooms:
|
||||
# del self._rooms[room.getName()]
|
||||
print("Would delete room: " + room.getName())
|
||||
if room.isEmpty() and room.getName():
|
||||
if self._roomsDbHandle and room.isNotPermanent():
|
||||
if room.isPersistent() and not room.isPlaylistEmpty():
|
||||
return
|
||||
self._roomsDbHandle.deleteRoom(room.getName())
|
||||
del self._rooms[room.getName()]
|
||||
|
||||
def findFreeUsername(self, username):
|
||||
username = truncateText(username, constants.MAX_USERNAME_LENGTH)
|
||||
def findFreeUsername(self, username, maxUsernameLength=constants.MAX_USERNAME_LENGTH):
|
||||
username = truncateText(username, maxUsernameLength)
|
||||
allnames = []
|
||||
for room in self._rooms.values():
|
||||
for watcher in room.getWatchers():
|
||||
@ -396,19 +524,58 @@ class Room(object):
|
||||
STATE_PAUSED = 0
|
||||
STATE_PLAYING = 1
|
||||
|
||||
def __init__(self, name):
|
||||
def __init__(self, name, roomsdbhandle):
|
||||
self._name = name
|
||||
self._roomsDbHandle = roomsdbhandle
|
||||
self._watchers = {}
|
||||
self._playState = self.STATE_PAUSED
|
||||
self._setBy = None
|
||||
self._playlist = []
|
||||
self._playlistIndex = None
|
||||
self._lastUpdate = time.time()
|
||||
self._lastSavedUpdate = 0
|
||||
self._position = 0
|
||||
self._permanent = False
|
||||
|
||||
def __str__(self, *args, **kwargs):
|
||||
return self.getName()
|
||||
|
||||
def roomsCanPersist(self):
|
||||
return self._roomsDbHandle is not None
|
||||
|
||||
def isPersistent(self):
|
||||
return self.roomsCanPersist() and not self.isMarkedAsTemporary()
|
||||
|
||||
def isMarkedAsTemporary(self):
|
||||
roomName = self.getName().lower()
|
||||
return roomName.endswith("-temp") or "-temp:" in roomName
|
||||
|
||||
def isPlaylistEmpty(self):
|
||||
return len(self._playlist) == 0
|
||||
|
||||
def isPermanent(self):
|
||||
return self._permanent
|
||||
|
||||
def isNotPermanent(self):
|
||||
return not self.isPermanent()
|
||||
|
||||
def sanitizeFilename(self, filename, blacklist="<>:/\\|?*\"", placeholder="_"):
|
||||
return ''.join([c if c not in blacklist and ord(c) >= 32 else placeholder for c in filename])
|
||||
|
||||
def writeToDb(self):
|
||||
if not self.isPersistent():
|
||||
return
|
||||
processed_playlist = getListAsMultilineString(self._playlist)
|
||||
self._roomsDbHandle.saveRoom(self._name, processed_playlist, self._playlistIndex, self._position, self._lastSavedUpdate)
|
||||
|
||||
def loadRoom(self, room):
|
||||
name, playlist, playlistindex, position, lastupdate = room
|
||||
self._name = name
|
||||
self._playlist = convertMultilineStringToList(playlist)
|
||||
self._playlistIndex = playlistindex
|
||||
self._position = position
|
||||
self._lastSavedUpdate = lastupdate
|
||||
|
||||
def getName(self):
|
||||
return self._name
|
||||
|
||||
@ -418,7 +585,7 @@ class Room(object):
|
||||
watcher = min(self._watchers.values())
|
||||
self._setBy = watcher
|
||||
self._position = watcher.getPosition()
|
||||
self._lastUpdate = time.time()
|
||||
self._lastSavedUpdate = self._lastUpdate = time.time()
|
||||
return self._position
|
||||
elif self._position is not None:
|
||||
return self._position + (age if self._playState == self.STATE_PLAYING else 0)
|
||||
@ -428,12 +595,17 @@ class Room(object):
|
||||
def setPaused(self, paused=STATE_PAUSED, setBy=None):
|
||||
self._playState = paused
|
||||
self._setBy = setBy
|
||||
self.writeToDb()
|
||||
|
||||
def setPosition(self, position, setBy=None):
|
||||
self._position = position
|
||||
for watcher in self._watchers.values():
|
||||
watcher.setPosition(position)
|
||||
self._setBy = setBy
|
||||
self.writeToDb()
|
||||
|
||||
def setPermanent(self, newState):
|
||||
self._permanent = newState
|
||||
|
||||
def isPlaying(self):
|
||||
return self._playState == self.STATE_PLAYING
|
||||
@ -445,7 +617,7 @@ class Room(object):
|
||||
return list(self._watchers.values())
|
||||
|
||||
def addWatcher(self, watcher):
|
||||
if self._watchers:
|
||||
if self._watchers or self.isPersistent():
|
||||
watcher.setPosition(self.getPosition())
|
||||
self._watchers[watcher.getName()] = watcher
|
||||
watcher.setRoom(self)
|
||||
@ -455,8 +627,9 @@ class Room(object):
|
||||
return
|
||||
del self._watchers[watcher.getName()]
|
||||
watcher.setRoom(None)
|
||||
if not self._watchers:
|
||||
if not self._watchers and not self.isPersistent():
|
||||
self._position = 0
|
||||
self.writeToDb()
|
||||
|
||||
def isEmpty(self):
|
||||
return not bool(self._watchers)
|
||||
@ -469,9 +642,11 @@ class Room(object):
|
||||
|
||||
def setPlaylist(self, files, setBy=None):
|
||||
self._playlist = files
|
||||
self.writeToDb()
|
||||
|
||||
def setPlaylistIndex(self, index, setBy=None):
|
||||
self._playlistIndex = index
|
||||
self.writeToDb()
|
||||
|
||||
def getPlaylist(self):
|
||||
return self._playlist
|
||||
@ -479,10 +654,12 @@ class Room(object):
|
||||
def getPlaylistIndex(self):
|
||||
return self._playlistIndex
|
||||
|
||||
def getControllers(self):
|
||||
return []
|
||||
|
||||
class ControlledRoom(Room):
|
||||
def __init__(self, name):
|
||||
Room.__init__(self, name)
|
||||
def __init__(self, name, roomsdbhandle):
|
||||
Room.__init__(self, name, roomsdbhandle)
|
||||
self._controllers = {}
|
||||
|
||||
def getPosition(self):
|
||||
@ -505,6 +682,7 @@ class ControlledRoom(Room):
|
||||
Room.removeWatcher(self, watcher)
|
||||
if watcher.getName() in self._controllers:
|
||||
del self._controllers[watcher.getName()]
|
||||
self.writeToDb()
|
||||
|
||||
def setPaused(self, paused=Room.STATE_PAUSED, setBy=None):
|
||||
if self.canControl(setBy):
|
||||
@ -526,7 +704,7 @@ class ControlledRoom(Room):
|
||||
return watcher.getName() in self._controllers
|
||||
|
||||
def getControllers(self):
|
||||
return self._controllers
|
||||
return {}
|
||||
|
||||
|
||||
class Watcher(object):
|
||||
@ -606,6 +784,23 @@ class Watcher(object):
|
||||
if self._connector.meetsMinVersion(constants.CHAT_MIN_VERSION):
|
||||
self._connector.sendMessage({"Chat": message})
|
||||
|
||||
def sendList(self, toGUIOnly=False):
|
||||
if toGUIOnly and self.isGUIUser(self._connector.getFeatures()):
|
||||
clientFeatures = self._connector.getFeatures()
|
||||
if "uiMode" in clientFeatures:
|
||||
if clientFeatures["uiMode"] == constants.CONSOLE_UI_MODE:
|
||||
return
|
||||
else:
|
||||
return
|
||||
self._connector.sendList()
|
||||
|
||||
def isGUIUser(self, clientFeatures):
|
||||
clientFeatures = self._connector.getFeatures()
|
||||
uiMode = clientFeatures["uiMode"] if "uiMode" in clientFeatures else constants.UNKNOWN_UI_MODE
|
||||
if uiMode == constants.UNKNOWN_UI_MODE:
|
||||
uiMode = constants.FALLBACK_ASSUMED_UI_MODE
|
||||
return uiMode == constants.GRAPHICAL_UI_MODE
|
||||
|
||||
def sendSetReady(self, username, isReady, manuallyInitiated=True):
|
||||
self._connector.sendSetReady(username, isReady, manuallyInitiated)
|
||||
|
||||
@ -691,6 +886,8 @@ class ConfigurationGetter(object):
|
||||
self._argparser.add_argument('--disable-chat', action='store_true', help=getMessage("server-chat-argument"))
|
||||
self._argparser.add_argument('--salt', metavar='salt', type=str, nargs='?', help=getMessage("server-salt-argument"), default=os.environ.get('SYNCPLAY_SALT'))
|
||||
self._argparser.add_argument('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-argument"))
|
||||
self._argparser.add_argument('--rooms-db-file', metavar='rooms', type=str, nargs='?', help=getMessage("server-rooms-argument"))
|
||||
self._argparser.add_argument('--permanent-rooms-file', metavar='permanentrooms', type=str, nargs='?', help=getMessage("server-permanent-rooms-argument"))
|
||||
self._argparser.add_argument('--max-chat-message-length', metavar='maxChatMessageLength', type=int, nargs='?', help=getMessage("server-chat-maxchars-argument").format(constants.MAX_CHAT_MESSAGE_LENGTH))
|
||||
self._argparser.add_argument('--max-username-length', metavar='maxUsernameLength', type=int, nargs='?', help=getMessage("server-maxusernamelength-argument").format(constants.MAX_USERNAME_LENGTH))
|
||||
self._argparser.add_argument('--stats-db-file', metavar='file', type=str, nargs='?', help=getMessage("server-stats-db-file-argument"))
|
||||
|
||||
@ -10,7 +10,7 @@ from configparser import SafeConfigParser, DEFAULTSECT
|
||||
from syncplay import constants, utils, version, milestone
|
||||
from syncplay.messages import getMessage, setLanguage, isValidLanguage
|
||||
from syncplay.players.playerFactory import PlayerFactory
|
||||
from syncplay.utils import isMacOS
|
||||
from syncplay.utils import isMacOS, isWindows
|
||||
|
||||
|
||||
class InvalidConfigValue(Exception):
|
||||
@ -514,13 +514,22 @@ class ConfigurationGetter(object):
|
||||
self._overrideConfigWithArgs(args)
|
||||
if not self._config['noGui']:
|
||||
try:
|
||||
from syncplay.vendor.Qt import QtWidgets, IsPySide, IsPySide2
|
||||
from syncplay.vendor.Qt import QtWidgets, IsPySide, IsPySide2, QtGui
|
||||
from syncplay.vendor.Qt.QtCore import QCoreApplication
|
||||
from syncplay.vendor import qt5reactor
|
||||
if not (IsPySide2 or IsPySide):
|
||||
raise ImportError
|
||||
if QCoreApplication.instance() is None:
|
||||
self.app = QtWidgets.QApplication(sys.argv)
|
||||
if isWindows():
|
||||
try:
|
||||
from syncplay.vendor import darkdetect
|
||||
isDarkMode = darkdetect.isDark()
|
||||
except:
|
||||
isDarkMode = False
|
||||
if isDarkMode:
|
||||
self.app.setStyle(QtWidgets.QStyleFactory.create("fusion"))
|
||||
self.app.setPalette(self.getDarkPalette(QtGui))
|
||||
qt5reactor.install()
|
||||
if isMacOS():
|
||||
import appnope
|
||||
@ -550,6 +559,29 @@ class ConfigurationGetter(object):
|
||||
setLanguage(self._config['language'])
|
||||
return self._config
|
||||
|
||||
def getDarkPalette(self, QtGui):
|
||||
# Based on https://gist.github.com/lschmierer/443b8e21ad93e2a2d7eb#gistcomment-3503395
|
||||
darkPalette = QtGui.QPalette()
|
||||
darkPalette.setColor(QtGui.QPalette.Window, QtGui.QColor(53, 53, 53))
|
||||
darkPalette.setColor(QtGui.QPalette.WindowText, QtGui.QColor(255, 255, 255))
|
||||
darkPalette.setColor(QtGui.QPalette.Base, QtGui.QColor(35, 35, 35))
|
||||
darkPalette.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(53, 53, 53))
|
||||
darkPalette.setColor(QtGui.QPalette.ToolTipBase, QtGui.QColor(25, 25, 25))
|
||||
darkPalette.setColor(QtGui.QPalette.ToolTipText, QtGui.QColor(255, 255, 255))
|
||||
darkPalette.setColor(QtGui.QPalette.Text, QtGui.QColor(255, 255, 255))
|
||||
darkPalette.setColor(QtGui.QPalette.Button, QtGui.QColor(53, 53, 53))
|
||||
darkPalette.setColor(QtGui.QPalette.ButtonText, QtGui.QColor(255, 255, 255))
|
||||
darkPalette.setColor(QtGui.QPalette.BrightText, QtGui.QColor(255, 0, 0))
|
||||
darkPalette.setColor(QtGui.QPalette.Link, QtGui.QColor(42, 130, 218))
|
||||
darkPalette.setColor(QtGui.QPalette.Highlight, QtGui.QColor(42, 130, 218))
|
||||
darkPalette.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor(35, 35, 35))
|
||||
darkPalette.setColor(QtGui.QPalette.Active, QtGui.QPalette.Button, QtGui.QColor(53, 53, 53))
|
||||
darkPalette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, QtGui.QColor(128, 128, 128))
|
||||
darkPalette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, QtGui.QColor(128, 128, 128))
|
||||
darkPalette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Text, QtGui.QColor(128, 128, 128))
|
||||
darkPalette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Light, QtGui.QColor(53, 53, 53))
|
||||
return darkPalette
|
||||
|
||||
def setConfigOption(self, option, value):
|
||||
path = self._getConfigurationFilePath()
|
||||
backup = self._config.copy()
|
||||
|
||||
@ -9,7 +9,7 @@ from syncplay import utils
|
||||
from syncplay.messages import getMessage, getLanguages, setLanguage, getInitialLanguage
|
||||
from syncplay.players.playerFactory import PlayerFactory
|
||||
from syncplay.utils import isBSD, isLinux, isMacOS, isWindows
|
||||
from syncplay.utils import resourcespath, posixresourcespath
|
||||
from syncplay.utils import resourcespath, posixresourcespath, playerPathExists
|
||||
|
||||
from syncplay.vendor.Qt import QtCore, QtWidgets, QtGui, __binding__, IsPySide, IsPySide2
|
||||
from syncplay.vendor.Qt.QtCore import Qt, QSettings, QCoreApplication, QSize, QPoint, QUrl, QLine, QEventLoop, Signal
|
||||
@ -177,6 +177,7 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
RoomsLayout.addWidget(RoomsButtonBox, 2, 0, 1, 1)
|
||||
RoomsDialog.setLayout(RoomsLayout)
|
||||
RoomsDialog.setModal(True)
|
||||
RoomsDialog.setWindowFlags(RoomsDialog.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
||||
RoomsDialog.show()
|
||||
result = RoomsDialog.exec_()
|
||||
if result == QtWidgets.QDialog.Accepted:
|
||||
@ -209,12 +210,14 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
self.executablepathCombobox.addItem(foundpath)
|
||||
|
||||
else:
|
||||
if not os.path.isfile(playerpath):
|
||||
if not playerPathExists(playerpath):
|
||||
expandedpath = PlayerFactory().getExpandedPlayerPathByPath(playerpath)
|
||||
if expandedpath is not None and os.path.isfile(expandedpath):
|
||||
if expandedpath is not None and playerPathExists(expandedpath):
|
||||
playerpath = expandedpath
|
||||
elif "mpvnet.exe" in playerpath and playerPathExists(playerpath.replace("mpvnet.exe","mpvnet.com")):
|
||||
self.executablepathCombobox.addItem(playerpath)
|
||||
|
||||
if os.path.isfile(playerpath):
|
||||
if playerPathExists(playerpath):
|
||||
foundpath = playerpath
|
||||
self.executablepathCombobox.addItem(foundpath)
|
||||
|
||||
@ -225,7 +228,7 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
if path != playerpath:
|
||||
self.executablepathCombobox.addItem(path)
|
||||
|
||||
elif os.path.isfile(path) and os.path.normcase(os.path.normpath(path)) != os.path.normcase(os.path.normpath(foundpath)):
|
||||
elif playerPathExists(path) and os.path.normcase(os.path.normpath(path)) != os.path.normcase(os.path.normpath(foundpath)):
|
||||
self.executablepathCombobox.addItem(path)
|
||||
if foundpath == "":
|
||||
foundpath = path
|
||||
@ -655,6 +658,9 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
self.serverpassLabel = QLabel(getMessage("password-label"), self)
|
||||
self.roomsCombobox = QtWidgets.QComboBox(self)
|
||||
self.roomsCombobox.setEditable(True)
|
||||
caseSensitiveCompleter = QtWidgets.QCompleter("", self)
|
||||
caseSensitiveCompleter.setCaseSensitivity(Qt.CaseSensitive)
|
||||
self.roomsCombobox.setCompleter(caseSensitiveCompleter)
|
||||
self.fillRoomsCombobox()
|
||||
self.roomsCombobox.setEditText(config['room'])
|
||||
self.usernameLabel = QLabel(getMessage("name-label"), self)
|
||||
@ -665,7 +671,6 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
self.editRoomsButton.setIcon(QtGui.QIcon(resourcespath + 'bullet_edit_centered.png'))
|
||||
self.editRoomsButton.setObjectName(constants.LOAD_SAVE_MANUALLY_MARKER + "edit-rooms")
|
||||
self.editRoomsButton.released.connect(self.openRoomsDialog)
|
||||
|
||||
self.hostLabel.setObjectName("host")
|
||||
self.hostCombobox.setObjectName(constants.LOAD_SAVE_MANUALLY_MARKER + "host")
|
||||
self.usernameLabel.setObjectName("name")
|
||||
|
||||
@ -18,6 +18,7 @@ class ConsoleUI(threading.Thread):
|
||||
self.PromptResult = ""
|
||||
self.promptMode.set()
|
||||
self._syncplayClient = None
|
||||
self.uiMode = constants.CONSOLE_UI_MODE
|
||||
threading.Thread.__init__(self, name="ConsoleUI")
|
||||
|
||||
def addClient(self, client):
|
||||
@ -193,8 +194,10 @@ class ConsoleUI(threading.Thread):
|
||||
if filename is None:
|
||||
self.showErrorMessage("No file/url given")
|
||||
return
|
||||
|
||||
self._syncplayClient.ui.addFileToPlaylist(filename)
|
||||
elif command.group('command') in constants.COMMANDS_QUEUEANDSELECT:
|
||||
self._syncplayClient.playlist.switchToNewPlaylistItem = True
|
||||
self.executeCommand("{} {}".format(constants.COMMANDS_QUEUE[0], command.group('parameter')))
|
||||
elif command.group('command') in constants.COMMANDS_PLAYLIST:
|
||||
playlist = self._syncplayClient.playlist
|
||||
playlist_elements = ["\t{}: {}".format(i+1, el) for i, el in enumerate(playlist._playlist)]
|
||||
@ -237,12 +240,14 @@ class ConsoleUI(threading.Thread):
|
||||
self.showMessage(getMessage("commandlist-notification/undo"), True)
|
||||
self.showMessage(getMessage("commandlist-notification/pause"), True)
|
||||
self.showMessage(getMessage("commandlist-notification/seek"), True)
|
||||
self.showMessage(getMessage("commandlist-notification/offset"), True)
|
||||
self.showMessage(getMessage("commandlist-notification/help"), True)
|
||||
self.showMessage(getMessage("commandlist-notification/toggle"), True)
|
||||
self.showMessage(getMessage("commandlist-notification/create"), True)
|
||||
self.showMessage(getMessage("commandlist-notification/auth"), True)
|
||||
self.showMessage(getMessage("commandlist-notification/chat"), True)
|
||||
self.showMessage(getMessage("commandList-notification/queue"), True)
|
||||
self.showMessage(getMessage("commandList-notification/queueandselect"), True)
|
||||
self.showMessage(getMessage("commandList-notification/playlist"), True)
|
||||
self.showMessage(getMessage("commandList-notification/select"), True)
|
||||
self.showMessage(getMessage("commandList-notification/delete"), True)
|
||||
|
||||
@ -40,7 +40,7 @@ if isMacOS() and IsPySide:
|
||||
from Cocoa import NSString, NSUTF8StringEncoding
|
||||
lastCheckedForUpdates = None
|
||||
from syncplay.vendor import darkdetect
|
||||
if isMacOS():
|
||||
if isMacOS() or isWindows():
|
||||
isDarkMode = darkdetect.isDark()
|
||||
else:
|
||||
isDarkMode = None
|
||||
@ -468,6 +468,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.roomsCombobox.clear()
|
||||
for roomListValue in self.config['roomList']:
|
||||
self.roomsCombobox.addItem(roomListValue)
|
||||
for room in self.currentRooms:
|
||||
if room not in self.config['roomList']:
|
||||
self.roomsCombobox.addItem(room)
|
||||
self.roomsCombobox.setEditText(previousRoomSelection)
|
||||
|
||||
def addRoomToList(self, newRoom=None):
|
||||
@ -603,7 +606,16 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
):
|
||||
self._syncplayClient.fileSwitch.setCurrentDirectory(os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"]))
|
||||
|
||||
self.currentRooms = []
|
||||
for room in rooms:
|
||||
self.currentRooms.append(room)
|
||||
if self.hideEmptyRooms:
|
||||
foundEmptyRooms = False
|
||||
for user in rooms[room]:
|
||||
if user.username.strip() == "":
|
||||
foundEmptyRooms = True
|
||||
if foundEmptyRooms:
|
||||
continue
|
||||
self.newWatchlist = []
|
||||
roomitem = QtGui.QStandardItem(room)
|
||||
font = QtGui.QFont()
|
||||
@ -624,6 +636,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
roomitem.setIcon(QtGui.QPixmap(resourcespath + 'chevrons_right.png'))
|
||||
|
||||
for user in rooms[room]:
|
||||
if user.username.strip() == "":
|
||||
continue
|
||||
useritem = QtGui.QStandardItem(user.username)
|
||||
isController = user.isController()
|
||||
sameRoom = room == currentUser.room
|
||||
@ -695,6 +709,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.listTreeView.expandAll()
|
||||
self.updateListGeometry()
|
||||
self._syncplayClient.fileSwitch.setFilenameWatchlist(self.newWatchlist)
|
||||
self.fillRoomsCombobox()
|
||||
|
||||
@needsClient
|
||||
def undoPlaylistChange(self):
|
||||
@ -731,7 +746,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
lambda: utils.open_system_file_browser(pathFound))
|
||||
if self._syncplayClient.isUntrustedTrustableURI(firstFile):
|
||||
domain = utils.getDomainFromURL(firstFile)
|
||||
menu.addAction(QtGui.QPixmap(resourcespath + "shield_add.png"), getMessage("addtrusteddomain-menu-label").format(domain), lambda: self.addTrustedDomain(domain))
|
||||
if domain:
|
||||
menu.addAction(QtGui.QPixmap(resourcespath + "shield_add.png"), getMessage("addtrusteddomain-menu-label").format(domain), lambda: self.addTrustedDomain(domain))
|
||||
menu.addAction(QtGui.QPixmap(resourcespath + "delete.png"), getMessage("removefromplaylist-menu-label"), lambda: self.deleteSelectedPlaylistItems())
|
||||
menu.addSeparator()
|
||||
menu.addAction(QtGui.QPixmap(resourcespath + "arrow_switch.png"), getMessage("shuffleremainingplaylist-menu-label"), lambda: self.shuffleRemainingPlaylist())
|
||||
@ -794,7 +810,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), getMessage("openusersfile-menu-label").format(shortUsername), lambda: self.openFile(pathFound, resetPosition=False, fromUser=True))
|
||||
if self._syncplayClient.isUntrustedTrustableURI(filename):
|
||||
domain = utils.getDomainFromURL(filename)
|
||||
menu.addAction(QtGui.QPixmap(resourcespath + "shield_add.png"), getMessage("addtrusteddomain-menu-label").format(domain), lambda: self.addTrustedDomain(domain))
|
||||
if domain:
|
||||
menu.addAction(QtGui.QPixmap(resourcespath + "shield_add.png"), getMessage("addtrusteddomain-menu-label").format(domain), lambda: self.addTrustedDomain(domain))
|
||||
|
||||
if not isURL(filename) and filename != getMessage("nofile-note"):
|
||||
path = self._syncplayClient.fileSwitch.findFilepath(filename)
|
||||
@ -1097,7 +1114,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
defaultdirectory = currentdirectory
|
||||
else:
|
||||
defaultdirectory = self.getInitialMediaDirectory()
|
||||
browserfilter = "Playlists (*.m3u *.m3u8 *.txt)"
|
||||
browserfilter = "Playlists (*.txt *.m3u8)"
|
||||
filepath, filtr = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self, "Load playlist from file", defaultdirectory,
|
||||
browserfilter, "", options) # TODO: Note Shuffle and move to messages_en
|
||||
@ -1118,7 +1135,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
defaultdirectory = currentdirectory
|
||||
else:
|
||||
defaultdirectory = self.getInitialMediaDirectory()
|
||||
browserfilter = "Playlist (*.m3u8 *.m3u *.txt)"
|
||||
browserfilter = "Playlist (*.txt)"
|
||||
filepath, filtr = QtWidgets.QFileDialog.getSaveFileName(
|
||||
self, "Save playlist to file", defaultdirectory,
|
||||
browserfilter, "", options) # TODO: Move to messages_en
|
||||
@ -1143,6 +1160,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
URIsLayout.addWidget(URIsButtonBox, 2, 0, 1, 1)
|
||||
URIsDialog.setLayout(URIsLayout)
|
||||
URIsDialog.setModal(True)
|
||||
URIsDialog.setWindowFlags(URIsDialog.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
||||
URIsDialog.show()
|
||||
result = URIsDialog.exec_()
|
||||
if result == QtWidgets.QDialog.Accepted:
|
||||
@ -1173,6 +1191,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
RoomsLayout.addWidget(RoomsButtonBox, 2, 0, 1, 1)
|
||||
RoomsDialog.setLayout(RoomsLayout)
|
||||
RoomsDialog.setModal(True)
|
||||
RoomsDialog.setWindowFlags(RoomsDialog.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
||||
RoomsDialog.show()
|
||||
result = RoomsDialog.exec_()
|
||||
if result == QtWidgets.QDialog.Accepted:
|
||||
@ -1207,6 +1226,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
editPlaylistDialog.setModal(True)
|
||||
editPlaylistDialog.setMinimumWidth(600)
|
||||
editPlaylistDialog.setMinimumHeight(500)
|
||||
editPlaylistDialog.setWindowFlags(editPlaylistDialog.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
||||
editPlaylistDialog.show()
|
||||
result = editPlaylistDialog.exec_()
|
||||
if result == QtWidgets.QDialog.Accepted:
|
||||
@ -1237,6 +1257,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
MediaDirectoriesAddFolderButton.pressed.connect(lambda: self.openAddMediaDirectoryDialog(MediaDirectoriesTextbox, MediaDirectoriesDialog))
|
||||
MediaDirectoriesLayout.addWidget(MediaDirectoriesAddFolderButton, 1, 1, 1, 1, Qt.AlignTop)
|
||||
MediaDirectoriesDialog.setLayout(MediaDirectoriesLayout)
|
||||
MediaDirectoriesDialog.setWindowFlags(MediaDirectoriesDialog.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
||||
MediaDirectoriesDialog.setModal(True)
|
||||
MediaDirectoriesDialog.show()
|
||||
result = MediaDirectoriesDialog.exec_()
|
||||
@ -1262,6 +1283,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
TrustedDomainsButtonBox.rejected.connect(TrustedDomainsDialog.reject)
|
||||
TrustedDomainsLayout.addWidget(TrustedDomainsButtonBox, 2, 0, 1, 1)
|
||||
TrustedDomainsDialog.setLayout(TrustedDomainsLayout)
|
||||
TrustedDomainsDialog.setWindowFlags(TrustedDomainsDialog.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
||||
TrustedDomainsDialog.setModal(True)
|
||||
TrustedDomainsDialog.show()
|
||||
result = TrustedDomainsDialog.exec_()
|
||||
@ -1489,6 +1511,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
window.listLayout.addWidget(window.listSplit)
|
||||
window.roomsCombobox = QtWidgets.QComboBox(self)
|
||||
window.roomsCombobox.setEditable(True)
|
||||
caseSensitiveCompleter = QtWidgets.QCompleter("", self)
|
||||
caseSensitiveCompleter.setCaseSensitivity(Qt.CaseSensitive)
|
||||
window.roomsCombobox.setCompleter(caseSensitiveCompleter)
|
||||
#window.roomsCombobox.setMaxLength(constants.MAX_ROOM_NAME_LENGTH)
|
||||
window.roomButton = QtWidgets.QPushButton(
|
||||
QtGui.QPixmap(resourcespath + 'door_in.png'),
|
||||
@ -1743,6 +1768,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
window.autoplayAction.setCheckable(True)
|
||||
window.autoplayAction.triggered.connect(self.updateAutoplayVisibility)
|
||||
|
||||
window.hideEmptyRoomsAction = window.windowMenu.addAction(getMessage("hideemptyrooms-menu-label"))
|
||||
window.hideEmptyRoomsAction.setCheckable(True)
|
||||
window.hideEmptyRoomsAction.triggered.connect(self.updateEmptyRoomVisiblity)
|
||||
|
||||
# Help menu
|
||||
|
||||
@ -1808,6 +1836,11 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
def updateAutoplayVisibility(self):
|
||||
self.autoplayFrame.setVisible(self.autoplayAction.isChecked())
|
||||
|
||||
def updateEmptyRoomVisiblity(self):
|
||||
self.hideEmptyRooms = self.hideEmptyRoomsAction.isChecked()
|
||||
if self._syncplayClient:
|
||||
self._syncplayClient.getUserList()
|
||||
|
||||
def changeReadyState(self):
|
||||
self.updateReadyIcon()
|
||||
if self._syncplayClient:
|
||||
@ -2007,6 +2040,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
settings.setValue("pos", self.pos())
|
||||
settings.setValue("showPlaybackButtons", self.playbackAction.isChecked())
|
||||
settings.setValue("showAutoPlayButton", self.autoplayAction.isChecked())
|
||||
settings.setValue("hideEmptyRooms", self.hideEmptyRoomsAction.isChecked())
|
||||
settings.setValue("autoplayChecked", self.autoplayPushButton.isChecked())
|
||||
settings.setValue("autoplayMinUsers", self.autoplayThresholdSpinbox.value())
|
||||
settings.endGroup()
|
||||
@ -2023,13 +2057,21 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
settings = QSettings("Syncplay", "MainWindow")
|
||||
settings.beginGroup("MainWindow")
|
||||
self.resize(settings.value("size", QSize(700, 500)))
|
||||
self.move(settings.value("pos", QPoint(200, 200)))
|
||||
movePos = settings.value("pos", QPoint(200, 200))
|
||||
windowGeometry = QtWidgets.QApplication.desktop().availableGeometry(self)
|
||||
posIsOnScreen = windowGeometry.contains(QtCore.QRect(movePos.x(), movePos.y(), 1, 1))
|
||||
if not posIsOnScreen:
|
||||
movePos = QPoint(200,200)
|
||||
self.move(movePos)
|
||||
if settings.value("showPlaybackButtons", "false") == "true":
|
||||
self.playbackAction.setChecked(True)
|
||||
self.updatePlaybackFrameVisibility()
|
||||
if settings.value("showAutoPlayButton", "false") == "true":
|
||||
self.autoplayAction.setChecked(True)
|
||||
self.updateAutoplayVisibility()
|
||||
if settings.value("hideEmptyRooms", "false") == "true":
|
||||
self.hideEmptyRooms = True
|
||||
self.hideEmptyRoomsAction.setChecked(True)
|
||||
if settings.value("autoplayChecked", "false") == "true":
|
||||
self.updateAutoPlayState(True)
|
||||
self.autoplayPushButton.setChecked(True)
|
||||
@ -2053,6 +2095,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.lastCheckedForUpdates = None
|
||||
self._syncplayClient = None
|
||||
self.folderSearchEnabled = True
|
||||
self.hideEmptyRooms = False
|
||||
self.currentRooms = []
|
||||
self.QtGui = QtGui
|
||||
if isMacOS():
|
||||
self.setWindowFlags(self.windowFlags())
|
||||
@ -2071,3 +2115,4 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.show()
|
||||
self.setAcceptDrops(True)
|
||||
self.clearedPlaylistNote = False
|
||||
self.uiMode = constants.GRAPHICAL_UI_MODE
|
||||
|
||||
@ -232,8 +232,9 @@ def truncateText(unicodeText, maxLength):
|
||||
unicodeText = unicodeText.decode('utf-8')
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
return(str(unicodeText.encode("utf-8"), "utf-8", errors="ignore")[:maxLength])
|
||||
return str(unicodeText.encode("utf-8"), "utf-8", errors="ignore")[:maxLength]
|
||||
except:
|
||||
pass
|
||||
return ""
|
||||
@ -395,12 +396,15 @@ def playlistIsValid(files):
|
||||
|
||||
def getDomainFromURL(URL):
|
||||
try:
|
||||
URL = URL.split("//")[-1].split("/")[0]
|
||||
if URL.startswith("www."):
|
||||
URL = URL[4:]
|
||||
return URL
|
||||
except:
|
||||
o = urllib.parse.urlparse(URL)
|
||||
except ValueError:
|
||||
# not a URL
|
||||
return None
|
||||
if o.hostname is not None and o.hostname.startswith("www."):
|
||||
return o.hostname[4:]
|
||||
else:
|
||||
# may return None if URL does not have domain (invalid url)
|
||||
return o.hostname
|
||||
|
||||
|
||||
def open_system_file_browser(path):
|
||||
@ -414,6 +418,13 @@ def open_system_file_browser(path):
|
||||
else:
|
||||
subprocess.Popen(["xdg-open", path])
|
||||
|
||||
def playerPathExists(path):
|
||||
if os.path.isfile(path):
|
||||
return True
|
||||
elif "mpvnet.exe" in path and os.path.isfile(path.replace("mpvnet.exe","mpvnet.com")):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def getListOfPublicServers():
|
||||
try:
|
||||
|
||||
20
syncplay/vendor/darkdetect/__init__.py
vendored
Executable file → Normal file
20
syncplay/vendor/darkdetect/__init__.py
vendored
Executable file → Normal file
@ -4,19 +4,29 @@
|
||||
# Distributed under the terms of the 3-clause BSD License.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
__version__ = '0.1.1'
|
||||
__version__ = '0.5.2'
|
||||
|
||||
import sys
|
||||
import platform
|
||||
|
||||
if sys.platform != "darwin":
|
||||
from ._dummy import *
|
||||
else:
|
||||
if sys.platform == "darwin":
|
||||
from distutils.version import LooseVersion as V
|
||||
if V(platform.mac_ver()[0]) < V("10.14"):
|
||||
from ._dummy import *
|
||||
else:
|
||||
from ._detect import *
|
||||
from ._mac_detect import *
|
||||
del V
|
||||
elif sys.platform == "win32" and int(platform.release()) >= 10:
|
||||
# Checks if running Windows 10 version 10.0.14393 (Anniversary Update) OR HIGHER. The getwindowsversion method returns a tuple.
|
||||
# The third item is the build number that we can use to check if the user has a new enough version of Windows.
|
||||
winver = int(platform.version().split('.')[2])
|
||||
if winver >= 14393:
|
||||
from ._windows_detect import *
|
||||
else:
|
||||
from ._dummy import *
|
||||
elif sys.platform == "linux":
|
||||
from ._linux_detect import *
|
||||
else:
|
||||
from ._dummy import *
|
||||
|
||||
del sys, platform
|
||||
0
syncplay/vendor/darkdetect/_dummy.py
vendored
Executable file → Normal file
0
syncplay/vendor/darkdetect/_dummy.py
vendored
Executable file → Normal file
30
syncplay/vendor/darkdetect/_linux_detect.py
vendored
Normal file
30
syncplay/vendor/darkdetect/_linux_detect.py
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (C) 2019 Alberto Sottile, Eric Larson
|
||||
#
|
||||
# Distributed under the terms of the 3-clause BSD License.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
import subprocess
|
||||
|
||||
|
||||
def theme():
|
||||
# Here we just triage to GTK settings for now
|
||||
try:
|
||||
out = subprocess.run(
|
||||
['gsettings', 'get', 'org.gnome.desktop.interface', 'gtk-theme'],
|
||||
capture_output=True)
|
||||
stdout = out.stdout.decode()
|
||||
except Exception:
|
||||
return 'Light'
|
||||
# we have a string, now remove start and end quote
|
||||
theme = stdout.lower().strip()[1:-1]
|
||||
if theme.endswith('-dark'):
|
||||
return 'Dark'
|
||||
else:
|
||||
return 'Light'
|
||||
|
||||
def isDark():
|
||||
return theme() == 'Dark'
|
||||
|
||||
def isLight():
|
||||
return theme() == 'Light'
|
||||
16
syncplay/vendor/darkdetect/_detect.py → syncplay/vendor/darkdetect/_mac_detect.py
vendored
Executable file → Normal file
16
syncplay/vendor/darkdetect/_detect.py → syncplay/vendor/darkdetect/_mac_detect.py
vendored
Executable file → Normal file
@ -7,18 +7,24 @@
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
|
||||
appkit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('AppKit'))
|
||||
objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc'))
|
||||
try:
|
||||
# macOS Big Sur+ use "a built-in dynamic linker cache of all system-provided libraries"
|
||||
appkit = ctypes.cdll.LoadLibrary('AppKit.framework/AppKit')
|
||||
objc = ctypes.cdll.LoadLibrary('libobjc.dylib')
|
||||
except OSError:
|
||||
# revert to full path for older OS versions and hardened programs
|
||||
appkit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('AppKit'))
|
||||
objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc'))
|
||||
|
||||
void_p = ctypes.c_void_p
|
||||
ull = ctypes.c_uint64
|
||||
|
||||
objc.objc_getClass.restype = void_p
|
||||
objc.sel_registerName.restype = void_p
|
||||
objc.objc_msgSend.restype = void_p
|
||||
objc.objc_msgSend.argtypes = [void_p, void_p]
|
||||
|
||||
msg = objc.objc_msgSend
|
||||
# See https://docs.python.org/3/library/ctypes.html#function-prototypes for arguments description
|
||||
MSGPROTOTYPE = ctypes.CFUNCTYPE(void_p, void_p, void_p, void_p)
|
||||
msg = MSGPROTOTYPE(('objc_msgSend', objc), ((1 ,'', None), (1, '', None), (1, '', None)))
|
||||
|
||||
def _utf8(s):
|
||||
if not isinstance(s, bytes):
|
||||
24
syncplay/vendor/darkdetect/_windows_detect.py
vendored
Normal file
24
syncplay/vendor/darkdetect/_windows_detect.py
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
from winreg import HKEY_CURRENT_USER as hkey, QueryValueEx as getSubkeyValue, OpenKey as getKey
|
||||
|
||||
def theme():
|
||||
""" Uses the Windows Registry to detect if the user is using Dark Mode """
|
||||
# Registry will return 0 if Windows is in Dark Mode and 1 if Windows is in Light Mode. This dictionary converts that output into the text that the program is expecting.
|
||||
valueMeaning = {0: "Dark", 1: "Light"}
|
||||
# In HKEY_CURRENT_USER, get the Personalisation Key.
|
||||
try:
|
||||
key = getKey(hkey, "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize")
|
||||
# In the Personalisation Key, get the AppsUseLightTheme subkey. This returns a tuple.
|
||||
# The first item in the tuple is the result we want (0 or 1 indicating Dark Mode or Light Mode); the other value is the type of subkey e.g. DWORD, QWORD, String, etc.
|
||||
subkey = getSubkeyValue(key, "AppsUseLightTheme")[0]
|
||||
except FileNotFoundError:
|
||||
# some headless Windows instances (e.g. GitHub Actions or Docker images) do not have this key
|
||||
return None
|
||||
return valueMeaning[subkey]
|
||||
|
||||
def isDark():
|
||||
if theme() is not None:
|
||||
return theme() == 'Dark'
|
||||
|
||||
def isLight():
|
||||
if theme() is not None:
|
||||
return theme() == 'Light'
|
||||
@ -426,7 +426,6 @@ class MPV:
|
||||
try:
|
||||
command_list = [x["name"] for x in self.command("get_property", "command-list")]
|
||||
except MPVError:
|
||||
log.warning("Using fallback command list.")
|
||||
command_list = FALLBACK_COMMAND_LIST
|
||||
for command in command_list:
|
||||
object.__setattr__(self, command.replace("-", "_"), self._get_wrapper(command))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user