diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 7cb048b..0000000 --- a/.appveyor.yml +++ /dev/null @@ -1,54 +0,0 @@ -environment: - MINICONDA: "C:\\Miniconda" - clone_folder: c:\projects\syncplay - -image: - - Visual Studio 2013 - -platform: x86 - -configuration: Release - -init: - - set PATH=C:\Miniconda;C:\Miniconda\Scripts;%PATH% - - cmd: conda create -n syncplay -y - - cmd: activate syncplay - - cmd: conda install python pywin32 pyside -y - - cmd: pip install twisted py2exe_py2 zope.interface - - cmd: type nul > C:\Miniconda\envs\syncplay\lib\site-packages\zope\__init__.py - - cmd: pip freeze - - cmd: conda list - -install: - - cmd: cd c:\projects\syncplay - - cmd: python buildPy2exe.py - - cmd: del C:\projects\syncplay\syncplay_v1.5.0\lib\DNSAPI.dll - - cmd: del C:\projects\syncplay\syncplay_v1.5.0\lib\MPR.dll - - cmd: mkdir C:\projects\syncplay\syncplay_v1.5.0\platforms - #- cmd: copy C:\Miniconda\envs\syncplay\library\plugins\platforms\qwindows.dll C:\projects\syncplay\syncplay_v1.5.0\platforms\ - -# Not a project with an msbuild file, build done at install. -build: off - -artifacts: - path: 'syncplay_v1.5.0' - type: zip - name: Syncplay_chat-osd_win - -# Push artefact to S3 bucket and list all -before_deploy: - - cmd: dir - #- cmd: python -c "from PySide2 import QtCore; print QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.PluginsPath)" - -# Deploy build to BinTray -deploy: - provider: BinTray - username: alby128 - api_key: - secure: lAocj5KA9Z9x4BefQBIgNlQJbeW4qPBfCgYVBHMyOP3NgyhnMLmvR57ZCqtCKBlQ - subject: Syncplay - repo: Syncplay - package: Syncplay - version: "test" - publish: true - override: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8309fad..6335c67 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,3 @@ syncplay_setup.nsi dist.7z .* !.travis.yml -!.appveyor.yml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/appdmg.py b/appdmg.py index 58112cf..3e2dc8c 100644 --- a/appdmg.py +++ b/appdmg.py @@ -27,10 +27,10 @@ compression_level = 9 size = defines.get('size', None) # Files to include -files = [ application, 'resources/lua/intf/syncplay.lua', 'resources/.macos_vlc_install.command' ] +files = [ application, 'resources/lua/intf/.syncplay.lua', 'resources/.macos_vlc_install.command', 'resources/.macOS_readme.pdf' ] # Symlinks to create -symlinks = { 'Applications': '/Applications', 'Install in VLC': '.macos_vlc_install.command' } +symlinks = { 'Applications': '/Applications', 'Install for VLC': '.macos_vlc_install.command', 'Read Me': '.macOS_readme.pdf' } # Volume icon # @@ -43,10 +43,10 @@ badge_icon = icon_from_app(application) # Where to put the icons icon_locations = { - appname: (80, 80), - 'Applications': (280, 80), - 'syncplay.lua': (80, 240), - 'Install in VLC': (280, 240) + appname: (150, 110), + 'Applications': (450, 110), + 'Read Me': (100, 285), + 'Install for VLC': (500, 285) } # .. Window configuration ...................................................... @@ -71,7 +71,7 @@ icon_locations = { # # Other color components may be expressed either in the range 0 to 1, or # as percentages (e.g. 60% is equivalent to 0.6). -background = '#bacbe0' +background = 'resources/macOS_dmg_bkg.tiff' show_status_bar = False show_tab_view = False @@ -81,7 +81,7 @@ show_sidebar = False sidebar_width = 180 # Window position in ((x, y), (w, h)) format -window_rect = ((100, 100), (360, 400)) +window_rect = ((100, 100), (600, 460)) # Select the default view; must be one of # diff --git a/resources/macOS_dmg_bkg.tiff b/resources/macOS_dmg_bkg.tiff new file mode 100644 index 0000000..9ead2f6 Binary files /dev/null and b/resources/macOS_dmg_bkg.tiff differ diff --git a/resources/macOS_readme.pdf b/resources/macOS_readme.pdf new file mode 100644 index 0000000..f18c6db Binary files /dev/null and b/resources/macOS_readme.pdf differ diff --git a/resources/macos_vlc_install.command b/resources/macos_vlc_install.command index b03f213..4774144 100755 --- a/resources/macos_vlc_install.command +++ b/resources/macos_vlc_install.command @@ -2,4 +2,4 @@ mkdir -p $HOME/Library/Application\ Support/org.videolan.vlc/lua/intf/ -cp /Volumes/Syncplay/syncplay.lua $HOME/Library/Application\ Support/org.videolan.vlc/lua/intf/ +cp /Volumes/Syncplay/.syncplay.lua $HOME/Library/Application\ Support/org.videolan.vlc/lua/intf/syncplay.lua diff --git a/syncplay/__init__.py b/syncplay/__init__.py index d06a5e0..fc4ca02 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,4 +1,4 @@ version = '1.5.0' milestone = 'Yoitsu' -release_number = '40' +release_number = '45' projectURL = 'http://syncplay.pl/' diff --git a/syncplay/client.py b/syncplay/client.py index 4a53481..2d1b4c1 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -92,6 +92,8 @@ class SyncplayClient(object): if config['password']: config['password'] = hashlib.md5(config['password']).hexdigest() self._serverPassword = config['password'] + self._host = u"{}:{}".format(config['host'],config['port']) + self._publicServers = config["publicServers"] if not config['file']: self.__getUserlistOnLogon = True else: @@ -648,7 +650,20 @@ class SyncplayClient(object): self.userlist.showUserList(altUI) def getPassword(self): - return self._serverPassword + if self.thisIsPublicServer(): + return "" + else: + return self._serverPassword + + def thisIsPublicServer(self): + self._publicServers = [] + if self._publicServers and self._host in self._publicServers: + return True + i = 0 + for server in constants.FALLBACK_PUBLIC_SYNCPLAY_SERVERS: + if server[1] == self._host: + return True + i += 1 def setPosition(self, position): if self._lastPlayerUpdate: @@ -1620,11 +1635,23 @@ class SyncplayPlaylist(): self.changePlaylist(newPlaylist, username=None) @needsSharedPlaylistsEnabled - def shufflePlaylist(self): + def shuffleRemainingPlaylist(self): + if self._playlist and len(self._playlist) > 0: + shuffledPlaylist = deepcopy(self._playlist) + shufflePoint = self._playlistIndex + 1 + partToKeep = shuffledPlaylist[:shufflePoint] + partToShuffle = shuffledPlaylist[shufflePoint:] + random.shuffle(partToShuffle) + shuffledPlaylist = partToKeep + partToShuffle + self.changePlaylist(shuffledPlaylist, username=None, resetIndex=False) + + @needsSharedPlaylistsEnabled + def shuffleEntirePlaylist(self): if self._playlist and len(self._playlist) > 0: shuffledPlaylist = deepcopy(self._playlist) random.shuffle(shuffledPlaylist) self.changePlaylist(shuffledPlaylist, username=None, resetIndex=True) + self.switchToNewPlaylistIndex(0, resetPosition=True) def canUndoPlaylist(self, currentPlaylist): return self._previousPlaylist is not None and currentPlaylist <> self._previousPlaylist diff --git a/syncplay/constants.py b/syncplay/constants.py index 8efa7a5..41653cc 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -55,6 +55,7 @@ SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear PLAYLIST_MAX_CHARACTERS = 10000 PLAYLIST_MAX_ITEMS = 250 MAXIMUM_TAB_WIDTH = 350 +TAB_PADDING = 30 DEFAULT_WINDOWS_MONOSPACE_FONT = "Consolas" DEFAULT_OSX_MONOSPACE_FONT = "Menlo" FALLBACK_MONOSPACE_FONT = "Monospace" @@ -186,6 +187,7 @@ MPV_SYNCPLAYINTF_CONSTANTS_TO_SEND = ["MaxChatMessageLength={}".format(MAX_CHAT_ MPV_SYNCPLAYINTF_LANGUAGE_TO_SEND = ["mpv-key-hint", "alphakey-mode-warning-first-line", "alphakey-mode-warning-second-line"] VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek', '--play-and-pause', '--start-time=0'] +VLC_SLAVE_OSX_ARGS = ['--verbose=2', '--no-file-logging'] VLC_SLAVE_NONOSX_ARGS = ['--no-one-instance', '--no-one-instance-when-started-from-file'] MPV_SUPERSEDE_IF_DUPLICATE_COMMANDS = ["no-osd set time-pos ", "loadfile "] MPV_REMOVE_BOTH_IF_DUPLICATE_COMMANDS = ["cycle pause"] @@ -208,6 +210,8 @@ INPUT_POSITION_TOP = "Top" INPUT_POSITION_MIDDLE = "Middle" INPUT_POSITION_BOTTOM = "Bottom" +VLC_EOF_DURATION_THRESHOLD = 2.0 + PRIVACY_HIDDENFILENAME = "**Hidden filename**" INVERTED_STATE_MARKER = "*" ERROR_MESSAGE_MARKER = "*" diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 82717c1..c90d26b 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -93,6 +93,7 @@ de = { "language-changed-msgbox-label" : u"Die Sprache wird geändert, wenn du Syncplay neu startest.", "promptforupdate-label" : u"Soll Syncplay regelmäßig nach Updates suchen?", + "vlc-version-mismatch": u"This version of VLC does not support Syncplay. VLC {}+ supports Syncplay but VLC 3 does not. Please use an alternative media player.", # VLC min version # TODO: Translate "vlc-interface-version-mismatch": u"Du nutzt Version {} des VLC-Syncplay Interface-Moduls, Syncplay benötigt aber mindestens Version {}. In der Syncplay-Anleitung unter http://syncplay.pl/guide/ [Englisch] findest du Details zur Installation des syncplay.lua-Skripts.", # VLC interface version, VLC interface min version "vlc-interface-oldversion-warning": u"Warnung: Es ist eine alte Version des Syncplay Interface-Moduls für VLC im VLC-Verzeichnis installiert. In der Syncplay-Anleitung unter http://syncplay.pl/guide/ [Englisch] findest du Details zur Installation des syncplay.lua-Skripts.", "vlc-interface-not-installed": u"Warnung: Es wurde kein Syncplay Interface-Modul für VLC im VLC-Verzeichnis gefunden. Daher wird, wenn du VLC 2.0 nutzt, die syncplay.lua die mit Syncplay mitgeliefert wurde, verwendet. Dies bedeutet allerdings, dass keine anderen Interface-Skripts und Erweiterungen geladen werden. In der Syncplay-Anleitung unter http://syncplay.pl/guide/ [Englisch] findest du Details zur Installation des syncplay.lua-Skripts.", @@ -131,14 +132,9 @@ de = { "vlc-failed-connection": u"Kann nicht zu VLC verbinden. Wenn du syncplay.lua nicht installiert hast, findest du auf http://syncplay.pl/LUA/ [Englisch] eine Anleitung.", "vlc-failed-noscript": u"Laut VLC ist das syncplay.lua Interface-Skript nicht installiert. Auf http://syncplay.pl/LUA/ [Englisch] findest du eine Anleitung.", "vlc-failed-versioncheck": u"Diese VLC-Version wird von Syncplay nicht unterstützt. Bitte nutze VLC 2.0", + "vlc-failed-other" : u"Beim Laden des syncplay.lua Interface-Skripts durch VLC trat folgender Fehler auf: {}", # Syncplay Error - "feature-sharedPlaylists": u"shared playlists", # used for not-supported-by-server-error # TODO: Translate - "feature-chat": u"chat", # used for not-supported-by-server-error # TODO: Translate - "feature-readiness": u"readiness", # used for not-supported-by-server-error # TODO: Translate - "feature-managedRooms": u"managed rooms", # used for not-supported-by-server-error # TODO: Translate - - "not-supported-by-server-error": u"The {} feature is not supported by this server..", # feature # TODO: Translate - #OLD TRANSLATION: "not-supported-by-server-error" : u"Dieses Feature wird vom Server nicht unterstützt. Es wird ein Server mit Syncplay Version {}+ benötigt, aktuell verwendet wird jedoch Version {}.", #minVersion, serverVersion + "not-supported-by-server-error" : u"Dieses Feature wird vom Server nicht unterstützt. Es wird ein Server mit Syncplay Version {}+ benötigt, aktuell verwendet wird jedoch Version {}.", #minVersion, serverVersion "shared-playlists-not-supported-by-server-error" : "The shared playlists feature may not be supported by the server. To ensure that it works correctly requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion # TODO: Translate "shared-playlists-disabled-by-server-error" : "The shared playlist feature has been disabled in the server configuration. To use this feature you will need to connect to a different server.", # TODO: Translate @@ -185,7 +181,7 @@ de = { "media-setting-title" : u"Media-Player Einstellungen", "executable-path-label" : u"Pfad zum Media-Player:", - "media-path-label" : u"Pfad zur Datei:", + "media-path-label" : u"Pfad zur Datei:", # Todo: Translate to 'Path to video (optional)' "player-arguments-label" : u"Playerparameter:", "browse-label" : u"Durchsuchen", "update-server-list-label" : u"Liste aktualisieren", @@ -262,7 +258,7 @@ de = { "run-label" : u"Syncplay starten", "storeandrun-label" : u"Konfiguration speichern und Syncplay starten", - "contact-label" : u"Du hast eine Idee, einen Bug gefunden oder möchtest Feedback geben? Sende eine E-Mail an dev@syncplay.pl, chatte auf dem #Syncplay IRC-Kanal auf irc.freenode.net oder öffne eine Fehlermeldung auf GitHub. Außerdem findest du auf http://syncplay.pl/ weitere Informationen, Hilfestellungen und Updates. NOTE: Chat messages are not encrypted so do not use Syncplay to send sensitive information.", # TODO: Translate message at end + "contact-label" : u"Du hast eine Idee, einen Bug gefunden oder möchtest Feedback geben? Sende eine E-Mail an dev@syncplay.pl, chatte auf dem #Syncplay IRC-Kanal auf irc.freenode.net oder öffne eine Fehlermeldung auf GitHub. Außerdem findest du auf http://syncplay.pl/ weitere Informationen, Hilfestellungen und Updates. OTE: Chat messages are not encrypted so do not use Syncplay to send sensitive information.", # TODO: Translate last sentence "joinroom-label" : u"Raum beitreten", "joinroom-menu-label" : u"Raum beitreten {}", #TODO: Might want to fix this @@ -345,7 +341,7 @@ de = { "room-tooltip" : u"Der Raum, der betreten werden soll, kann ein x-beliebiger sein. Allerdings werden nur Clients im selben Raum synchronisiert.", "executable-path-tooltip" : u"Pfad zum ausgewählten, unterstützten Mediaplayer (MPC-HC, VLC, mplayer2 or mpv).", - "media-path-tooltip" : u"Pfad zum wiederzugebenden Video oder Stream. Notwendig für mpv und mplayer2.", + "media-path-tooltip" : u"Pfad zum wiederzugebenden Video oder Stream. Notwendig für mplayer2.", # TODO: Confirm translation "player-arguments-tooltip" : u"Zusätzliche Kommandozeilenparameter / -schalter für diesen Mediaplayer.", "mediasearcdirectories-arguments-tooltip" : u"Verzeichnisse, in denen Syncplay nach Mediendateien suchen soll, z.B. wenn du das Click-to-switch-Feature verwendest. Syncplay wird rekursiv Unterordner durchsuchen.", # TODO: Translate Click-to-switch? (or use as name for feature) @@ -451,7 +447,8 @@ de = { "cannot-add-unsafe-path-error" : u"Could not automatically load {} because it is not on a trusted domain. You can switch to the URL manually by double clicking it in the playlist, and add trusted domains via File->Advanced->Set Trusted Domains. If you right click on a URL then you can add its domain as a trusted domain via the context menu.", # Filename "sharedplaylistenabled-label" : u"Enable shared playlists", "removefromplaylist-menu-label" : u"Remove from playlist", - "shuffleplaylist-menuu-label" : u"Shuffle playlist", + "shuffleremainingplaylist-menu-label" : u"Shuffle remaining playlist", + "shuffleentireplaylist-menuu-label" : u"Shuffle entire playlist", "undoplaylist-menu-label" : u"Undo last change to playlist", "addfilestoplaylist-menu-label" : u"Add file(s) to bottom of playlist", "addurlstoplaylist-menu-label" : u"Add URL(s) to bottom of playlist", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 2525116..97d1698 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -109,7 +109,7 @@ en = { "mpc-version-insufficient-error" : "MPC version not sufficient, please use `mpc-hc` >= `{}`", "mpv-version-error" : "Syncplay is not compatible with this version of mpv. Please use a different version of mpv (e.g. Git HEAD).", "player-file-open-error" : "Player failed opening file", - "player-path-error" : "Player path is not set properly", + "player-path-error" : "Player path is not set properly. Supported players are: mpv, VLC, MPC-HC and mplayer2", "hostname-empty-error" : "Hostname can't be empty", "empty-error" : "{} can't be empty", # Configuration "media-player-error": "Media player error: \"{}\"", # Error line @@ -119,7 +119,7 @@ en = { "unable-to-start-client-error" : "Unable to start client", - "player-path-config-error": "Player path is not set properly", + "player-path-config-error": "Player path is not set properly. Supported players are: mpv, VLC, MPC-HC and mplayer2.", "no-file-path-config-error" :"File must be selected before starting your player", "no-hostname-config-error": "Hostname can't be empty", "invalid-port-config-error" : "Port must be valid", @@ -184,7 +184,7 @@ en = { "media-setting-title" : "Media player settings", "executable-path-label" : "Path to media player:", - "media-path-label" : "Path to media file:", + "media-path-label" : "Path to video (optional):", "player-arguments-label" : "Player arguments (if any):", "browse-label" : "Browse", "update-server-list-label" : u"Update list", @@ -345,8 +345,8 @@ en = { "password-tooltip" : "Passwords are only needed for connecting to private servers.", "room-tooltip" : "Room to join upon connection can be almost anything, but you will only be synchronised with people in the same room.", - "executable-path-tooltip" : "Location of your chosen supported media player (MPC-HC, VLC, mplayer2 or mpv).", - "media-path-tooltip" : "Location of video or stream to be opened. Necessary for mpv and mplayer2.", + "executable-path-tooltip" : "Location of your chosen supported media player (mpv, VLC, MPC-HC or mplayer2).", + "media-path-tooltip" : "Location of video or stream to be opened. Necessary for mplayer2.", "player-arguments-tooltip" : "Additional command line arguments / switches to pass on to this media player.", "mediasearcdirectories-arguments-tooltip" : u"Directories where Syncplay will search for media files, e.g. when you are using the click to switch feature. Syncplay will look recursively through sub-folders.", @@ -451,7 +451,8 @@ en = { "cannot-add-unsafe-path-error" : u"Could not automatically load {} because it is not on a trusted domain. You can switch to the URL manually by double clicking it in the playlist, and add trusted domains via File->Advanced->Set Trusted Domains. If you right click on a URL then you can add its domain as a trusted domain via the context menu.", # Filename "sharedplaylistenabled-label" : u"Enable shared playlists", "removefromplaylist-menu-label" : u"Remove from playlist", - "shuffleplaylist-menuu-label" : u"Shuffle playlist", + "shuffleremainingplaylist-menu-label" : u"Shuffle remaining playlist", + "shuffleentireplaylist-menuu-label" : u"Shuffle entire playlist", "undoplaylist-menu-label" : u"Undo last change to playlist", "addfilestoplaylist-menu-label" : u"Add file(s) to bottom of playlist", "addurlstoplaylist-menu-label" : u"Add URL(s) to bottom of playlist", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 604f4f0..6f5d7d9 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -93,6 +93,7 @@ ru = { "language-changed-msgbox-label" : u"Язык переключится при следующем запуске Syncplay.", "promptforupdate-label" : u"Вы не против, если Syncplay будет автоматически изредка проверять наличие обновлений?", + "vlc-version-mismatch": u"Syncplay не поддерживает данную версию VLC. Syncplay поддерживает VLC {}+, но не VLC 3. Используйте другой проигрыватель.", # VLC min version "vlc-interface-version-mismatch" : u"Вы используете модуль интерфейса Syncplay устаревшей версии {} для VLC. К сожалению, Syncplay способен работать с версией {} и выше. Пожалуйста, обратитесь к Руководству Пользователя Syncplay (http://syncplay.pl/guide/) за инструкциями о том, как установить syncplay.lua.", # VLC interface version, VLC interface min version "vlc-interface-oldversion-warning" : u"Внимание: Syncplay обнаружил, что старая версия модуля интерфейса Syncplay для VLC уже установлена в директорию VLC. Пожалуйста, обратитесь к Руководству Пользователя Syncplay (http://syncplay.pl/guide/) за инструкциями о том, как установить syncplay.lua.", "vlc-interface-not-installed" : u"Внимание: Модуль интерфейса Syncplay для VLC не обнаружен в директории VLC. По существу, если Вы используете VLC 2.0, то VLC будет использовать модуль syncplay.lua из директории Syncplay, но в таком случае другие пользовательские скрипты и расширения интерфейса не будут работать. Пожалуйста, обратитесь к Руководству Пользователя Syncplay (http://syncplay.pl/guide/) за инструкциями о том, как установить syncplay.lua.", @@ -109,7 +110,7 @@ ru = { "mpc-version-insufficient-error" : u"Версия MPC слишком старая, пожалуйста, используйте `mpc-hc` >= `{}`", "mpv-version-error" : u"Syncplay не совместим с данной версией mpv. Пожалуйста, используйте другую версию mpv (лучше свежайшую).", "player-file-open-error" : u"Проигрыватель не может открыть файл.", - "player-path-error" : u"Путь к проигрывателю задан неверно.", + "player-path-error" : u"Путь к проигрывателю задан неверно. Supported players are: mpv, VLC, MPC-HC and mplayer2.", # TODO: Translate last sentence "hostname-empty-error" : u"Имя пользователя не может быть пустым.", "empty-error" : u"{} не может быть пустым.", # Configuration "media-player-error" : u"Ошибка проигрывателя: \"{}\"", # Error line @@ -119,7 +120,7 @@ ru = { "unable-to-start-client-error" : u"Невозможно запустить клиент", - "player-path-config-error": u"Путь к проигрывателю установлен неверно", + "player-path-config-error": u"Путь к проигрывателю установлен неверно. Supported players are: mpv, VLC, MPC-HC and mplayer2", # To do: Translate end "no-file-path-config-error" : u"Файл должен быть указан до включения проигрывателя", "no-hostname-config-error": u"Имя сервера не может быть пустым", "invalid-port-config-error" : u"Неверный номер порта", @@ -131,6 +132,7 @@ ru = { "vlc-failed-connection" : u"Ошибка подключения к VLC. Если у Вас не установлен syncplay.lua, то обратитесь к http://syncplay.pl/LUA/ за инструкциями.", "vlc-failed-noscript" : u"VLC сообщает, что скрипт интерфейса syncplay.lua не установлен. Пожалуйста, обратитесь к http://syncplay.pl/LUA/ за инструкциями.", "vlc-failed-versioncheck" : u"Данная версия VLC не поддерживается Syncplay. Пожалуйста, используйте VLC версии 2 или выше.", + "vlc-failed-other" : u"Во время загрузки скрипта интерфейса syncplay.lua в VLC произошла следующая ошибка: {}", # Syncplay Error "feature-sharedPlaylists": u"shared playlists", # used for not-supported-by-server-error # TODO: Translate "feature-chat": u"chat", # used for not-supported-by-server-error # TODO: Translate @@ -185,7 +187,7 @@ ru = { "media-setting-title" : u"Воспроизведение", "executable-path-label" : u"Путь к проигрывателю:", - "media-path-label" : u"Путь к видеофайлу:", + "media-path-label" : u"Путь к видеофайлу:", # Todo: Translate to 'Path to video (optional)' "player-arguments-label" : u"Аргументы запуска проигрывателя:", "browse-label" : u"Выбрать", "update-server-list-label" : u"Обновить список", @@ -265,7 +267,7 @@ ru = { "run-label" : u"Запустить", "storeandrun-label" : u"Сохранить и запустить", - "contact-label" : u"Есть идея, нашли ошибку или хотите оставить отзыв? Пишите на dev@syncplay.pl, в IRC канал #Syncplay на irc.freenode.net или задавайте вопросы через GitHub. Кроме того, заходите на www.syncplay.pl за инорфмацией, помощью и обновлениями! NOTE: Chat messages are not encrypted so do not use Syncplay to send sensitive information.", # TODO: Translate end of message + "contact-label" : u"Есть идея, нашли ошибку или хотите оставить отзыв? Пишите на dev@syncplay.pl, в IRC канал #Syncplay на irc.freenode.net или задавайте вопросы через GitHub. Кроме того, заходите на www.syncplay.pl за инорфмацией, помощью и обновлениями! NOTE: Chat messages are not encrypted so do not use Syncplay to send sensitive information.", # TODO: Translate last sentence "joinroom-label" : u"Зайти в комнату", "joinroom-menu-label" : u"Зайти в комнату {}", @@ -347,7 +349,7 @@ ru = { "room-tooltip" : u"Комната, в которую Вы попадете сразу после подключения. Синхронизация возможна только между людьми в одной и той же комнате.", "executable-path-tooltip" : u"Расположение Вашего видеопроигрывателя (MPC-HC, VLC, mplayer2 или mpv).", - "media-path-tooltip" : u"Расположение видеофайла или потока для просмотра. Обязательно для mpv и mplayer2.", + "media-path-tooltip" : u"Расположение видеофайла или потока для просмотра. Обязательно для mplayer2.", # TODO: Confirm translation "player-arguments-tooltip" : u"Передавать дополнительные аргументы командной строки этому проигрывателю.", "mediasearcdirectories-arguments-tooltip" : u"Папки, где Syncplay будет искать медиа файлы, включая подпапки.", @@ -450,7 +452,8 @@ ru = { "cannot-add-unsafe-path-error" : u"Не удалось автоматически переключиться на {}, потому что ссылка не соответствует доверенным сайтам. Её можно включить вручную, дважны кливнув по ссылке в списке воспроизведения. Добавить доверенный сайт можно в выпадающем меню 'Дополнительно' или просто кликнув по ссылке правой кнопкой мыши.", # Filename "sharedplaylistenabled-label" : u"Включить общий список воспроизведения", "removefromplaylist-menu-label" : u"Удалить", - "shuffleplaylist-menuu-label" : u"Перемешать список", + "shufflepremaininglaylist-menuu-label" : u"Shuffle remaining playlist", # Was: Перемешать список # TODO: Translate + "shuffleentireplaylist-menuu-label" : u"Shuffle entire playlist", # TODO: Translate "undoplaylist-menu-label" : u"Отменить последнее действие", "addfilestoplaylist-menu-label" : u"Добавить файлы в очередь", "addurlstoplaylist-menu-label" : u"Добавить ссылку в очередь", diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index e8aac44..e34aa77 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -395,7 +395,12 @@ class MplayerPlayer(BasePlayer): for itemID, deletionCandidate in enumerate(self.sendQueue): if deletionCandidate.startswith(command): self.__playerController._client.ui.showDebugMessage(u" Remove duplicate (supersede): {}".format(self.sendQueue[itemID])) - self.sendQueue.remove(self.sendQueue[itemID]) + try: + self.sendQueue.remove(self.sendQueue[itemID]) + except UnicodeWarning: + self.__playerController._client.ui.showDebugMessage(u" Unicode mismatch occured when trying to remove duplicate") + # TODO: Prevent this from being triggered + pass break break if constants.MPV_REMOVE_BOTH_IF_DUPLICATE_COMMANDS: diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py old mode 100644 new mode 100755 index 8153e0e..d55b8d4 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -21,8 +21,10 @@ class VlcPlayer(BasePlayer): RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX) SLAVE_ARGS = constants.VLC_SLAVE_ARGS - if not sys.platform.startswith('darwin'): - SLAVE_ARGS.extend(constants.VLC_SLAVE_NONOSX_ARGS) + if sys.platform.startswith('darwin'): + SLAVE_ARGS.extend(constants.VLC_SLAVE_OSX_ARGS) + else: + SLAVE_ARGS.extend(constants.VLC_SLAVE_NONOSX_ARGS) vlcport = random.randrange(constants.VLC_MIN_PORT, constants.VLC_MAX_PORT) if (constants.VLC_MIN_PORT < constants.VLC_MAX_PORT) else constants.VLC_MIN_PORT def __init__(self, client, playerPath, filePath, args): @@ -207,18 +209,22 @@ class VlcPlayer(BasePlayer): self._durationAsk.set() elif name == "playstate": self._paused = bool(value != 'playing') if(value != "no-input" and self._filechanged == False) else self._client.getGlobalPaused() + diff = time.time() - self._lastVLCPositionUpdate if self._lastVLCPositionUpdate else 0 if self._paused == False \ and self._position == self._previousPreviousPosition \ and self._previousPosition == self._position \ and self._duration > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH \ - and self._position == self._duration: - self._paused = True + and (self._duration - self._position) < constants.VLC_EOF_DURATION_THRESHOLD \ + and diff > constants.VLC_LATENCY_ERROR_THRESHOLD: self._client.ui.showDebugMessage("Treating 'playing' response as 'paused' due to VLC EOF bug") + self.setPaused(True) self._pausedAsk.set() elif name == "position": newPosition = float(value.replace(",", ".")) if (value != "no-input" and self._filechanged == False) else self._client.getGlobalPosition() if newPosition == self._previousPosition and newPosition <> self._duration and not self._paused: self._client.ui.showDebugMessage("Not considering position {} duplicate as new time because of VLC time precision bug".format(newPosition)) + self._previousPreviousPosition = self._previousPosition + self._previousPosition = self._position self._positionAsk.set() return self._previousPreviousPosition = self._previousPosition @@ -382,7 +388,12 @@ class VlcPlayer(BasePlayer): playerController._client.ui.showErrorMessage( getMessage("media-player-error").format(line), True) break - self.__process.stderr = None + if not sys.platform.startswith('darwin'): + self.__process.stderr = None + else: + vlcoutputthread = threading.Thread(target = self.handle_vlcoutput, args=()) + vlcoutputthread.setDaemon(True) + vlcoutputthread.start() threading.Thread.__init__(self, name="VLC Listener") asynchat.async_chat.__init__(self) self.set_terminator("\n") @@ -428,6 +439,15 @@ class VlcPlayer(BasePlayer): asynchat.async_chat.handle_close(self) self.__playerController.drop(getMessage("vlc-failed-connection").format(constants.VLC_MIN_VERSION)) + def handle_vlcoutput(self): + out = self.__process.stderr + for line in iter(out.readline, ''): + if '[syncplay] core interface debug: removing module' in line: + self.__playerController.drop() + break + out.close() + + def found_terminator(self): self.vlcHasResponded = True self.__playerController.lineReceived("".join(self._ibuffer)) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 2ebe064..ba0e187 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -348,13 +348,19 @@ class SyncServerProtocol(JSONCommandProtocol): def _extractHelloArguments(self, hello): roomName = None - username = hello["username"] if hello.has_key("username") else None - username = username.strip() + if hello.has_key("username"): + username = hello["username"] + username = username.strip() + else: + username = None serverPassword = hello["password"] if hello.has_key("password") else None room = hello["room"] if hello.has_key("room") else None if room: - roomName = room["name"] if room.has_key("name") else None - roomName = roomName.strip() + if room.has_key("name"): + roomName = room["name"] + roomName = roomName.strip() + else: + roomName = None version = hello["version"] if hello.has_key("version") else None version = hello["realversion"] if hello.has_key("realversion") else version features = hello["features"] if hello.has_key("features") else None diff --git a/syncplay/server.py b/syncplay/server.py index 4761875..d9b0ed6 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -397,7 +397,6 @@ class Watcher(object): reactor.callLater(0.1, self._scheduleSendState) def setFile(self, file_): - print file_ if file_ and file_.has_key("name"): file_["name"] = truncateText(file_["name"],constants.MAX_FILENAME_LENGTH) self._file = file_ diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 8a014c8..de561c7 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -85,6 +85,7 @@ class ConfigurationGetter(object): "notificationTimeout": 3, "alertTimeout": 5, "chatTimeout": 7, + "publicServers" : [] } self._defaultConfig = self._config.copy() @@ -142,6 +143,7 @@ class ConfigurationGetter(object): "perPlayerArguments", "mediaSearchDirectories", "trustedDomains", + "publicServers", ] self._numeric = [ @@ -179,7 +181,7 @@ class ConfigurationGetter(object): "autoplayInitialState", "mediaSearchDirectories", "sharedPlaylistEnabled", "loopAtEndOfPlaylist", "loopSingleFiles", - "onlySwitchToTrustedDomains", "trustedDomains"], + "onlySwitchToTrustedDomains", "trustedDomains","publicServers"], "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showDurationNotification", diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index fc7ba4e..30219e6 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -89,6 +89,9 @@ class ConfigDialog(QtGui.QDialog): self.resetButton.show() self.playerargsTextbox.show() self.playerargsLabel.show() + self.mediapathTextbox.show() + self.mediapathLabel.show() + self.mediabrowseButton.show() self.runButton.show() self.saveMoreState(True) self.tabListWidget.setCurrentRow(0) @@ -100,6 +103,14 @@ class ConfigDialog(QtGui.QDialog): self.playerargsTextbox.hide() self.playerargsLabel.hide() self.runButton.hide() + if self.mediapathTextbox.text() == "": + self.mediapathTextbox.hide() + self.mediapathLabel.hide() + self.mediabrowseButton.hide() + else: + self.mediapathTextbox.show() + self.mediapathLabel.show() + self.mediabrowseButton.show() self.saveMoreState(False) self.stackedLayout.setCurrentIndex(0) newHeight = self.connectionSettingsGroup.minimumSizeHint().height()+self.mediaplayerSettingsGroup.minimumSizeHint().height()+self.bottomButtonFrame.minimumSizeHint().height()+3 @@ -110,7 +121,6 @@ class ConfigDialog(QtGui.QDialog): self.setFixedSize(self.sizeHint()) self.moreToggling = False self.setFixedWidth(self.minimumSizeHint().width()) - self.executablepathCombobox.setFixedWidth(self.mediapathTextbox.width()) def openHelp(self): self.QtGui.QDesktopServices.openUrl(QUrl("http://syncplay.pl/guide/client/")) @@ -372,6 +382,8 @@ class ConfigDialog(QtGui.QDialog): self.config["mediaSearchDirectories"] = utils.convertMultilineStringToList(self.mediasearchTextEdit.toPlainText()) self.config["trustedDomains"] = utils.convertMultilineStringToList(self.trusteddomainsTextEdit.toPlainText()) + if self.serverpassTextbox.isEnabled(): + self.config['password'] = self.serverpassTextbox.text() self.processWidget(self, lambda w: self.saveValues(w)) if self.hostCombobox.currentText(): self.config['host'] = self.hostCombobox.currentText() if ":" in self.hostCombobox.currentText() else self.hostCombobox.currentText() + ":" + unicode(constants.DEFAULT_PORT) @@ -386,6 +398,7 @@ class ConfigDialog(QtGui.QDialog): self.config['file'] = os.path.abspath(self.mediapathTextbox.text()) else: self.config['file'] = unicode(self.mediapathTextbox.text()) + self.config['publicServers'] = self.publicServerAddresses self.pressedclosebutton = False self.close() @@ -415,6 +428,16 @@ class ConfigDialog(QtGui.QDialog): self.executablepathCombobox.setEditText(dropfilepath) else: self.mediapathTextbox.setText(dropfilepath) + self.mediapathTextbox.show() + self.mediapathLabel.show() + self.mediabrowseButton.show() + if not self.showmoreCheckbox.isChecked(): + newHeight = self.connectionSettingsGroup.minimumSizeHint().height() + self.mediaplayerSettingsGroup.minimumSizeHint().height() + self.bottomButtonFrame.minimumSizeHint().height() + 3 + if self.error: + newHeight += self.errorLabel.height() + 3 + self.stackedFrame.setFixedHeight(newHeight) + self.adjustSize() + self.setFixedSize(self.sizeHint()) def processWidget(self, container, torun): for widget in container.children(): @@ -514,8 +537,12 @@ class ConfigDialog(QtGui.QDialog): if self.publicServers: i = 0 for publicServer in self.publicServers: - self.hostCombobox.addItem(publicServer[1]) - self.hostCombobox.setItemData(i, publicServer[0], Qt.ToolTipRole) + serverTitle = publicServer[0] + serverAddressPort = publicServer[1] + self.hostCombobox.addItem(serverAddressPort) + self.hostCombobox.setItemData(i, serverTitle, Qt.ToolTipRole) + if not serverAddressPort in self.publicServerAddresses: + self.publicServerAddresses.append(serverAddressPort) i += 1 self.hostCombobox.setEditable(True) self.hostCombobox.setEditText(host) @@ -531,6 +558,7 @@ class ConfigDialog(QtGui.QDialog): self.defaultroomTextbox = QLineEdit(self) self.usernameLabel = QLabel(getMessage("name-label"), self) self.serverpassTextbox = QLineEdit(self) + self.serverpassTextbox.setText(self.storedPassword) self.defaultroomLabel = QLabel(getMessage("room-label"), self) self.hostLabel.setObjectName("host") @@ -538,7 +566,9 @@ class ConfigDialog(QtGui.QDialog): self.usernameLabel.setObjectName("name") self.usernameTextbox.setObjectName("name") self.serverpassLabel.setObjectName("password") - self.serverpassTextbox.setObjectName("password") + self.serverpassTextbox.setObjectName(constants.LOAD_SAVE_MANUALLY_MARKER + "password") + self.hostCombobox.editTextChanged.connect(self.updatePasswordVisibilty) + self.hostCombobox.currentIndexChanged.connect(self.updatePasswordVisibilty) self.defaultroomLabel.setObjectName("room") self.defaultroomTextbox.setObjectName("room") @@ -566,11 +596,12 @@ class ConfigDialog(QtGui.QDialog): self.executableiconImage = QtGui.QImage() self.executableiconLabel = QLabel(self) self.executableiconLabel.setMinimumWidth(16) + self.executableiconLabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self.executablepathCombobox = QtGui.QComboBox(self) self.executablepathCombobox.setEditable(True) self.executablepathCombobox.currentIndexChanged.connect(self.updateExecutableIcon) self.executablepathCombobox.setEditText(self._tryToFillPlayerPath(config['playerPath'], playerpaths)) - self.executablepathCombobox.setFixedWidth(165) + self.executablepathCombobox.setFixedWidth(250) self.executablepathCombobox.editTextChanged.connect(self.updateExecutableIcon) self.executablepathLabel = QLabel(getMessage("executable-path-label"), self) @@ -1111,7 +1142,7 @@ class ConfigDialog(QtGui.QDialog): self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + u"cog.png"),getMessage("misc-label"))) self.tabListLayout.addWidget(self.tabListWidget) self.tabListFrame.setLayout(self.tabListLayout) - self.tabListFrame.setFixedWidth(self.tabListFrame.minimumSizeHint().width()) + self.tabListFrame.setFixedWidth(self.tabListFrame.minimumSizeHint().width() + constants.TAB_PADDING) self.tabListWidget.setStyleSheet(constants.STYLE_TABLIST) self.tabListWidget.currentItemChanged.connect(self.tabChange) @@ -1136,7 +1167,6 @@ class ConfigDialog(QtGui.QDialog): def showEvent(self, *args, **kwargs): self.ensureTabListIsVisible() self.setFixedWidth(self.minimumSizeHint().width()) - self.executablepathCombobox.setFixedWidth(self.mediapathTextbox.width()) def clearGUIData(self, leaveMore=False): settings = QSettings("Syncplay", "PlayerList") @@ -1168,8 +1198,22 @@ class ConfigDialog(QtGui.QDialog): for server in self.publicServers: self.hostCombobox.addItem(server[1]) self.hostCombobox.setItemData(i, server[0], Qt.ToolTipRole) + if not server[1] in self.publicServerAddresses: + self.publicServerAddresses.append(server[1]) i += 1 self.hostCombobox.setEditText(currentServer) + + def updatePasswordVisibilty(self): + if (self.hostCombobox.currentText() == "" and self.serverpassTextbox.text() == "") or unicode(self.hostCombobox.currentText()) in self.publicServerAddresses: + self.serverpassTextbox.setDisabled(True) + self.serverpassTextbox.setReadOnly(True) + if self.serverpassTextbox.text() != "": + self.storedPassword = self.serverpassTextbox.text() + self.serverpassTextbox.setText("") + else: + self.serverpassTextbox.setEnabled(True) + self.serverpassTextbox.setReadOnly(False) + self.serverpassTextbox.setText(self.storedPassword) def __init__(self, config, playerpaths, error, defaultConfig): self.config = config @@ -1179,6 +1223,7 @@ class ConfigDialog(QtGui.QDialog): self.config['resetConfig'] = False self.subitems = {} self.publicServers = None + self.publicServerAddresses = [] self._playerProbeThread = GetPlayerIconThread() self._playerProbeThread.done.connect(self._updateExecutableIcon) @@ -1211,6 +1256,7 @@ class ConfigDialog(QtGui.QDialog): self.mainLayout.setSpacing(0) self.mainLayout.setContentsMargins(0,0,0,0) + self.storedPassword = self.config['password'] self.addBasicTab() self.addReadinessTab() self.addSyncTab() @@ -1221,6 +1267,7 @@ class ConfigDialog(QtGui.QDialog): self.mainLayout.addWidget(self.stackedFrame, 0, 1) self.addBottomLayout() + self.updatePasswordVisibilty() if self.getMoreState() == False: self.tabListFrame.hide() @@ -1228,6 +1275,14 @@ class ConfigDialog(QtGui.QDialog): self.playerargsTextbox.hide() self.playerargsLabel.hide() self.runButton.hide() + if self.mediapathTextbox.text() == "": + self.mediapathTextbox.hide() + self.mediapathLabel.hide() + self.mediabrowseButton.hide() + else: + self.mediapathTextbox.show() + self.mediapathLabel.show() + self.mediabrowseButton.show() newHeight = self.connectionSettingsGroup.minimumSizeHint().height()+self.mediaplayerSettingsGroup.minimumSizeHint().height()+self.bottomButtonFrame.minimumSizeHint().height()+3 if self.error: newHeight +=self.errorLabel.height()+3 diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index c9a2455..78a2023 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -410,6 +410,9 @@ class MainWindow(QtGui.QMainWindow): def setFeatures(self, featureList): if not featureList["readiness"]: self.readyPushButton.setEnabled(False) + if not featureList["chat"]: + self.chatFrame.setEnabled(False) + self.chatInput.setReadOnly(True) if not featureList["sharedPlaylists"]: self.playlistGroup.setEnabled(False) @@ -531,7 +534,7 @@ class MainWindow(QtGui.QMainWindow): filenameitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) filenameitem.setFont(underlinefont) if not sameSize: - if currentUser.file is not None and formatSize(user.file['size']) == formatSize(currentUser.file['size']): + if formatSize(user.file['size']) == formatSize(currentUser.file['size']): filesizeitem = QtGui.QStandardItem(formatSize(user.file['size'],precise=True)) filesizeitem.setFont(underlinefont) filesizeitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) @@ -570,8 +573,12 @@ class MainWindow(QtGui.QMainWindow): self._syncplayClient.playlist.undoPlaylistChange() @needsClient - def shufflePlaylist(self): - self._syncplayClient.playlist.shufflePlaylist() + def shuffleRemainingPlaylist(self): + self._syncplayClient.playlist.shuffleRemainingPlaylist() + + @needsClient + def shuffleEntirePlaylist(self): + self._syncplayClient.playlist.shuffleEntirePlaylist() @needsClient def openPlaylistMenu(self, position): @@ -603,7 +610,8 @@ class MainWindow(QtGui.QMainWindow): menu.addAction(QtGui.QPixmap(resourcespath + u"shield_add.png"),getMessage("addtrusteddomain-menu-label").format(domain), lambda: self.addTrustedDomain(domain)) menu.addAction(QtGui.QPixmap(resourcespath + u"delete.png"), getMessage("removefromplaylist-menu-label"), lambda: self.deleteSelectedPlaylistItems()) menu.addSeparator() - menu.addAction(QtGui.QPixmap(resourcespath + u"arrow_switch.png"), getMessage("shuffleplaylist-menuu-label"), lambda: self.shufflePlaylist()) + menu.addAction(QtGui.QPixmap(resourcespath + u"arrow_switch.png"), getMessage("shuffleremainingplaylist-menu-label"), lambda: self.shuffleRemainingPlaylist()) + menu.addAction(QtGui.QPixmap(resourcespath + u"arrow_switch.png"), getMessage("shuffleentireplaylist-menuu-label"), lambda: self.shuffleEntirePlaylist()) menu.addAction(QtGui.QPixmap(resourcespath + u"arrow_undo.png"), getMessage("undoplaylist-menu-label"), lambda: self.undoPlaylistChange()) menu.addAction(QtGui.QPixmap(resourcespath + u"film_edit.png"), getMessage("editplaylist-menu-label"), lambda: self.openEditPlaylistDialog()) menu.addAction(QtGui.QPixmap(resourcespath + u"film_add.png"),getMessage("addfilestoplaylist-menu-label"), lambda: self.OpenAddFilesToPlaylistDialog()) diff --git a/syncplay/utils.py b/syncplay/utils.py index 8ab8def..4a35da9 100644 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -42,8 +42,8 @@ def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None): try_one_last_time = True while mtries > 1: try: + #try_one_last_time = False return f(*args, **kwargs) - try_one_last_time = False break except ExceptionToCheck, e: if logger: