Merge branch 'master' into 'qtpy-pyside2'

This commit is contained in:
alby128 2018-01-19 13:40:09 +01:00
commit 1cc13a2c7d
25 changed files with 1874 additions and 230 deletions

View File

@ -47,6 +47,7 @@ common:
chmod 755 $(LIB_PATH)/syncplay/ chmod 755 $(LIB_PATH)/syncplay/
cp -r resources/hicolor $(SHARE_PATH)/icons/ cp -r resources/hicolor $(SHARE_PATH)/icons/
cp -r resources/*.png $(LIB_PATH)/syncplay/resources/ cp -r resources/*.png $(LIB_PATH)/syncplay/resources/
cp -r resources/*.lua $(LIB_PATH)/syncplay/resources/
cp -r resources/lua/intf/*.lua $(LIB_PATH)/syncplay/resources/lua/intf/ cp -r resources/lua/intf/*.lua $(LIB_PATH)/syncplay/resources/lua/intf/
cp resources/hicolor/48x48/apps/syncplay.png $(SHARE_PATH)/app-install/icons/ cp resources/hicolor/48x48/apps/syncplay.png $(SHARE_PATH)/app-install/icons/
cp resources/hicolor/48x48/apps/syncplay.png $(SHARE_PATH)/pixmaps/ cp resources/hicolor/48x48/apps/syncplay.png $(SHARE_PATH)/pixmaps/

View File

@ -1,6 +1,6 @@
# Syncplay # Syncplay
Solution to synchronize video playback across multiple instances of mplayer2, mpv, Media Player Classic (MPC-HC) and VLC over the Internet. Solution to synchronize video playback across multiple instances of mpv, VLC, MPC-HC, MPC-BE and mplayer2 over the Internet.
## Official website ## Official website
http://syncplay.pl http://syncplay.pl
@ -12,13 +12,14 @@ http://syncplay.pl/download/
Syncplay synchronises the position and play state of multiple media players so that the viewers can watch the same thing at the same time. Syncplay synchronises the position and play state of multiple media players so that the viewers can watch the same thing at the same time.
This means that when one person pauses/unpauses playback or seeks (jumps position) within their media player then this will be replicated across all media players connected to the same server and in the same 'room' (viewing session). This means that when one person pauses/unpauses playback or seeks (jumps position) within their media player then this will be replicated across all media players connected to the same server and in the same 'room' (viewing session).
When a new person joins they will also be synchronised. When a new person joins they will also be synchronised. Syncplay also includes text-based chat so you can discuss a video as you watch it (or you could use third-party Voice over IP software to talk over a video).
## What it doesn't do ## What it doesn't do
Syncplay does not use video streaming or file sharing so each user must have their own copy of the media to be played. Syncplay does not synchronise player configuration, audio/subtitle track choice, playback rate, volume or filters. Furthermore, users must manually choose what file to play as Syncplay does not synchronise which file is open. Finally, Syncplay does not provide a voice or text-based chat platform to allow for discussion during playback as Syncplay is intended to be used in conjunction with third-party communication solutions such as IRC and Mumble. Syncplay is not a file sharing service.
## Authors ## Authors
* *Concept and principal Syncplay developer* - Uriziel. * *Initial concept and core internals developer* - Uriziel.
* *Other Syncplay coders* - daniel-123, Et0h. * *GUI design and current lead developer* - Et0h.
* *Original SyncPlay code* - Tomasz Kowalczyk (Fluxid), who developed SyncPlay at https://github.com/fluxid/syncplay * *Original SyncPlay code* - Tomasz Kowalczyk (Fluxid), who developed SyncPlay at https://github.com/fluxid/syncplay
* *Other contributors* - See http://syncplay.pl/about/development/

View File

@ -671,6 +671,8 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock
'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png', 'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png',
'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', 'resources/arrow_switch.png', 'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', 'resources/arrow_switch.png',
'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', 'resources/bullet_right_grey.png', 'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', 'resources/bullet_right_grey.png',
'resources/user_comment.png',
'resources/error.png',
'resources/film_folder_edit.png', 'resources/film_folder_edit.png',
'resources/film_edit.png', 'resources/film_edit.png',
'resources/folder_film.png', 'resources/folder_film.png',
@ -679,7 +681,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock
'resources/email_go.png', 'resources/email_go.png',
'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png', 'resources/spinner.mng' 'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png', 'resources/spinner.mng'
] ]
resources = ["resources/icon.ico", "resources/syncplay.png", "resources/license.rtf", "resources/third-party-notices.rtf"] resources = ["resources/icon.ico", "resources/syncplay.png", "resources/syncplayintf.lua", "resources/license.rtf", "resources/third-party-notices.rtf"]
resources.extend(guiIcons) resources.extend(guiIcons)
intf_resources = ["resources/lua/intf/syncplay.lua"] intf_resources = ["resources/lua/intf/syncplay.lua"]

BIN
resources/error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

979
resources/syncplayintf.lua Normal file
View File

@ -0,0 +1,979 @@
-- syncplayintf.lua -- An interface for communication between mpv and Syncplay
-- Author: Etoh, utilising repl.lua code by James Ross-Gowan (see below)
-- Thanks: RiCON, James Ross-Gowan, Argon-, wm4, uau
-- Includes code copied/adapted from repl.lua -- A graphical REPL for mpv input commands
--
-- c 2016, James Ross-Gowan
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted, provided that the above
-- copyright notice and this permission notice appear in all copies.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-- See https://github.com/rossy/mpv-repl for a copy of repl.lua
local CANVAS_WIDTH = 1920
local CANVAS_HEIGHT = 1080
local ROW_HEIGHT = 100
local chat_format = "{\\fs50}{\an1}"
local max_scrolling_rows = 100
local MOVEMENT_PER_SECOND = 200
local TICK_INTERVAL = 0.01
local CHAT_MODE_CHATROOM = "Chatroom"
local CHAT_MODE_SUBTITLE = "Subtitle"
local CHAT_MODE_SCROLLING = "Scrolling"
local last_chat_time = 0
local use_alpha_rows_for_chat = true
local MOOD_NEUTRAL = 0
local MOOD_BAD = 1
local MOOD_GOOD = 2
local WORDWRAPIFY_MAGICWORD = "{\\\\fscx0} {\\\\fscx100}"
local SCROLLING_ADDITIONAL_BOTTOM_MARGIN = 75
local default_oscvisibility_state = "never"
local ALPHA_WARNING_TEXT_COLOUR = "FF00FF" -- RBG
local HINT_TEXT_COLOUR = "AAAAAA" -- RBG
local NEUTRAL_ALERT_TEXT_COLOUR = "FFFFFF" -- RBG
local BAD_ALERT_TEXT_COLOUR = "0000FF" -- RBG
local GOOD_ALERT_TEXT_COLOUR = "00FF00" -- RBG
local NOTIFICATION_TEXT_COLOUR = "FFFF00" -- RBG
local FONT_SIZE_MULTIPLIER = 2
local chat_log = {}
local assdraw = require "mp.assdraw"
local opt = require 'mp.options'
local repl_active = false
local insert_mode = false
local line = ''
local cursor = 1
local key_hints_enabled = false
function format_scrolling(xpos, ypos, text)
local chat_message = "\n"..chat_format .. "{\\pos("..xpos..","..ypos..")\\q2}"..text.."\\N\\n"
return string.format(chat_message)
end
function format_chatroom(text)
local chat_message = chat_format .. text .."\\N\\n"
return string.format(chat_message)
end
function clear_chat()
chat_log = {}
end
local alert_osd = ""
local last_alert_osd_time = nil
local alert_osd_mood = MOOD_NEUTRAL
local notification_osd = ""
local last_notification_osd_time = nil
local notification_osd_mood = MOOD_NEUTRAL
function set_alert_osd(osd_message, mood)
alert_osd = osd_message
last_alert_osd_time = mp.get_time()
alert_osd_mood = mood
end
function set_notification_osd(osd_message, mood)
notification_osd = osd_message
last_notification_osd_time = mp.get_time()
notification_osd_mood = mood
end
function add_chat(chat_message, mood)
last_chat_time = mp.get_time()
local entry = #chat_log+1
for i = 1, #chat_log do
if chat_log[i].text == '' then
entry = i
break
end
end
local row = ((entry-1) % max_scrolling_rows)+1
if opts['chatOutputMode'] == CHAT_MODE_CHATROOM then
if entry > opts['chatMaxLines'] then
table.remove(chat_log, 1)
entry = entry - 1
end
end
chat_log[entry] = { xpos=CANVAS_WIDTH, timecreated=mp.get_time(), text=tostring(chat_message), row=row }
end
function chat_update()
local ass = assdraw.ass_new()
local chat_ass = ''
local rowsAdded = 0
local to_add = ''
local incrementRow = 0
if opts['chatOutputMode'] == CHAT_MODE_CHATROOM and chat_log ~= {} then
local timedelta = mp.get_time() - last_chat_time
if timedelta >= opts['chatTimeout'] then
clear_chat()
end
end
rowsAdded,to_add = process_alert_osd()
if to_add ~= nil and to_add ~= "" then
chat_ass = to_add
end
incrementRow,to_add = process_notification_osd(rowsAdded)
rowsAdded = rowsAdded + incrementRow
if to_add ~= nil and to_add ~= "" then
chat_ass = chat_ass .. to_add
end
if #chat_log > 0 then
for i = 1, #chat_log do
local to_add = process_chat_item(i,rowsAdded)
if to_add ~= nil and to_add ~= "" then
chat_ass = chat_ass .. to_add
end
end
end
local xpos = opts['chatLeftMargin']
local ypos = opts['chatTopMargin']
chat_ass = "\n".."{\\pos("..xpos..","..ypos..")}".. chat_ass
if use_alpha_rows_for_chat == false and opts['chatDirectInput'] == true then
local alphawarning_ass = assdraw.ass_new()
alphawarning_ass = "{\\a6}{\\1c&H"..ALPHA_WARNING_TEXT_COLOUR.."}"..opts['alphakey-mode-warning-first-line'].."\n{\\a6}{\\1c&H"..ALPHA_WARNING_TEXT_COLOUR.."}"..opts['alphakey-mode-warning-second-line']
ass:append(alphawarning_ass)
elseif opts['chatOutputMode'] == CHAT_MODE_CHATROOM and opts['chatInputPosition'] == "Top" then
ass:append(chat_ass)
ass:append(input_ass())
else
ass:append(input_ass())
ass:append(chat_ass)
end
mp.set_osd_ass(CANVAS_WIDTH,CANVAS_HEIGHT, ass.text)
end
function process_alert_osd()
local rowsCreated = 0
local stringToAdd = ""
if alert_osd ~= "" and mp.get_time() - last_alert_osd_time < opts['alertTimeout'] and last_alert_osd_time ~= nil then
local messageColour
if alert_osd_mood == MOOD_NEUTRAL then
messageColour = "{\\1c&H"..NEUTRAL_ALERT_TEXT_COLOUR.."}"
elseif alert_osd_mood == MOOD_BAD then
messageColour = "{\\1c&H"..BAD_ALERT_TEXT_COLOUR.."}"
elseif alert_osd_mood == MOOD_GOOD then
messageColour = "{\\1c&H"..GOOD_ALERT_TEXT_COLOUR.."}"
end
local messageString = wordwrapify_string(alert_osd)
local startRow = 0
if messageString ~= '' and messageString ~= nil then
local toDisplay
rowsCreated = rowsCreated + 1
messageString = messageColour..messageString
if stringToAdd ~= "" then
stringToAdd = stringToAdd .. format_chatroom(messageString)
else
stringToAdd = format_chatroom(messageString)
end
end
end
return rowsCreated, stringToAdd
end
function process_notification_osd(startRow)
local rowsCreated = 0
local startRow = startRow
local stringToAdd = ""
if notification_osd ~= "" and mp.get_time() - last_notification_osd_time < opts['alertTimeout'] and last_notification_osd_time ~= nil then
local messageColour
messageColour = "{\\1c&H"..NOTIFICATION_TEXT_COLOUR.."}"
local messageString
messageString = wordwrapify_string(notification_osd)
messageString = messageColour..messageString
messageString = format_chatroom(messageString)
stringToAdd = messageString
rowsCreated = 1
end
return rowsCreated, stringToAdd
end
function process_chat_item(i, rowsAdded)
if opts['chatOutputMode'] == CHAT_MODE_CHATROOM then
return process_chat_item_chatroom(i, rowsAdded)
elseif opts['chatOutputMode'] == CHAT_MODE_SCROLLING then
return process_chat_item_scrolling(i)
end
end
function process_chat_item_scrolling(i)
local timecreated = chat_log[i].timecreated
local timedelta = mp.get_time() - timecreated
local xpos = CANVAS_WIDTH - (timedelta*MOVEMENT_PER_SECOND)
local text = chat_log[i].text
if text ~= '' then
local roughlen = string.len(text) * (opts['chatOutputRelativeFontSize']*FONT_SIZE_MULTIPLIER) * 1.5
if xpos > (-1*roughlen) then
local row = chat_log[i].row-1+opts['scrollingFirstRowOffset']
local ypos = opts['chatTopMargin']+(row * (opts['chatOutputRelativeFontSize']*FONT_SIZE_MULTIPLIER))
return format_scrolling(xpos,ypos,text)
else
chat_log[i].text = ''
end
end
end
function process_chat_item_chatroom(i, startRow)
local text = chat_log[i].text
if text ~= '' then
local text = wordwrapify_string(text)
local rowNumber = i+startRow-1
return(format_chatroom(text))
end
end
function process_chat_item_subtitle(i)
local timecreated = chat_log[i].timecreated
local timedelta = mp.get_time() - timecreated
local xpos = CANVAS_WIDTH - (timedelta*MOVEMENT_PER_SECOND)
local text = chat_log[i].text
if text ~= '' then
local roughlen = string.len(text) * (opts['chatOutputRelativeFontSize']*FONT_SIZE_MULTIPLIER)
if xpos > (-1*roughlen) then
local row = chat_log[i].row
local ypos = row * (opts['chatOutputRelativeFontSize']*FONT_SIZE_MULTIPLIER)
return(format_scrolling(xpos,ypos,text))
else
chat_log[i].text = ''
end
end
end
chat_timer=mp.add_periodic_timer(TICK_INTERVAL, chat_update)
mp.register_script_message('chat', function(e)
add_chat(e)
end)
-- Chat OSD
mp.register_script_message('chat-osd-neutral', function(e)
add_chat(e,MOOD_NEUTRAL)
end)
mp.register_script_message('chat-osd-bad', function(e)
add_chat(e,MOOD_BAD)
end)
mp.register_script_message('chat-osd-good', function(e)
add_chat(e,MOOD_GOOD)
end)
-- Alert OSD
mp.register_script_message('alert-osd-neutral', function(e)
set_alert_osd(e,MOOD_NEUTRAL)
end)
mp.register_script_message('alert-osd-bad', function(e)
set_alert_osd(e,MOOD_BAD)
end)
mp.register_script_message('alert-osd-good', function(e)
set_alert_osd(e,MOOD_GOOD)
end)
-- Notification OSD
mp.register_script_message('notification-osd-neutral', function(e)
set_notification_osd(e,MOOD_NEUTRAL)
end)
mp.register_script_message('notification-osd-bad', function(e)
set_notification_osd(e,MOOD_BAD)
end)
mp.register_script_message('notification-osd-good', function(e)
set_notification_osd(e,MOOD_GOOD)
end)
--
mp.register_script_message('set_syncplayintf_options', function(e)
set_syncplayintf_options(e)
end)
-- Default options
local utils = require 'mp.utils'
local options = require 'mp.options'
opts = {
-- All drawing is scaled by this value, including the text borders and the
-- cursor. Change it if you have a high-DPI display.
scale = 1,
-- Set the font used for the REPL and the console. This probably doesn't
-- have to be a monospaced font.
['chatInputFontFamily'] = 'monospace',
-- Enable/Disable
['chatInputEnabled'] = true,
['chatOutputEnabled'] = true,
['OscVisibilityChangeCompatible'] = false,
-- Set the font size used for the REPL and the console. This will be
-- multiplied by "scale."
['chatInputRelativeFontSize'] = 14,
['chatInputFontWeight'] = 1,
['chatInputFontUnderline'] = false,
['chatInputFontColor'] = "#000000",
['chatInputPosition'] = "Top",
['MaxChatMessageLength'] = 500,
['chatOutputFontFamily'] = "sans serif",
['chatOutputFontSize'] = 50,
['chatOutputFontWeight'] = 1,
['chatOutputFontUnderline'] = false,
['chatOutputFontColor'] = "#FFFFFF",
['chatOutputMode'] = "Chatroom",
['scrollingFirstRowOffset'] = 2,
-- Can be "Chatroom", "Subtitle" or "Scrolling" style
['chatMaxLines'] = 7,
['chatTopMargin'] = 25,
['chatLeftMargin'] = 20,
['chatDirectInput'] = true,
--
['notificationTimeout'] = 3,
['alertTimeout'] = 5,
['chatTimeout'] = 7,
--
['inputPromptStartCharacter'] = ">",
['inputPromptEndCharacter'] = "<",
['backslashSubstituteCharacter'] = "|",
--Lang:
['mpv-key-tab-hint'] = "[TAB] to toggle access to alphabet row key shortcuts.",
['mpv-key-hint'] = "[ENTER] to send message. [ESC] to escape chat mode.",
['alphakey-mode-warning-first-line'] = "You can temporarily use old mpv bindings with a-z keys.",
['alphakey-mode-warning-second-line'] = "Press [TAB] to return to Syncplay chat mode.",
}
function detect_platform()
local o = {}
-- Kind of a dumb way of detecting the platform but whatever
if mp.get_property_native('options/vo-mmcss-profile', o) ~= o then
return 'windows'
elseif mp.get_property_native('options/input-app-events', o) ~= o then
return 'macos'
end
return 'linux'
end
-- Pick a better default font for Windows and macOS
local platform = detect_platform()
if platform == 'windows' then
opts.font = 'Consolas'
elseif platform == 'macos' then
opts.font = 'Menlo'
end
-- Apply user-set options
options.read_options(opts)
-- Escape a string for verbatim display on the OSD
function ass_escape(str)
-- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if
-- it isn't followed by a recognised character, so add a zero-width
-- non-breaking space
str = str:gsub('\\', '\\\239\187\191')
str = str:gsub('{', '\\{')
str = str:gsub('}', '\\}')
-- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of
-- consecutive newlines
str = str:gsub('\n', '\239\187\191\n')
return str
end
function update()
return
end
function input_ass()
if not repl_active then
return ""
end
last_chat_time = mp.get_time() -- to keep chat messages showing while entering input
local bold
if opts['chatInputFontWeight'] < 75 then
bold = 0
else
bold = 1
end
local underline = opts['chatInputFontUnderline'] and 1 or 0
local red = string.sub(opts['chatInputFontColor'],2,3)
local green = string.sub(opts['chatInputFontColor'],4,5)
local blue = string.sub(opts['chatInputFontColor'],6,7)
local fontColor = blue .. green .. red
local style = '{\\r' ..
'\\1a&H00&\\3a&H00&\\4a&H99&' ..
'\\1c&H'..fontColor..'&\\3c&H111111&\\4c&H000000&' ..
'\\fn' .. opts['chatInputFontFamily'] .. '\\fs' .. (opts['chatInputRelativeFontSize']*FONT_SIZE_MULTIPLIER) .. '\\b' .. bold ..
'\\bord2\\xshad0\\yshad1\\fsp0\\q1}'
local after_style = '{\\u' .. underline .. '}'
local cheight = opts['chatInputRelativeFontSize'] * FONT_SIZE_MULTIPLIER * 8
local cglyph = '_'
local before_cur = wordwrapify_string(ass_escape(line:sub(1, cursor - 1)))
local after_cur = wordwrapify_string(ass_escape(line:sub(cursor)))
local secondary_pos = "10,"..tostring(10+(opts['chatInputRelativeFontSize']*FONT_SIZE_MULTIPLIER))
local alignment = 7
local position = "5,5"
local start_marker = opts['inputPromptStartCharacter']
local end_marker = ""
if opts['chatInputPosition'] == "Middle" then
alignment = 5
position = tostring(CANVAS_WIDTH/2)..","..tostring(CANVAS_HEIGHT/2)
secondary_pos = tostring(CANVAS_WIDTH/2)..","..tostring((CANVAS_HEIGHT/2)+20+(opts['chatInputRelativeFontSize']*FONT_SIZE_MULTIPLIER))
end_marker = "{\\u0}"..opts['inputPromptEndCharacter']
elseif opts['chatInputPosition'] == "Bottom" then
alignment = 1
position = tostring(5)..","..tostring(CANVAS_HEIGHT-5)
secondary_pos = "10,"..tostring(CANVAS_HEIGHT-(20+(opts['chatInputRelativeFontSize']*FONT_SIZE_MULTIPLIER)))
end
local osd_help_message = opts['mpv-key-hint']
if opts['chatDirectInput'] then
osd_help_message = opts['mpv-key-tab-hint'] .. " " .. osd_help_message
end
local help_prompt = '\\N\\n{\\an'..alignment..'\\pos('..secondary_pos..')\\fn' .. opts['chatOutputFontFamily'] .. '\\fs' .. ((opts['chatInputRelativeFontSize']*FONT_SIZE_MULTIPLIER)/1.25) .. '\\1c&H'..HINT_TEXT_COLOUR..'}' .. osd_help_message
local firststyle = "{\\an"..alignment.."}{\\pos("..position..")}"
if opts['chatOutputEnabled'] and opts['chatOutputMode'] == CHAT_MODE_CHATROOM and opts['chatInputPosition'] == "Top" then
firststyle = get_output_style().."{'\\1c&H'"..fontColor.."}"
before_cur = before_cur .. firststyle
after_cur = after_cur .. firststyle
help_prompt = '\\N\\n'..firststyle..'{\\1c&H'..HINT_TEXT_COLOUR..'}' .. osd_help_message .. '\\N\\n'
end
if key_hints_enabled == false then help_prompt = "" end
return firststyle..style..start_marker.." "..after_style..before_cur..style..cglyph..style..after_style..after_cur..end_marker..help_prompt
end
function get_output_style()
local bold
if opts['chatOutputFontWeight'] < 75 then
bold = 0
else
bold = 1
end
local underline = opts['chatOutputFontUnderline'] and 1 or 0
local red = string.sub(opts['chatOutputFontColor'],2,3)
local green = string.sub(opts['chatOutputFontColor'],4,5)
local blue = string.sub(opts['chatOutputFontColor'],6,7)
local fontColor = blue .. green .. red
local style = '{\\r' ..
'\\1a&H00&\\3a&H00&\\4a&H99&' ..
'\\1c&H'..fontColor..'&\\3c&H111111&\\4c&H000000&' ..
'\\fn' .. opts['chatOutputFontFamily'] .. '\\fs' .. (opts['chatOutputRelativeFontSize']*FONT_SIZE_MULTIPLIER) .. '\\b' .. bold ..
'\\u' .. underline .. '\\a5\\MarginV=500' .. '}'
--mp.osd_message("",0)
return style
end
function escape()
set_active(false)
clear()
end
-- Set the REPL visibility (`, Esc)
function set_active(active)
if use_alpha_rows_for_chat == false then active = false end
if active == repl_active then return end
if active then
repl_active = true
insert_mode = false
mp.enable_key_bindings('repl-input', 'allow-hide-cursor+allow-vo-dragging')
else
repl_active = false
mp.disable_key_bindings('repl-input')
end
if default_oscvisibility_state ~= "never" and opts['OscVisibilityChangeCompatible'] == true then
if active then
mp.commandv("script-message", "osc-visibility","never", "no-osd")
else
mp.commandv("script-message", "osc-visibility",default_oscvisibility_state, "no-osd")
end
end
end
-- Show the repl if hidden and replace its contents with 'text'
-- (script-message-to repl type)
function show_and_type(text)
text = text or ''
line = text
cursor = line:len() + 1
insert_mode = false
if repl_active then
update()
else
set_active(true)
end
end
-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
function next_utf8(str, pos)
if pos > str:len() then return pos end
repeat
pos = pos + 1
until pos > str:len() or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
return pos
end
-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
-- As above, but finds the previous UTF-8 charcter in 'str' before 'pos'
function prev_utf8(str, pos)
if pos <= 1 then return pos end
repeat
pos = pos - 1
until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
return pos
end
function trim_string(line,maxCharacters)
-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
local str = line
if str == nil or str == "" or str:len() <= maxCharacters then
return str, ""
end
local pos = 0
local oldPos = -1
local chars = 0
repeat
oldPos = pos
pos = next_utf8(str, pos)
chars = chars + 1
until pos == oldPos or chars > maxCharacters
return str:sub(1,pos-1), str:sub(pos)
end
function wordwrapify_string(line)
-- Used to ensure characters wrap on a per-character rather than per-word basis
-- to avoid issues with long filenames, etc.
local str = line
if str == nil or str == "" then
return ""
end
local newstr = ""
local currentChar = 0
local nextChar = 0
local chars = 0
local maxChars = str:len()
repeat
nextChar = next_utf8(str, currentChar)
if nextChar == currentChar then
return newstr
end
local charToTest = str:sub(currentChar,nextChar-1)
if charToTest ~= "\\" and charToTest ~= "{" and charToTest ~= "}" and charToTest ~= "%" then
newstr = newstr .. WORDWRAPIFY_MAGICWORD .. str:sub(currentChar,nextChar-1)
else
newstr = newstr .. str:sub(currentChar,nextChar-1)
end
currentChar = nextChar
until currentChar > maxChars
newstr = string.gsub(newstr,opts['backslashSubstituteCharacter'], '\\\239\187\191') -- Workaround for \ escape issues
return newstr
end
function trim_input()
-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
local str = line
if str == nil or str == "" or str:len() <= opts['MaxChatMessageLength'] then
return
end
local pos = 0
local oldPos = -1
local chars = 0
repeat
oldPos = pos
pos = next_utf8(str, pos)
chars = chars + 1
until pos == oldPos or chars > opts['MaxChatMessageLength']
line = line:sub(1,pos-1)
if cursor > pos then
cursor = pos
end
return
end
-- Insert a character at the current cursor position (' '-'~', Shift+Enter)
function handle_char_input(c)
if c == nil then return end
if c == "\\" then c = opts['backslashSubstituteCharacter'] end
if key_hints_enabled and (string.len(line) > 0 or opts['chatDirectInput'] == false) then
key_hints_enabled = false
end
set_active(true)
if insert_mode then
line = line:sub(1, cursor - 1) .. c .. line:sub(next_utf8(line, cursor))
else
line = line:sub(1, cursor - 1) .. c .. line:sub(cursor)
end
cursor = cursor + c:len()
trim_input()
update()
end
-- Remove the character behind the cursor (Backspace)
function handle_backspace()
if cursor <= 1 then return end
local prev = prev_utf8(line, cursor)
line = line:sub(1, prev - 1) .. line:sub(cursor)
cursor = prev
update()
end
-- Remove the character in front of the cursor (Del)
function handle_del()
if cursor > line:len() then return end
line = line:sub(1, cursor - 1) .. line:sub(next_utf8(line, cursor))
update()
end
-- Toggle insert mode (Ins)
function handle_ins()
insert_mode = not insert_mode
end
--local was_active_before_tab = false
function handle_tab()
use_alpha_rows_for_chat = not use_alpha_rows_for_chat
if use_alpha_rows_for_chat then
mp.enable_key_bindings('repl-alpha-input')
--set_active(was_active_before_tab)
else
mp.disable_key_bindings('repl-alpha-input')
--was_active_before_tab = repl_active
--set_active(false)
escape()
end
end
-- Move the cursor to the next character (Right)
function next_char(amount)
cursor = next_utf8(line, cursor)
update()
end
-- Move the cursor to the previous character (Left)
function prev_char(amount)
cursor = prev_utf8(line, cursor)
update()
end
-- Clear the current line (Ctrl+C)
function clear()
line = ''
cursor = 1
insert_mode = false
update()
end
-- Close the REPL if the current line is empty, otherwise do nothing (Ctrl+D)
function maybe_exit()
if line == '' then
set_active(false)
end
end
-- Run the current command and clear the line (Enter)
function handle_enter()
if not repl_active then
set_active(true)
return
end
set_active(false)
if line == '' then
return
end
key_hints_enabled = false
line = string.gsub(line,"\\", "\\\\")
line = string.gsub(line,"\"", "\\\"")
mp.command('print-text "<chat>'..line..'</chat>"')
clear()
end
-- Move the cursor to the beginning of the line (HOME)
function go_home()
cursor = 1
update()
end
-- Move the cursor to the end of the line (END)
function go_end()
cursor = line:len() + 1
update()
end
-- Delete from the cursor to the end of the line (Ctrl+K)
function del_to_eol()
line = line:sub(1, cursor - 1)
update()
end
-- Delete from the cursor back to the start of the line (Ctrl+U)
function del_to_start()
line = line:sub(cursor)
cursor = 1
update()
end
-- Returns a string of UTF-8 text from the clipboard (or the primary selection)
function get_clipboard(clip)
if platform == 'linux' then
local res = utils.subprocess({ args = {
'xclip', '-selection', clip and 'clipboard' or 'primary', '-out'
} })
if not res.error then
return res.stdout
end
elseif platform == 'windows' then
local res = utils.subprocess({ args = {
'powershell', '-NoProfile', '-Command', [[& {
Trap {
Write-Error -ErrorRecord $_
Exit 1
}
$clip = ""
if (Get-Command "Get-Clipboard" -errorAction SilentlyContinue) {
$clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText
} else {
Add-Type -AssemblyName PresentationCore
$clip = [Windows.Clipboard]::GetText()
}
$clip = $clip -Replace "`r",""
$u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip)
[Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length)
}]]
} })
if not res.error then
return res.stdout
end
elseif platform == 'macos' then
local res = utils.subprocess({ args = { 'pbpaste' } })
if not res.error then
return res.stdout
end
end
return ''
end
-- Paste text from the window-system's clipboard. 'clip' determines whether the
-- clipboard or the primary selection buffer is used (on X11 only.)
function paste(clip)
local text = get_clipboard(clip)
local before_cur = line:sub(1, cursor - 1)
local after_cur = line:sub(cursor)
line = before_cur .. text .. after_cur
cursor = cursor + text:len()
trim_input()
update()
end
-- The REPL has pretty specific requirements for key bindings that aren't
-- really satisified by any of mpv's helper methods, since they must be in
-- their own input section, but they must also raise events on key-repeat.
-- Hence, this function manually creates an input section and puts a list of
-- bindings in it.
function add_repl_bindings(bindings)
local cfg = ''
for i, binding in ipairs(bindings) do
local key = binding[1]
local fn = binding[2]
local name = '__repl_binding_' .. i
mp.add_forced_key_binding(nil, name, fn, 'repeatable')
cfg = cfg .. key .. ' script-binding ' .. mp.script_name .. '/' ..
name .. '\n'
end
mp.commandv('define-section', 'repl-input', cfg, 'force')
end
function add_repl_alpharow_bindings(bindings)
local cfg = ''
for i, binding in ipairs(bindings) do
local key = binding[1]
local fn = binding[2]
local name = '__repl_alpha_binding_' .. i
mp.add_forced_key_binding(nil, name, fn, 'repeatable')
cfg = cfg .. key .. ' script-binding ' .. mp.script_name .. '/' ..
name .. '\n'
end
mp.commandv('define-section', 'repl-alpha-input', cfg, 'force')
mp.enable_key_bindings('repl-alpha-input')
end
-- Mapping from characters to mpv key names
local binding_name_map = {
[' '] = 'SPACE',
['#'] = 'SHARP',
}
-- List of input bindings. This is a weird mashup between common GUI text-input
-- bindings and readline bindings.
local bindings = {
{ 'esc', function() escape() end },
{ 'bs', handle_backspace },
{ 'shift+bs', handle_backspace },
{ 'del', handle_del },
{ 'shift+del', handle_del },
{ 'ins', handle_ins },
{ 'left', function() prev_char() end },
{ 'right', function() next_char() end },
{ 'up', function() clear() end },
{ 'home', go_home },
{ 'end', go_end },
{ 'ctrl+c', clear },
{ 'ctrl+d', maybe_exit },
{ 'ctrl+k', del_to_eol },
{ 'ctrl+l', clear_log_buffer },
{ 'ctrl+u', del_to_start },
{ 'ctrl+v', function() paste(true) end },
{ 'meta+v', function() paste(true) end },
}
local alpharowbindings = {}
-- Add bindings for all the printable US-ASCII characters from ' ' to '~'
-- inclusive. Note, this is a pretty hacky way to do text input. mpv's input
-- system was designed for single-key key bindings rather than text input, so
-- things like dead-keys and non-ASCII input won't work. This is probably okay
-- though, since all mpv's commands and properties can be represented in ASCII.
for b = (' '):byte(), ('~'):byte() do
local c = string.char(b)
local binding = binding_name_map[c] or c
bindings[#bindings + 1] = {binding, function() handle_char_input(c) end}
end
function add_alpharowbinding(firstchar,lastchar)
for b = (firstchar):byte(), (lastchar):byte() do
local c = string.char(b)
local alphabinding = binding_name_map[c] or c
alpharowbindings[#alpharowbindings + 1] = {alphabinding, function() handle_char_input(c) end}
end
end
function add_specialalphabindings(charinput)
local alphabindingarray = charinput
for i, alphabinding in ipairs(alphabindingarray) do
alpharowbindings[#alpharowbindings + 1] = {alphabinding, function() handle_char_input(alphabinding) end }
end
end
add_alpharowbinding('a','z')
add_alpharowbinding('A','Z')
add_alpharowbinding('/','/')
add_alpharowbinding(':',':')
add_alpharowbinding('(',')')
add_alpharowbinding('{','}')
add_alpharowbinding(':',';')
add_alpharowbinding('<','>')
add_alpharowbinding(',','.')
add_alpharowbinding('|','|')
add_alpharowbinding('\\','\\')
add_alpharowbinding('?','?')
add_alpharowbinding('[',']')
add_alpharowbinding('#','#')
add_alpharowbinding('~','~')
add_alpharowbinding('\'','\'')
add_alpharowbinding('@','@')
add_specialalphabindings({'à','è','ì','ò','ù','À','È','Ì','Ò','Ù'})
add_specialalphabindings({'á', 'é', 'í', 'ó', 'ú', 'ý', 'Á', 'É', 'Í', 'Ó', 'Ú', 'Ý'})
add_specialalphabindings({'â', 'ê', 'î', 'ô', 'û', 'Â', 'Ê', 'Î', 'Ô', 'Û'})
add_specialalphabindings({'ã', 'ñ', 'õ', 'Ã', 'Ñ', 'Õ'})
add_specialalphabindings({'ä', 'ë', 'ï', 'ö', 'ü', 'ÿ', 'Ä', 'Ë', 'Ï', 'Ö', 'Ü', 'Ÿ'})
add_specialalphabindings({'å', 'Å','æ','Æ','œ','Œ','ç','Ç','ð','Ð','ø','Ø','¿','¡','ß'})
add_specialalphabindings({'¤','','×','÷','','±','','','','§','ˆ','˜','«','»','¦','','©','®',''})
add_specialalphabindings({'ž','Ž'})
add_specialalphabindings({'ª','Þ','þ','ƒ','µ','°','º','','','','','¬','¥','£','','¢','¹','²','³','½','¼','¾'})
add_specialalphabindings({'·','Ĉ','ĉ','Ĝ','ĝ','Ĥ','ĥ','Ĵ','ĵ','Ŝ','ŝ','Ŭ','ŭ'})
add_specialalphabindings({'Б','б','В','в','Г','г','Д','д','Е','е','Ё','ё','Ж','ж','З','з'})
add_specialalphabindings({'И','и','Й','й','К','к','Л','л','М','м','Н','н','О','о','П','п'})
add_specialalphabindings({'Р','р','С','с','Т','т','У','у','Ф','ф','Х','х','Ц','ц','Ч','ч'})
add_specialalphabindings({'Ш','ш','Щ','щ','Ъ','ъ','Ы','ы','Ь','ь','Э','э','Ю','ю','Я','я'})
add_specialalphabindings({'',''})
add_repl_bindings(bindings)
-- Add a script-message to show the REPL and fill it with the provided text
mp.register_script_message('type', function(text)
show_and_type(text)
end)
local syncplayintfSet = false
mp.command('print-text "<get_syncplayintf_options>"')
function readyMpvAfterSettingsKnown()
if syncplayintfSet == false then
local vertical_output_area = CANVAS_HEIGHT-(opts['chatTopMargin']+opts['chatBottomMargin']+((opts['chatOutputRelativeFontSize']*FONT_SIZE_MULTIPLIER)*opts['scrollingFirstRowOffset'])+SCROLLING_ADDITIONAL_BOTTOM_MARGIN)
max_scrolling_rows = math.floor(vertical_output_area/(opts['chatOutputRelativeFontSize']*FONT_SIZE_MULTIPLIER))
local user_opts = { visibility = "auto", }
opt.read_options(user_opts, "osc")
default_oscvisibility_state = user_opts.visibility
if opts['chatInputEnabled'] == true then
key_hints_enabled = true
mp.add_forced_key_binding('enter', handle_enter)
mp.add_forced_key_binding('kp_enter', handle_enter)
if opts['chatDirectInput'] == true then
add_repl_alpharow_bindings(alpharowbindings)
mp.add_forced_key_binding('tab', handle_tab)
end
end
syncplayintfSet = true
end
end
function set_syncplayintf_options(input)
--mp.command('print-text "<chat>...'..input..'</chat>"')
for option, value in string.gmatch(input, "([^ ,=]+)=([^,]+)") do
local valueType = type(opts[option])
if valueType == "number" then
value = tonumber(value)
elseif valueType == "boolean" then
if value == "True" then
value = true
else
value = false
end
end
opts[option] = value
--mp.command('print-text "<chat>'..option.."="..tostring(value).." - "..valueType..'</chat>"')
end
chat_format = get_output_style()
readyMpvAfterSettingsKnown()
end

BIN
resources/user_comment.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

View File

@ -1,4 +1,4 @@
version = '1.5.1' version = '1.5.2'
milestone = 'Yoitsu' milestone = 'Yoitsu'
release_number = '51' release_number = '55'
projectURL = 'http://syncplay.pl/' projectURL = 'http://syncplay.pl/'

View File

@ -145,8 +145,8 @@ class SyncplayClient(object):
def initPlayer(self, player): def initPlayer(self, player):
self._player = player self._player = player
if not self._player.secondaryOSDSupported: if not self._player.alertOSDSupported:
constants.OSD_WARNING_MESSAGE_DURATION = constants.NO_SECONDARY_OSD_WARNING_DURATION constants.OSD_WARNING_MESSAGE_DURATION = constants.NO_ALERT_OSD_WARNING_DURATION
self.scheduleAskPlayer() self.scheduleAskPlayer()
self.__playerReady.callback(player) self.__playerReady.callback(player)
@ -205,7 +205,7 @@ class SyncplayClient(object):
if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\ if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\
and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD: and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD:
self.playlist.advancePlaylistCheck() self.playlist.advancePlaylistCheck()
elif pauseChange and self.serverFeatures["readiness"]: elif pauseChange and self.serverFeatures.has_key("readiness") and self.serverFeatures["readiness"]:
if currentLength == 0 or currentLength == -1 or\ if currentLength == 0 or currentLength == -1 or\
not (not self.playlist.notJustChangedPlaylist() and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD): not (not self.playlist.notJustChangedPlaylist() and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD):
pauseChange = self._toggleReady(pauseChange, paused) pauseChange = self._toggleReady(pauseChange, paused)
@ -546,15 +546,32 @@ class SyncplayClient(object):
"sharedPlaylists": utils.meetsMinVersion(self.serverVersion, constants.SHARED_PLAYLIST_MIN_VERSION), "sharedPlaylists": utils.meetsMinVersion(self.serverVersion, constants.SHARED_PLAYLIST_MIN_VERSION),
"chat": utils.meetsMinVersion(self.serverVersion, constants.CHAT_MIN_VERSION), "chat": utils.meetsMinVersion(self.serverVersion, constants.CHAT_MIN_VERSION),
"readiness": utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION), "readiness": utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION),
"managedRooms": utils.meetsMinVersion(self.serverVersion, constants.CONTROLLED_ROOMS_MIN_VERSION) "managedRooms": utils.meetsMinVersion(self.serverVersion, constants.CONTROLLED_ROOMS_MIN_VERSION),
"maxChatMessageLength": constants.FALLBACK_MAX_CHAT_MESSAGE_LENGTH,
"maxUsernameLength": constants.FALLBACK_MAX_USERNAME_LENGTH,
"maxRoomNameLength": constants.FALLBACK_MAX_ROOM_NAME_LENGTH,
"maxFilenameLength": constants.FALLBACK_MAX_FILENAME_LENGTH
} }
if featureList: if featureList:
self.serverFeatures.update(featureList) self.serverFeatures.update(featureList)
if not utils.meetsMinVersion(self.serverVersion, constants.SHARED_PLAYLIST_MIN_VERSION): if not utils.meetsMinVersion(self.serverVersion, constants.SHARED_PLAYLIST_MIN_VERSION):
self.ui.showErrorMessage(getMessage("shared-playlists-not-supported-by-server-error").format(constants.SHARED_PLAYLIST_MIN_VERSION, self.serverVersion)) self.ui.showErrorMessage(getMessage("shared-playlists-not-supported-by-server-error").format(constants.SHARED_PLAYLIST_MIN_VERSION, self.serverVersion))
elif not self.serverFeatures["sharedPlaylists"]: elif not self.serverFeatures["sharedPlaylists"]:
self.ui.showErrorMessage(getMessage("shared-playlists-disabled-by-server-error")) self.ui.showErrorMessage(getMessage("shared-playlists-disabled-by-server-error"))
# TODO: Have messages for all unsupported & disabled features
constants.MAX_CHAT_MESSAGE_LENGTH = self.serverFeatures["maxChatMessageLength"]
constants.MAX_USERNAME_LENGTH = self.serverFeatures["maxUsernameLength"]
constants.MAX_ROOM_NAME_LENGTH = self.serverFeatures["maxRoomNameLength"]
constants.MAX_FILENAME_LENGTH = self.serverFeatures["maxFilenameLength"]
constants.MPV_SYNCPLAYINTF_CONSTANTS_TO_SEND = ["MaxChatMessageLength={}".format(constants.MAX_CHAT_MESSAGE_LENGTH),
u"inputPromptStartCharacter={}".format(constants.MPV_INPUT_PROMPT_START_CHARACTER),
u"inputPromptEndCharacter={}".format(constants.MPV_INPUT_PROMPT_END_CHARACTER),
u"backslashSubstituteCharacter={}".format(
constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER)]
self.ui.setFeatures(self.serverFeatures) self.ui.setFeatures(self.serverFeatures)
if self._player:
self._player.setFeatures(self.serverFeatures)
def getSanitizedCurrentUserFile(self): def getSanitizedCurrentUserFile(self):
if self.userlist.currentUser.file: if self.userlist.currentUser.file:
@ -583,16 +600,29 @@ class SyncplayClient(object):
def getUsername(self): def getUsername(self):
return self.userlist.currentUser.username return self.userlist.currentUser.username
def chatIsEnabled(self):
return True
# TODO: Allow chat to be disabled
def getFeatures(self):
features = dict()
# Can change during runtime:
features["sharedPlaylists"] = self.sharedPlaylistIsEnabled() # Can change during runtime
features["chat"] = self.chatIsEnabled() # Can change during runtime
# Static for this version/release of Syncplay:
features["featureList"] = True
features["readiness"] = True
features["managedRooms"] = True
return features
def setRoom(self, roomName, resetAutoplay=False): def setRoom(self, roomName, resetAutoplay=False):
self.userlist.currentUser.room = roomName self.userlist.currentUser.room = roomName
if resetAutoplay: if resetAutoplay:
self.resetAutoPlayState() self.resetAutoPlayState()
def sendChat(self,message):
if self._protocol and self._protocol.logged:
message = utils.truncateText(message,constants.MAX_CHAT_MESSAGE_LENGTH)
self._protocol.sendChatMessage(message)
def sendRoom(self): def sendRoom(self):
room = self.userlist.currentUser.room room = self.userlist.currentUser.room
if self._protocol and self._protocol.logged and room: if self._protocol and self._protocol.logged and room:
@ -632,8 +662,8 @@ class SyncplayClient(object):
if self._protocol and self._protocol.logged: if self._protocol and self._protocol.logged:
self._protocol.sendList() self._protocol.sendList()
def showUserList(self): def showUserList(self, altUI=None):
self.userlist.showUserList() self.userlist.showUserList(altUI)
def getPassword(self): def getPassword(self):
if self.thisIsPublicServer(): if self.thisIsPublicServer():
@ -700,19 +730,30 @@ class SyncplayClient(object):
if promptForAction: if promptForAction:
self.ui.promptFor(getMessage("enter-to-exit-prompt")) self.ui.promptFor(getMessage("enter-to-exit-prompt"))
def requireMinServerVersion(minVersion): def requireServerFeature(featureRequired):
def requireMinVersionDecorator(f): def requireServerFeatureDecorator(f):
@wraps(f) @wraps(f)
def wrapper(self, *args, **kwds): def wrapper(self, *args, **kwds):
if not utils.meetsMinVersion(self.serverVersion,minVersion): if self.serverVersion == "0.0.0":
if self.serverVersion != "0.0.0": self.ui.showDebugMessage(
self.ui.showErrorMessage(getMessage("not-supported-by-server-error").format(minVersion, self.serverVersion)) "Tried to check server version too soon (testing support for: {})".format(featureRequired))
else: return None
self.ui.showDebugMessage("Tried to check server version too soon (threshold: {})".format(minVersion)) if not self.serverFeatures.has_key(featureRequired) or not self.serverFeatures[featureRequired]:
featureName = getMessage(u"feature-{}".format(featureRequired))
self.ui.showErrorMessage(getMessage("not-supported-by-server-error").format(featureName))
return return
return f(self, *args, **kwds) return f(self, *args, **kwds)
return wrapper return wrapper
return requireMinVersionDecorator return requireServerFeatureDecorator
@requireServerFeature("chat")
def sendChat(self,message):
if self._protocol and self._protocol.logged:
message = utils.truncateText(message,constants.MAX_CHAT_MESSAGE_LENGTH)
self._protocol.sendChatMessage(message)
def sendFeaturesUpdate(self, features):
self._protocol.sendFeaturesUpdate(features)
def changePlaylistEnabledState(self, newState): def changePlaylistEnabledState(self, newState):
oldState = self.sharedPlaylistIsEnabled() oldState = self.sharedPlaylistIsEnabled()
@ -780,7 +821,7 @@ class SyncplayClient(object):
allReadyMessage = getMessage("all-users-ready").format(self.userlist.readyUserCount()) allReadyMessage = getMessage("all-users-ready").format(self.userlist.readyUserCount())
autoplayingMessage = getMessage("autoplaying-notification").format(int(self.autoplayTimeLeft)) autoplayingMessage = getMessage("autoplaying-notification").format(int(self.autoplayTimeLeft))
countdownMessage = u"{}{}{}".format(allReadyMessage,self._player.osdMessageSeparator, autoplayingMessage) countdownMessage = u"{}{}{}".format(allReadyMessage,self._player.osdMessageSeparator, autoplayingMessage)
self.ui.showOSDMessage(countdownMessage, 1, secondaryOSD=True) self.ui.showOSDMessage(countdownMessage, 1, OSDType=constants.OSD_ALERT, mood=constants.MESSAGE_GOODNEWS)
if self.autoplayTimeLeft <= 0: if self.autoplayTimeLeft <= 0:
self.setPaused(False) self.setPaused(False)
self.stopAutoplayCountdown() self.stopAutoplayCountdown()
@ -792,11 +833,11 @@ class SyncplayClient(object):
self.ui.updateAutoPlayState(False) self.ui.updateAutoPlayState(False)
self.stopAutoplayCountdown() self.stopAutoplayCountdown()
@requireMinServerVersion(constants.USER_READY_MIN_VERSION) @requireServerFeature("readiness")
def toggleReady(self, manuallyInitiated=True): def toggleReady(self, manuallyInitiated=True):
self._protocol.setReady(not self.userlist.currentUser.isReady(), manuallyInitiated) self._protocol.setReady(not self.userlist.currentUser.isReady(), manuallyInitiated)
@requireMinServerVersion(constants.USER_READY_MIN_VERSION) @requireServerFeature("readiness")
def changeReadyState(self, newState, manuallyInitiated=True): def changeReadyState(self, newState, manuallyInitiated=True):
oldState = self.userlist.currentUser.isReady() oldState = self.userlist.currentUser.isReady()
if newState != oldState: if newState != oldState:
@ -811,7 +852,12 @@ class SyncplayClient(object):
if oldReadyState != isReady: if oldReadyState != isReady:
self._warnings.checkReadyStates() self._warnings.checkReadyStates()
@requireMinServerVersion(constants.CONTROLLED_ROOMS_MIN_VERSION) @requireServerFeature("managedRooms")
def setUserFeatures(self, username, features):
self.userlist.setFeatures(username, features)
self.ui.userListChange()
@requireServerFeature("managedRooms")
def createControlledRoom(self, roomName): def createControlledRoom(self, roomName):
controlPassword = utils.RandomStringGenerator.generate_room_password() controlPassword = utils.RandomStringGenerator.generate_room_password()
self.lastControlPasswordAttempt = controlPassword self.lastControlPasswordAttempt = controlPassword
@ -830,7 +876,7 @@ class SyncplayClient(object):
else: else:
return "" return ""
@requireMinServerVersion(constants.CONTROLLED_ROOMS_MIN_VERSION) @requireServerFeature("managedRooms")
def identifyAsController(self, controlPassword): def identifyAsController(self, controlPassword):
controlPassword = self.stripControlPassword(controlPassword) controlPassword = self.stripControlPassword(controlPassword)
self.ui.showMessage(getMessage("identifying-as-controller-notification").format(controlPassword)) self.ui.showMessage(getMessage("identifying-as-controller-notification").format(controlPassword))
@ -924,7 +970,7 @@ class SyncplayClient(object):
def _checkIfYouReAloneInTheRoom(self, OSDOnly): def _checkIfYouReAloneInTheRoom(self, OSDOnly):
if self._userlist.areYouAloneInRoom(): if self._userlist.areYouAloneInRoom():
self._ui.showOSDMessage(getMessage("alone-in-the-room"), constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, secondaryOSD=True) self._ui.showOSDMessage(getMessage("alone-in-the-room"), constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, OSDType=constants.OSD_ALERT, mood=constants.MESSAGE_BADNEWS)
if not OSDOnly: if not OSDOnly:
self._ui.showMessage(getMessage("alone-in-the-room"), True) self._ui.showMessage(getMessage("alone-in-the-room"), True)
if constants.SHOW_OSD_WARNINGS and not self._warnings["alone-in-the-room"]['timer'].running: if constants.SHOW_OSD_WARNINGS and not self._warnings["alone-in-the-room"]['timer'].running:
@ -954,8 +1000,10 @@ class SyncplayClient(object):
if not self._client._player or self._client.autoplayTimerIsRunning(): if not self._client._player or self._client.autoplayTimerIsRunning():
return return
osdMessage = None osdMessage = None
messageMood = constants.MESSAGE_GOODNEWS
fileDifferencesForRoom = self._userlist.getFileDifferencesForRoom() fileDifferencesForRoom = self._userlist.getFileDifferencesForRoom()
if not self._userlist.areAllFilesInRoomSame() and fileDifferencesForRoom is not None: if not self._userlist.areAllFilesInRoomSame() and fileDifferencesForRoom is not None:
messageMood = constants.MESSAGE_BADNEWS
fileDifferencesMessage = getMessage("room-file-differences").format(fileDifferencesForRoom) fileDifferencesMessage = getMessage("room-file-differences").format(fileDifferencesForRoom)
if self._userlist.currentUser.canControl() and self._userlist.isReadinessSupported(): if self._userlist.currentUser.canControl() and self._userlist.isReadinessSupported():
if self._userlist.areAllUsersInRoomReady(): if self._userlist.areAllUsersInRoomReady():
@ -970,9 +1018,10 @@ class SyncplayClient(object):
if self._userlist.areAllUsersInRoomReady(): if self._userlist.areAllUsersInRoomReady():
osdMessage = getMessage("all-users-ready").format(self._userlist.readyUserCount()) osdMessage = getMessage("all-users-ready").format(self._userlist.readyUserCount())
else: else:
messageMood = constants.MESSAGE_BADNEWS
osdMessage = getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady()) osdMessage = getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady())
if osdMessage: if osdMessage:
self._ui.showOSDMessage(osdMessage, constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, secondaryOSD=True) self._ui.showOSDMessage(osdMessage, constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, OSDType=constants.OSD_ALERT, mood=messageMood)
def __displayMessageOnOSD(self, warningName, warningFunction): def __displayMessageOnOSD(self, warningName, warningFunction):
if constants.OSD_WARNING_MESSAGE_DURATION > self._warnings[warningName]["displayedFor"]: if constants.OSD_WARNING_MESSAGE_DURATION > self._warnings[warningName]["displayedFor"]:
@ -1001,6 +1050,7 @@ class SyncplayUser(object):
self.room = room self.room = room
self.file = file_ self.file = file_
self._controller = False self._controller = False
self._features = {}
def setFile(self, filename, duration, size, path=None): def setFile(self, filename, duration, size, path=None):
file_ = { file_ = {
@ -1054,6 +1104,9 @@ class SyncplayUser(object):
def setReady(self, ready): def setReady(self, ready):
self.ready = ready self.ready = ready
def setFeatures(self, features):
self._features = features
class SyncplayUserlist(object): class SyncplayUserlist(object):
def __init__(self, ui, client): def __init__(self, ui, client):
self.currentUser = SyncplayUser() self.currentUser = SyncplayUser()
@ -1132,7 +1185,7 @@ class SyncplayUserlist(object):
if differentDuration: differences.append(getMessage("file-difference-duration")) if differentDuration: differences.append(getMessage("file-difference-duration"))
return ", ".join(differences) return ", ".join(differences)
def addUser(self, username, room, file_, noMessage=False, isController=None, isReady=None): def addUser(self, username, room, file_, noMessage=False, isController=None, isReady=None, features={}):
if username == self.currentUser.username: if username == self.currentUser.username:
if isController is not None: if isController is not None:
self.currentUser.setControllerStatus(isController) self.currentUser.setControllerStatus(isController)
@ -1143,7 +1196,7 @@ class SyncplayUserlist(object):
user.setControllerStatus(isController) user.setControllerStatus(isController)
self._users[username] = user self._users[username] = user
user.setReady(isReady) user.setReady(isReady)
user.setFeatures(features)
if not noMessage: if not noMessage:
self.__showUserChangeMessage(username, room, file_) self.__showUserChangeMessage(username, room, file_)
self.userListChange(room) self.userListChange(room)
@ -1310,7 +1363,7 @@ class SyncplayUserlist(object):
def hasRoomStateChanged(self): def hasRoomStateChanged(self):
return self._roomUsersChanged return self._roomUsersChanged
def showUserList(self): def showUserList(self, altUI=None):
rooms = {} rooms = {}
for user in self._users.itervalues(): for user in self._users.itervalues():
if user.room not in rooms: if user.room not in rooms:
@ -1320,7 +1373,10 @@ class SyncplayUserlist(object):
rooms[self.currentUser.room] = [] rooms[self.currentUser.room] = []
rooms[self.currentUser.room].append(self.currentUser) rooms[self.currentUser.room].append(self.currentUser)
rooms = self.sortList(rooms) rooms = self.sortList(rooms)
self.ui.showUserList(self.currentUser, rooms) if altUI:
altUI.showUserList(self.currentUser, rooms)
else:
self.ui.showUserList(self.currentUser, rooms)
self._client.autoplayCheck() self._client.autoplayCheck()
def clearList(self): def clearList(self):
@ -1336,10 +1392,10 @@ class UiManager(object):
def __init__(self, client, ui): def __init__(self, client, ui):
self._client = client self._client = client
self.__ui = ui self.__ui = ui
self.lastPrimaryOSDMessage = None self.lastNotificatinOSDMessage = None
self.lastPrimaryOSDEndTime = None self.lastNotificationOSDEndTime = None
self.lastSecondaryOSDMessage = None self.lastAlertOSDMessage = None
self.lastSecondaryOSDEndTime = None self.lastAlertOSDEndTime = None
self.lastError = "" self.lastError = ""
def setPlaylist(self, newPlaylist, newIndexFilename=None): def setPlaylist(self, newPlaylist, newIndexFilename=None):
@ -1358,8 +1414,16 @@ class UiManager(object):
if constants.DEBUG_MODE and message.rstrip(): if constants.DEBUG_MODE and message.rstrip():
sys.stderr.write("{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()).decode('utf-8'),message.rstrip())) sys.stderr.write("{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()).decode('utf-8'),message.rstrip()))
def showMessage(self, message, noPlayer=False, noTimestamp=False, secondaryOSD=False): def showChatMessage(self, username, userMessage):
if not noPlayer: self.showOSDMessage(message, duration=constants.OSD_DURATION, secondaryOSD=secondaryOSD) messageString = u"<{}> {}".format(username, userMessage)
if self._client._player.chatOSDSupported and self._client._config["chatOutputEnabled"]:
self._client._player.displayChatMessage(username,userMessage)
else:
self.showOSDMessage(messageString, duration=constants.OSD_DURATION)
self.__ui.showMessage(messageString)
def showMessage(self, message, noPlayer=False, noTimestamp=False, OSDType=constants.OSD_NOTIFICATION,mood=constants.MESSAGE_NEUTRAL):
if not noPlayer: self.showOSDMessage(message, duration=constants.OSD_DURATION, OSDType=OSDType, mood=mood)
self.__ui.showMessage(message, noTimestamp) self.__ui.showMessage(message, noTimestamp)
def updateAutoPlayState(self, newState): def updateAutoPlayState(self, newState):
@ -1368,28 +1432,28 @@ class UiManager(object):
def showUserList(self, currentUser, rooms): def showUserList(self, currentUser, rooms):
self.__ui.showUserList(currentUser, rooms) self.__ui.showUserList(currentUser, rooms)
def showOSDMessage(self, message, duration=constants.OSD_DURATION, secondaryOSD=False): def showOSDMessage(self, message, duration=constants.OSD_DURATION, OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL):
autoplayConditionsMet = self._client.autoplayConditionsMet() autoplayConditionsMet = self._client.autoplayConditionsMet()
if secondaryOSD and not constants.SHOW_OSD_WARNINGS and not self._client.autoplayTimerIsRunning(): if OSDType == constants.OSD_ALERT and not constants.SHOW_OSD_WARNINGS and not self._client.autoplayTimerIsRunning():
return return
if not self._client._player: if not self._client._player:
return return
if constants.SHOW_OSD and self._client and self._client._player: if constants.SHOW_OSD and self._client and self._client._player:
if not self._client._player.secondaryOSDSupported: if not self._client._player.alertOSDSupported:
if secondaryOSD: if OSDType == constants.OSD_ALERT:
self.lastSecondaryOSDMessage = message self.lastAlertOSDMessage = message
if autoplayConditionsMet: if autoplayConditionsMet:
self.lastSecondaryOSDEndTime = time.time() + 1.0 self.lastAlertOSDEndTime = time.time() + 1.0
else: else:
self.lastSecondaryOSDEndTime = time.time() + constants.NO_SECONDARY_OSD_WARNING_DURATION self.lastAlertOSDEndTime = time.time() + constants.NO_ALERT_OSD_WARNING_DURATION
if self.lastPrimaryOSDEndTime and time.time() < self.lastPrimaryOSDEndTime: if self.lastNotificationOSDEndTime and time.time() < self.lastNotificationOSDEndTime:
message = u"{}{}{}".format(message, self._client._player.osdMessageSeparator, self.lastPrimaryOSDMessage) message = u"{}{}{}".format(message, self._client._player.osdMessageSeparator, self.lastNotificatinOSDMessage)
else: else:
self.lastPrimaryOSDMessage = message self.lastNotificatinOSDMessage = message
self.lastPrimaryOSDEndTime = time.time() + constants.OSD_DURATION self.lastNotificationOSDEndTime = time.time() + constants.OSD_DURATION
if self.lastSecondaryOSDEndTime and time.time() < self.lastSecondaryOSDEndTime: if self.lastAlertOSDEndTime and time.time() < self.lastAlertOSDEndTime:
message = u"{}{}{}".format(self.lastSecondaryOSDMessage, self._client._player.osdMessageSeparator, message) message = u"{}{}{}".format(self.lastAlertOSDMessage, self._client._player.osdMessageSeparator, message)
self._client._player.displayMessage(message, int(duration * 1000), secondaryOSD) self._client._player.displayMessage(message, int(duration * 1000), OSDType, mood)
def setControllerStatus(self, username, isController): def setControllerStatus(self, username, isController):
self.__ui.setControllerStatus(username, isController) self.__ui.setControllerStatus(username, isController)
@ -1411,6 +1475,9 @@ class UiManager(object):
def updateRoomName(self, room=""): def updateRoomName(self, room=""):
self.__ui.updateRoomName(room) self.__ui.updateRoomName(room)
def executeCommand(self, command):
self.__ui.executeCommand(command)
def drop(self): def drop(self):
self.__ui.drop() self.__ui.drop()
@ -1799,7 +1866,7 @@ class FileSwitchManager(object):
if os.path.isfile(filepath): if os.path.isfile(filepath):
return filepath return filepath
if highPriority and self.folderSearchEnabled: if highPriority and self.folderSearchEnabled and self.mediaDirectories is not None:
directoryList = self.mediaDirectories directoryList = self.mediaDirectories
# Spin up hard drives to prevent premature timeout # Spin up hard drives to prevent premature timeout
randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+u".txt" randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+u".txt"

View File

@ -1,8 +1,9 @@
# coding:utf8
# You might want to change these # You might want to change these
DEFAULT_PORT = 8999 DEFAULT_PORT = 8999
OSD_DURATION = 3.0 OSD_DURATION = 3.0
OSD_WARNING_MESSAGE_DURATION = 5.0 OSD_WARNING_MESSAGE_DURATION = 5.0
NO_SECONDARY_OSD_WARNING_DURATION = 13.0 NO_ALERT_OSD_WARNING_DURATION = 13.0
MPC_OSD_POSITION = 1 #Right corner, 1 for left MPC_OSD_POSITION = 1 #Right corner, 1 for left
MPLAYER_OSD_LEVEL = 1 MPLAYER_OSD_LEVEL = 1
UI_TIME_FORMAT = "[%X] " UI_TIME_FORMAT = "[%X] "
@ -56,12 +57,23 @@ PLAYLIST_MAX_CHARACTERS = 10000
PLAYLIST_MAX_ITEMS = 250 PLAYLIST_MAX_ITEMS = 250
MAXIMUM_TAB_WIDTH = 350 MAXIMUM_TAB_WIDTH = 350
TAB_PADDING = 30 TAB_PADDING = 30
DEFAULT_WINDOWS_MONOSPACE_FONT = "Consolas"
DEFAULT_OSX_MONOSPACE_FONT = "Menlo"
FALLBACK_MONOSPACE_FONT = "Monospace"
DEFAULT_CHAT_FONT_SIZE = 24
DEFAULT_CHAT_INPUT_FONT_COLOR = "#FFFF00"
DEFAULT_CHAT_OUTPUT_FONT_COLOR = "#FFFF00"
DEFAULT_CHAT_FONT_WEIGHT = 1
# Maximum character lengths (for client and server) # Max numbers are used by server (and client pre-connection). Once connected client gets values from server featureList (or uses 'fallback' versions for old servers)
MAX_CHAT_MESSAGE_LENGTH = 50 # Number of displayed characters MAX_CHAT_MESSAGE_LENGTH = 125 # Number of displayed characters
MAX_USERNAME_LENGTH = 16 # Number of displayed characters MAX_USERNAME_LENGTH = 20 # Number of displayed characters
MAX_ROOM_NAME_LENGTH = 35 # Number of displayed characters MAX_ROOM_NAME_LENGTH = 35 # Number of displayed characters
MAX_FILENAME_LENGTH = 250 # Number of displayed characters MAX_FILENAME_LENGTH = 250 # Number of displayed characters
FALLBACK_MAX_CHAT_MESSAGE_LENGTH = 50 # Number of displayed characters
FALLBACK_MAX_USERNAME_LENGTH = 16 # Number of displayed characters
FALLBACK_MAX_ROOM_NAME_LENGTH = 35 # Number of displayed characters
FALLBACK_MAX_FILENAME_LENGTH = 250 # Number of displayed characters
# Options for the File Switch feature: # Options for the File Switch feature:
FOLDER_SEARCH_FIRST_FILE_TIMEOUT = 25.0 # Secs - How long to wait to find the first file in folder search (to take account of HDD spin up) FOLDER_SEARCH_FIRST_FILE_TIMEOUT = 25.0 # Secs - How long to wait to find the first file in folder search (to take account of HDD spin up)
@ -133,7 +145,7 @@ MPC_ICONPATH = "mpc-hc.png"
MPC64_ICONPATH = "mpc-hc64.png" MPC64_ICONPATH = "mpc-hc64.png"
MPC_BE_ICONPATH = "mpc-be.png" MPC_BE_ICONPATH = "mpc-be.png"
MPV_ERROR_MESSAGES_TO_REPEAT = ['[ytdl_hook] Your version of youtube-dl is too old', '[ytdl_hook] youtube-dl failed', 'Failed to recognize file format.'] MPV_ERROR_MESSAGES_TO_REPEAT = ['[ytdl_hook] Your version of youtube-dl is too old', '[ytdl_hook] youtube-dl failed', 'Failed to recognize file format.', '[syncplayintf] Lua error']
#Changing these is usually not something you're looking for #Changing these is usually not something you're looking for
PLAYER_ASK_DELAY = 0.1 PLAYER_ASK_DELAY = 0.1
@ -179,6 +191,19 @@ MPV_ARGS = ['--force-window', '--idle', '--hr-seek=always', '--keep-open']
MPV_SLAVE_ARGS = ['--msg-level=all=error,cplayer=info,term-msg=info', '--input-terminal=no', '--input-file=/dev/stdin'] MPV_SLAVE_ARGS = ['--msg-level=all=error,cplayer=info,term-msg=info', '--input-terminal=no', '--input-file=/dev/stdin']
MPV_SLAVE_ARGS_NEW = ['--term-playing-msg=<SyncplayUpdateFile>\nANS_filename=${filename}\nANS_length=${=duration:${=length:0}}\nANS_path=${path}\n</SyncplayUpdateFile>', '--terminal=yes'] MPV_SLAVE_ARGS_NEW = ['--term-playing-msg=<SyncplayUpdateFile>\nANS_filename=${filename}\nANS_length=${=duration:${=length:0}}\nANS_path=${path}\n</SyncplayUpdateFile>', '--terminal=yes']
MPV_NEW_VERSION = False MPV_NEW_VERSION = False
MPV_OSC_VISIBILITY_CHANGE_VERSION = False
MPV_INPUT_PROMPT_START_CHARACTER = u""
MPV_INPUT_PROMPT_END_CHARACTER = u""
MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER = u""
MPV_SYNCPLAYINTF_OPTIONS_TO_SEND = ["chatInputEnabled","chatInputFontFamily", "chatInputRelativeFontSize", "chatInputFontWeight","chatInputFontUnderline",
"chatInputFontColor", "chatInputPosition","chatOutputFontFamily","chatOutputRelativeFontSize",
"chatOutputFontWeight","chatOutputFontUnderline","chatOutputMode","chatMaxLines",
"chatTopMargin","chatLeftMargin","chatBottomMargin","chatDirectInput",
"notificationTimeout","alertTimeout","chatTimeout","chatOutputEnabled"]
MPV_SYNCPLAYINTF_CONSTANTS_TO_SEND = ["MaxChatMessageLength={}".format(MAX_CHAT_MESSAGE_LENGTH),u"inputPromptStartCharacter={}".format(MPV_INPUT_PROMPT_START_CHARACTER),u"inputPromptEndCharacter={}".format(MPV_INPUT_PROMPT_END_CHARACTER),u"backslashSubstituteCharacter={}".format(MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER)]
# Note: Constants updated in client.py->checkForFeatureSupport
MPV_SYNCPLAYINTF_LANGUAGE_TO_SEND = ["mpv-key-tab-hint","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', VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek',
'--play-and-pause', '--start-time=0'] '--play-and-pause', '--start-time=0']
VLC_SLAVE_MACOS_ARGS = ['--verbose=2', '--no-file-logging'] VLC_SLAVE_MACOS_ARGS = ['--verbose=2', '--no-file-logging']
@ -200,6 +225,9 @@ UNPAUSE_IFALREADYREADY_MODE = "IfAlreadyReady"
UNPAUSE_IFOTHERSREADY_MODE = "IfOthersReady" UNPAUSE_IFOTHERSREADY_MODE = "IfOthersReady"
UNPAUSE_IFMINUSERSREADY_MODE = "IfMinUsersReady" UNPAUSE_IFMINUSERSREADY_MODE = "IfMinUsersReady"
UNPAUSE_ALWAYS_MODE = "Always" UNPAUSE_ALWAYS_MODE = "Always"
INPUT_POSITION_TOP = "Top"
INPUT_POSITION_MIDDLE = "Middle"
INPUT_POSITION_BOTTOM = "Bottom"
VLC_EOF_DURATION_THRESHOLD = 2.0 VLC_EOF_DURATION_THRESHOLD = 2.0
@ -217,6 +245,17 @@ FILEITEM_SWITCH_FILE_SWITCH = 1
FILEITEM_SWITCH_STREAM_SWITCH = 2 FILEITEM_SWITCH_STREAM_SWITCH = 2
PLAYLISTITEM_CURRENTLYPLAYING_ROLE = 3 PLAYLISTITEM_CURRENTLYPLAYING_ROLE = 3
MESSAGE_NEUTRAL = "neutral"
MESSAGE_BADNEWS = "bad"
MESSAGE_GOODNEWS = "good"
OSD_NOTIFICATION = "notification" # Also known as PrimaryOSD
OSD_ALERT = "alert" # Also known as SecondaryOSD
OSD_CHAT = "chat"
CHATROOM_MODE = "Chatroom"
SCROLLING_MODE = "Scrolling"
SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params
SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/" SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/"
SYNCPLAY_PUBLIC_SERVER_LIST_URL = u"http://syncplay.pl/listpublicservers?{}" # Params SYNCPLAY_PUBLIC_SERVER_LIST_URL = u"http://syncplay.pl/listpublicservers?{}" # Params

View File

@ -66,4 +66,6 @@ def getMessage(type_, locale=None):
if messages["en"].has_key(type_): if messages["en"].has_key(type_):
return unicode(messages["en"][type_]) return unicode(messages["en"][type_])
else: else:
raise KeyError(type_) print u"WARNING: Cannot find message '{}'!".format(type_)
return "!{}".format(type_) # TODO: Remove
#raise KeyError(type_)

View File

@ -17,23 +17,23 @@ de = {
"connected-successful-notification" : u"Erfolgreich mit Server verbunden", "connected-successful-notification" : u"Erfolgreich mit Server verbunden",
"retrying-notification" : u"%s, versuche erneut in %d Sekunden...", # Seconds "retrying-notification" : u"%s, versuche erneut in %d Sekunden...", # Seconds
"rewind-notification" : u"Zurückgespult wegen Zeitdifferenz mit <{}>", # User "rewind-notification" : u"Zurückgespult wegen Zeitdifferenz mit {}", # User
"fastforward-notification" : u"Vorgespult wegen Zeitdifferenz mit <{}>", # User "fastforward-notification" : u"Vorgespult wegen Zeitdifferenz mit {}", # User
"slowdown-notification" : u"Verlangsamt wegen Zeitdifferenz mit <{}>", # User "slowdown-notification" : u"Verlangsamt wegen Zeitdifferenz mit {}", # User
"revert-notification" : u"Normalgeschwindigkeit", "revert-notification" : u"Normalgeschwindigkeit",
"pause-notification" : u"<{}> pausierte", # User "pause-notification" : u"{} pausierte", # User
"unpause-notification" : u"<{}> startete", # User "unpause-notification" : u"{} startete", # User
"seek-notification" : u"<{}> sprang von {} nach {}", # User, from time, to time "seek-notification" : u"{} sprang von {} nach {}", # User, from time, to time
"current-offset-notification" : u"Aktueller Offset: {} Sekunden", # Offset "current-offset-notification" : u"Aktueller Offset: {} Sekunden", # Offset
"media-directory-list-updated-notification" : u"Syncplay media directories have been updated.", # TODO: Translate "media-directory-list-updated-notification" : u"Syncplay media directories have been updated.", # TODO: Translate
"room-join-notification" : u"<{}> hat den Raum '{}' betreten", # User "room-join-notification" : u"{} hat den Raum '{}' betreten", # User
"left-notification" : u"<{}> ist gegangen", # User "left-notification" : u"{} ist gegangen", # User
"left-paused-notification" : u"<{}> ist gegangen, <{}> pausierte", # User who left, User who paused "left-paused-notification" : u"{} ist gegangen, {} pausierte", # User who left, User who paused
"playing-notification" : u"<{}> spielt '{}' ({})", # User, file, duration "playing-notification" : u"{} spielt '{}' ({})", # User, file, duration
"playing-notification/room-addendum" : u" in Raum: '{}'", # Room "playing-notification/room-addendum" : u" in Raum: '{}'", # Room
"not-all-ready" : u"Noch nicht bereit: {}", # Usernames "not-all-ready" : u"Noch nicht bereit: {}", # Usernames
@ -44,11 +44,11 @@ de = {
"autoplaying-notification" : u"Starte in {}...", # Number of seconds until playback will start "autoplaying-notification" : u"Starte in {}...", # Number of seconds until playback will start
"identifying-as-controller-notification" : u"Identifiziere als Raumleiter mit Passwort '{}'...", # TODO: find a better translation to "room operator" "identifying-as-controller-notification" : u"Identifiziere als Raumleiter mit Passwort '{}'...", # TODO: find a better translation to "room operator"
"failed-to-identify-as-controller-notification" : u"<{}> konnte sich nicht als Raumleiter identifizieren.", "failed-to-identify-as-controller-notification" : u"{} konnte sich nicht als Raumleiter identifizieren.",
"authenticated-as-controller-notification" : u"<{}> authentifizierte sich als Raumleiter", "authenticated-as-controller-notification" : u"{} authentifizierte sich als Raumleiter",
"created-controlled-room-notification" : u"Gesteuerten Raum '{}' mit Passwort '{}' erstellt. Bitte diese Informationen für die Zukunft aufheben!", # RoomName, operatorPassword "created-controlled-room-notification" : u"Gesteuerten Raum '{}' mit Passwort '{}' erstellt. Bitte diese Informationen für die Zukunft aufheben!", # RoomName, operatorPassword
"file-different-notification" : u"Deine Datei scheint sich von <{}>s zu unterscheiden", # User "file-different-notification" : u"Deine Datei scheint sich von {}s zu unterscheiden", # User
"file-differences-notification" : u"Deine Datei unterscheidet sich auf folgende Art: {}", "file-differences-notification" : u"Deine Datei unterscheidet sich auf folgende Art: {}",
"room-file-differences" : u"Unterschiedlich in: {}", # File differences (filename, size, and/or duration) "room-file-differences" : u"Unterschiedlich in: {}", # File differences (filename, size, and/or duration)
"file-difference-filename" : u"Name", "file-difference-filename" : u"Name",
@ -225,6 +225,7 @@ de = {
"messages-label" : u"Nachrichten", "messages-label" : u"Nachrichten",
"messages-osd-title" : u"OSD-(OnScreenDisplay)-Einstellungen", "messages-osd-title" : u"OSD-(OnScreenDisplay)-Einstellungen",
"messages-other-title" : u"Weitere Display-Einstellungen", "messages-other-title" : u"Weitere Display-Einstellungen",
"chat-label" : u"Chat", # TODO: Translate
"privacy-label" : u"Privatsphäre", "privacy-label" : u"Privatsphäre",
"privacy-title" : u"Privatsphäreneinstellungen", "privacy-title" : u"Privatsphäreneinstellungen",
"unpause-title" : u"Wenn du Play drückst, auf Bereit setzen und:", "unpause-title" : u"Wenn du Play drückst, auf Bereit setzen und:",
@ -234,6 +235,27 @@ de = {
"unpause-always" : u"Immer wiedergeben", "unpause-always" : u"Immer wiedergeben",
"syncplay-trusteddomains-title": u"Trusted domains (for streaming services and hosted content)", # TODO: Translate into German "syncplay-trusteddomains-title": u"Trusted domains (for streaming services and hosted content)", # TODO: Translate into German
"chat-title": u"Chat message input", # TODO: Translate
"chatinputenabled-label": u"Enable chat input via mpv (using enter key)", # TODO: Translate
"chatdirectinput-label" : u"Allow instant chat input (bypass having to press enter key to chat)", # TODO: Translate
"chatinputfont-label": u"Chat input font", # TODO: Translate
"chatfont-label": u"Set font", # TODO: Translate
"chatcolour-label": u"Set colour", # TODO: Translate
"chatinputposition-label": u"Position of message input area in mpv", # TODO: Translate
"chat-top-option": u"Top", # TODO: Translate
"chat-middle-option": u"Middle", # TODO: Translate
"chat-bottom-option": u"Bottom", # TODO: Translate
"chatoutputfont-label": u"Chat output font", # TODO: Translate
"chatoutputenabled-label": u"Enable chat output in media player (mpv only for now)", # TODO: Translate
"chatoutputposition-label": u"Output mode", # TODO: Translate
"chat-chatroom-option": u"Chatroom style", # TODO: Translate
"chat-scrolling-option": u"Scrolling style", # TODO: Translate
"mpv-key-tab-hint": u"[TAB] to toggle access to alphabet row key shortcuts.", # TODO: Translate
"mpv-key-hint": u"[ENTER] to send message. [ESC] to escape chat mode.", # TODO: Translate
"alphakey-mode-warning-first-line": u"You can temporarily use old mpv bindings with a-z keys.", # TODO: Translate
"alphakey-mode-warning-second-line": u"Press [TAB] to return to Syncplay chat mode.", # TODO: Translate
"help-label" : u"Hilfe", "help-label" : u"Hilfe",
"reset-label" : u"Standardwerte zurücksetzen", "reset-label" : u"Standardwerte zurücksetzen",
"run-label" : u"Syncplay starten", "run-label" : u"Syncplay starten",
@ -357,6 +379,22 @@ de = {
"unpause-ifminusersready-tooltip" : u"Wenn du Play drückst und nicht bereit bist, wird nur gestartet, wenn die minimale Anzahl anderer Benutzer bereit ist.", "unpause-ifminusersready-tooltip" : u"Wenn du Play drückst und nicht bereit bist, wird nur gestartet, wenn die minimale Anzahl anderer Benutzer bereit ist.",
"trusteddomains-arguments-tooltip" : u"Domains that it is okay for Syncplay to automatically switch to when shared playlists is enabled.", # TODO: Translate into German "trusteddomains-arguments-tooltip" : u"Domains that it is okay for Syncplay to automatically switch to when shared playlists is enabled.", # TODO: Translate into German
"chatinputenabled-tooltip": u"Enable chat input in mpv (press enter to chat, enter to send, escape to cancel)", # TODO: Translate
"chatdirectinput-tooltip" : u"Skip having to press 'enter' to go into chat input mode in mpv. Press TAB in mpv to temporarily disable this feature.", # TODO: Translate
"font-label-tooltip": u"Font used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.", # TODO: Translate
"set-input-font-tooltip": u"Font family used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.", # TODO: Translate
"set-input-colour-tooltip": u"Font colour used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.", # TODO: Translate
"chatinputposition-tooltip": u"Location in mpv where chat input text will appear when you press enter and type.", # TODO: Translate
"chatinputposition-top-tooltip": u"Place chat input at top of mpv window.", # TODO: Translate
"chatinputposition-middle-tooltip": u"Place chat input in dead centre of mpv window.", # TODO: Translate
"chatinputposition-bottom-tooltip": u"Place chat input at bottom of mpv window.", # TODO: Translate
"chatoutputenabled-tooltip": u"Show chat messages in OSD (if supported by media player).", # TODO: Translate
"font-output-label-tooltip": u"Chat output font.", # TODO: Translate
"set-output-font-tooltip": u"Font used for when displaying chat messages.", # TODO: Translate
"chatoutputmode-tooltip": u"How chat messages are displayed.", # TODO: Translate
"chatoutputmode-chatroom-tooltip": u"Display new lines of chat directly below previous line.", # TODO: Translate
"chatoutputmode-scrolling-tooltip": u"Scroll chat text from right to left.", # TODO: Translate
"help-tooltip" : u"Öffnet Hilfe auf syncplay.pl [Englisch]", "help-tooltip" : u"Öffnet Hilfe auf syncplay.pl [Englisch]",
"reset-tooltip" : u"Alle Einstellungen auf Standardwerte zurücksetzen.", "reset-tooltip" : u"Alle Einstellungen auf Standardwerte zurücksetzen.",
"update-server-list-tooltip" : u"Mit syncplay.pl verbinden um die Liste öffentlicher Server zu aktualisieren.", "update-server-list-tooltip" : u"Mit syncplay.pl verbinden um die Liste öffentlicher Server zu aktualisieren.",
@ -420,6 +458,7 @@ de = {
"addurlstoplaylist-menu-label" : u"Add URL(s) to bottom of playlist", "addurlstoplaylist-menu-label" : u"Add URL(s) to bottom of playlist",
"editplaylist-menu-label": u"Edit playlist", "editplaylist-menu-label": u"Edit playlist",
"open-containing-folder": u"Open folder containing this file",
"addusersfiletoplaylist-menu-label" : u"Add {} file to playlist", # item owner indicator "addusersfiletoplaylist-menu-label" : u"Add {} file to playlist", # item owner indicator
"addusersstreamstoplaylist-menu-label" : u"Add {} stream to playlist", # item owner indicator "addusersstreamstoplaylist-menu-label" : u"Add {} stream to playlist", # item owner indicator
"openusersstream-menu-label" : u"Open {} stream", # [username]'s "openusersstream-menu-label" : u"Open {} stream", # [username]'s

View File

@ -17,23 +17,23 @@ en = {
"connected-successful-notification" : "Successfully connected to server", "connected-successful-notification" : "Successfully connected to server",
"retrying-notification" : "%s, Retrying in %d seconds...", # Seconds "retrying-notification" : "%s, Retrying in %d seconds...", # Seconds
"rewind-notification" : "Rewinded due to time difference with <{}>", # User "rewind-notification" : "Rewinded due to time difference with {}", # User
"fastforward-notification" : "Fast-forwarded due to time difference with <{}>", # User "fastforward-notification" : "Fast-forwarded due to time difference with {}", # User
"slowdown-notification" : "Slowing down due to time difference with <{}>", # User "slowdown-notification" : "Slowing down due to time difference with {}", # User
"revert-notification" : "Reverting speed back to normal", "revert-notification" : "Reverting speed back to normal",
"pause-notification" : u"<{}> paused", # User "pause-notification" : u"{} paused", # User
"unpause-notification" : u"<{}> unpaused", # User "unpause-notification" : u"{} unpaused", # User
"seek-notification" : u"<{}> jumped from {} to {}", # User, from time, to time "seek-notification" : u"{} jumped from {} to {}", # User, from time, to time
"current-offset-notification" : "Current offset: {} seconds", # Offset "current-offset-notification" : "Current offset: {} seconds", # Offset
"media-directory-list-updated-notification" : u"Syncplay media directories have been updated.", "media-directory-list-updated-notification" : u"Syncplay media directories have been updated.",
"room-join-notification" : u"<{}> has joined the room: '{}'", # User "room-join-notification" : u"{} has joined the room: '{}'", # User
"left-notification" : u"<{}> has left", # User "left-notification" : u"{} has left", # User
"left-paused-notification" : u"<{}> left, <{}> paused", # User who left, User who paused "left-paused-notification" : u"{} left, {} paused", # User who left, User who paused
"playing-notification" : u"<{}> is playing '{}' ({})", # User, file, duration "playing-notification" : u"{} is playing '{}' ({})", # User, file, duration
"playing-notification/room-addendum" : u" in room: '{}'", # Room "playing-notification/room-addendum" : u" in room: '{}'", # Room
"not-all-ready" : u"Not ready: {}", # Usernames "not-all-ready" : u"Not ready: {}", # Usernames
@ -44,11 +44,11 @@ en = {
"autoplaying-notification" : u"Auto-playing in {}...", # Number of seconds until playback will start "autoplaying-notification" : u"Auto-playing in {}...", # Number of seconds until playback will start
"identifying-as-controller-notification" : u"Identifying as room operator with password '{}'...", "identifying-as-controller-notification" : u"Identifying as room operator with password '{}'...",
"failed-to-identify-as-controller-notification" : u"<{}> failed to identify as a room operator.", "failed-to-identify-as-controller-notification" : u"{} failed to identify as a room operator.",
"authenticated-as-controller-notification" : u"<{}> authenticated as a room operator", "authenticated-as-controller-notification" : u"{} authenticated as a room operator",
"created-controlled-room-notification" : u"Created managed room '{}' with password '{}'. Please save this information for future reference!", # RoomName, operatorPassword "created-controlled-room-notification" : u"Created managed room '{}' with password '{}'. Please save this information for future reference!", # RoomName, operatorPassword
"file-different-notification" : "File you are playing appears to be different from <{}>'s", # User "file-different-notification" : "File you are playing appears to be different from {}'s", # User
"file-differences-notification" : u"Your file differs in the following way(s): {}", # Differences "file-differences-notification" : u"Your file differs in the following way(s): {}", # Differences
"room-file-differences" : u"File differences: {}", # File differences (filename, size, and/or duration) "room-file-differences" : u"File differences: {}", # File differences (filename, size, and/or duration)
"file-difference-filename" : u"name", "file-difference-filename" : u"name",
@ -133,7 +133,12 @@ en = {
"vlc-failed-noscript": "VLC has reported that the syncplay.lua interface script has not been installed. Please refer to http://syncplay.pl/LUA/ for instructions.", "vlc-failed-noscript": "VLC has reported that the syncplay.lua interface script has not been installed. Please refer to http://syncplay.pl/LUA/ for instructions.",
"vlc-failed-versioncheck": "This version of VLC is not supported by Syncplay.", "vlc-failed-versioncheck": "This version of VLC is not supported by Syncplay.",
"not-supported-by-server-error" : "This feature is not supported by the server. The feature requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion "feature-sharedPlaylists" : u"shared playlists", # used for not-supported-by-server-error
"feature-chat" : u"chat", # used for not-supported-by-server-error
"feature-readiness" : u"readiness", # used for not-supported-by-server-error
"feature-managedRooms" : u"managed rooms", # used for not-supported-by-server-error
"not-supported-by-server-error" : u"The {} feature is not supported by this server..", #feature
"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 "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
"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.", "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.",
@ -225,6 +230,7 @@ en = {
"messages-label" : "Messages", "messages-label" : "Messages",
"messages-osd-title" : "On-screen Display settings", "messages-osd-title" : "On-screen Display settings",
"messages-other-title" : "Other display settings", "messages-other-title" : "Other display settings",
"chat-label" : u"Chat",
"privacy-label" : "Privacy", # Currently unused, but will be brought back if more space is needed in Misc tab "privacy-label" : "Privacy", # Currently unused, but will be brought back if more space is needed in Misc tab
"privacy-title" : "Privacy settings", "privacy-title" : "Privacy settings",
"unpause-title" : u"If you press play, set as ready and:", "unpause-title" : u"If you press play, set as ready and:",
@ -234,6 +240,27 @@ en = {
"unpause-always" : u"Always unpause", "unpause-always" : u"Always unpause",
"syncplay-trusteddomains-title": u"Trusted domains (for streaming services and hosted content)", "syncplay-trusteddomains-title": u"Trusted domains (for streaming services and hosted content)",
"chat-title" : u"Chat message input",
"chatinputenabled-label" : u"Enable chat input via mpv",
"chatdirectinput-label" : u"Allow instant chat input (bypass having to press enter key to chat)",
"chatinputfont-label" : u"Chat input font",
"chatfont-label" : u"Set font",
"chatcolour-label" : u"Set colour",
"chatinputposition-label" : u"Position of message input area in mpv",
"chat-top-option" : u"Top",
"chat-middle-option" : u"Middle",
"chat-bottom-option" : u"Bottom",
"chatoutputfont-label": u"Chat output font",
"chatoutputenabled-label": u"Enable chat output in media player (mpv only for now)",
"chatoutputposition-label": u"Output mode",
"chat-chatroom-option": u"Chatroom style",
"chat-scrolling-option": u"Scrolling style",
"mpv-key-tab-hint": u"[TAB] to toggle access to alphabet row key shortcuts.",
"mpv-key-hint": u"[ENTER] to send message. [ESC] to escape chat mode.",
"alphakey-mode-warning-first-line": u"You can temporarily use old mpv bindings with a-z keys.",
"alphakey-mode-warning-second-line": u"Press [TAB] to return to Syncplay chat mode.",
"help-label" : "Help", "help-label" : "Help",
"reset-label" : "Restore defaults", "reset-label" : "Restore defaults",
"run-label" : "Run Syncplay", "run-label" : "Run Syncplay",
@ -355,6 +382,22 @@ en = {
"unpause-ifminusersready-tooltip" : u"If you press unpause when not ready, it will only unpause if others are ready and minimum users threshold is met.", "unpause-ifminusersready-tooltip" : u"If you press unpause when not ready, it will only unpause if others are ready and minimum users threshold is met.",
"trusteddomains-arguments-tooltip" : u"Domains that it is okay for Syncplay to automatically switch to when shared playlists is enabled.", "trusteddomains-arguments-tooltip" : u"Domains that it is okay for Syncplay to automatically switch to when shared playlists is enabled.",
"chatinputenabled-tooltip" : u"Enable chat input in mpv (press enter to chat, enter to send, escape to cancel)",
"chatdirectinput-tooltip" : u"Skip having to press 'enter' to go into chat input mode in mpv. Press TAB in mpv to temporarily disable this feature.",
"font-label-tooltip" : u"Font used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.",
"set-input-font-tooltip" : u"Font family used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.",
"set-input-colour-tooltip" : u"Font colour used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.",
"chatinputposition-tooltip" : u"Location in mpv where chat input text will appear when you press enter and type.",
"chatinputposition-top-tooltip" : u"Place chat input at top of mpv window.",
"chatinputposition-middle-tooltip" : u"Place chat input in dead centre of mpv window.",
"chatinputposition-bottom-tooltip" : u"Place chat input at bottom of mpv window.",
"chatoutputenabled-tooltip": u"Show chat messages in OSD (if supported by media player).",
"font-output-label-tooltip": u"Chat output font.",
"set-output-font-tooltip": u"Font used for when displaying chat messages.",
"chatoutputmode-tooltip": u"How chat messages are displayed.",
"chatoutputmode-chatroom-tooltip": u"Display new lines of chat directly below previous line.",
"chatoutputmode-scrolling-tooltip": u"Scroll chat text from right to left.",
"help-tooltip" : "Opens the Syncplay.pl user guide.", "help-tooltip" : "Opens the Syncplay.pl user guide.",
"reset-tooltip" : "Reset all settings to the default configuration.", "reset-tooltip" : "Reset all settings to the default configuration.",
"update-server-list-tooltip" : u"Connect to syncplay.pl to update list of public servers.", "update-server-list-tooltip" : u"Connect to syncplay.pl to update list of public servers.",

View File

@ -17,23 +17,23 @@ ru = {
"connected-successful-notification" : u"Соединение с сервером установлено", "connected-successful-notification" : u"Соединение с сервером установлено",
"retrying-notification" : u"%s, следующая попытка через %d секунд(ы)...", # Seconds "retrying-notification" : u"%s, следующая попытка через %d секунд(ы)...", # Seconds
"rewind-notification" : u"Перемотано из-за разницы во времени с <{}>", # User "rewind-notification" : u"Перемотано из-за разницы во времени с {}", # User
"fastforward-notification" : u"Ускорено из-за разницы во времени с <{}>", # User "fastforward-notification" : u"Ускорено из-за разницы во времени с {}", # User
"slowdown-notification" : u"Воспроизведение замедлено из-за разницы во времени с <{}>", # User "slowdown-notification" : u"Воспроизведение замедлено из-за разницы во времени с {}", # User
"revert-notification" : u"Возвращаемся к нормальной скорости воспроизведения", "revert-notification" : u"Возвращаемся к нормальной скорости воспроизведения",
"pause-notification" : u"<{}> приостановил воспроизведение", # User "pause-notification" : u"{} приостановил воспроизведение", # User
"unpause-notification" : u"<{}> возобновил воспроизведение", # User "unpause-notification" : u"{} возобновил воспроизведение", # User
"seek-notification" : u"<{}> перемотал с {} на {}", # User, from time, to time "seek-notification" : u"{} перемотал с {} на {}", # User, from time, to time
"current-offset-notification" : u"Текущее смещение: {} секунд(ы)", # Offset "current-offset-notification" : u"Текущее смещение: {} секунд(ы)", # Offset
"media-directory-list-updated-notification" : u"Папки воспроизведения обновлены.", "media-directory-list-updated-notification" : u"Папки воспроизведения обновлены.",
"room-join-notification" : u"<{}> зашел в комнату: '{}'", # User "room-join-notification" : u"{} зашел в комнату: '{}'", # User
"left-notification" : u"<{}> покинул комнату", # User "left-notification" : u"{} покинул комнату", # User
"left-paused-notification" : u"<{}> покинул комнату, <{}> приостановил воспроизведение", # User who left, User who paused "left-paused-notification" : u"{} покинул комнату, {} приостановил воспроизведение", # User who left, User who paused
"playing-notification" : u"<{}> включил '{}' ({})", # User, file, duration "playing-notification" : u"{} включил '{}' ({})", # User, file, duration
"playing-notification/room-addendum" : u" в комнате: '{}'", # Room "playing-notification/room-addendum" : u" в комнате: '{}'", # Room
"not-all-ready" : u"Не готовы: {}", # Usernames "not-all-ready" : u"Не готовы: {}", # Usernames
@ -44,11 +44,11 @@ ru = {
"autoplaying-notification" : u"Автовоспроизведение через {}...", # Number of seconds until playback will start "autoplaying-notification" : u"Автовоспроизведение через {}...", # Number of seconds until playback will start
"identifying-as-controller-notification" : u"Идентификация как оператора комнаты с паролем '{}'...", "identifying-as-controller-notification" : u"Идентификация как оператора комнаты с паролем '{}'...",
"failed-to-identify-as-controller-notification" : u"<{}> не прошел идентификацию в качестве оператора комнаты.", "failed-to-identify-as-controller-notification" : u"{} не прошел идентификацию в качестве оператора комнаты.",
"authenticated-as-controller-notification" : u"<{}> вошел как оператор комнаты.", "authenticated-as-controller-notification" : u"{} вошел как оператор комнаты.",
"created-controlled-room-notification" : u"Создана управляемая комната '{}' с паролем '{}'. Сохраните эти данные!", # RoomName, operatorPassword "created-controlled-room-notification" : u"Создана управляемая комната '{}' с паролем '{}'. Сохраните эти данные!", # RoomName, operatorPassword
"file-different-notification" : u"Вероятно, файл, который Вы смотрите, отличается от того, который смотрит <{}>.", # User "file-different-notification" : u"Вероятно, файл, который Вы смотрите, отличается от того, который смотрит {}.", # User
"file-differences-notification" : u"Ваш файл отличается: {}", # Differences "file-differences-notification" : u"Ваш файл отличается: {}", # Differences
"room-file-differences" : u"Несовпадения файла: {}", # File differences (filename, size, and/or duration) "room-file-differences" : u"Несовпадения файла: {}", # File differences (filename, size, and/or duration)
"file-difference-filename" : u"имя", "file-difference-filename" : u"имя",
@ -135,7 +135,13 @@ ru = {
"vlc-failed-versioncheck" : u"Данная версия VLC не поддерживается Syncplay. Пожалуйста, используйте VLC версии 2 или выше.", "vlc-failed-versioncheck" : u"Данная версия VLC не поддерживается Syncplay. Пожалуйста, используйте VLC версии 2 или выше.",
"vlc-failed-other" : u"Во время загрузки скрипта интерфейса syncplay.lua в VLC произошла следующая ошибка: {}", # Syncplay Error "vlc-failed-other" : u"Во время загрузки скрипта интерфейса syncplay.lua в VLC произошла следующая ошибка: {}", # Syncplay Error
"not-supported-by-server-error" : u"Эта возможность не поддерживается сервером. Требуется сервер Syncplay {}+, вы подключены к серверу Syncplay {}.", #minVersion, serverVersion "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"Эта возможность не поддерживается сервером. Требуется сервер Syncplay {}+, вы подключены к серверу Syncplay {}.", #minVersion, serverVersion
"shared-playlists-not-supported-by-server-error" : u"Общие списки воспроизведения могут не поддерживаться сервером. Для корректной работы требуется сервер Syncplay {}+, вы подключены к серверу Syncplay {}.", #minVersion, serverVersion "shared-playlists-not-supported-by-server-error" : u"Общие списки воспроизведения могут не поддерживаться сервером. Для корректной работы требуется сервер Syncplay {}+, вы подключены к серверу Syncplay {}.", #minVersion, serverVersion
"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 "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
@ -227,6 +233,7 @@ ru = {
"messages-label" : u"Сообщения", "messages-label" : u"Сообщения",
"messages-osd-title" : u"Настройки OSD", "messages-osd-title" : u"Настройки OSD",
"messages-other-title" : u"Другие настройки отображения", "messages-other-title" : u"Другие настройки отображения",
"chat-label" : u"Chat", # TODO: Translate
"privacy-label" : u"Приватность", "privacy-label" : u"Приватность",
"privacy-title" : u"Настройки приватности", "privacy-title" : u"Настройки приватности",
"unpause-title" : u"Если вы стартуете, то:", "unpause-title" : u"Если вы стартуете, то:",
@ -237,6 +244,27 @@ ru = {
"syncplay-trusteddomains-title": u"Доверенные сайты (стрим-сервисы, видеохостинги, файлы в сети)", "syncplay-trusteddomains-title": u"Доверенные сайты (стрим-сервисы, видеохостинги, файлы в сети)",
"addtrusteddomain-menu-label" : u"Добавить {} как доверенный сайт", # Domain "addtrusteddomain-menu-label" : u"Добавить {} как доверенный сайт", # Domain
"chat-title": u"Chat message input", # TODO: Translate
"chatinputenabled-label": u"Enable chat input via mpv (using enter key)", # TODO: Translate
"chatdirectinput-label" : u"Allow instant chat input (bypass having to press enter key to chat)", # TODO: Translate
"chatinputfont-label": u"Chat input font", # TODO: Translate
"chatfont-label": u"Set font", # TODO: Translate
"chatcolour-label": u"Set colour", # TODO: Translate
"chatinputposition-label": u"Position of message input area in mpv", # TODO: Translate
"chat-top-option": u"Top", # TODO: Translate
"chat-middle-option": u"Middle", # TODO: Translate
"chat-bottom-option": u"Bottom", # TODO: Translate
"chatoutputfont-label": u"Chat output font", # TODO: Translate
"chatoutputenabled-label": u"Enable chat output in media player (mpv only for now)", # TODO: Translate
"chatoutputposition-label": u"Output mode", # TODO: Translate
"chat-chatroom-option": u"Chatroom style", # TODO: Translate
"chat-scrolling-option": u"Scrolling style", # TODO: Translate
"mpv-key-tab-hint": u"[TAB] to toggle access to alphabet row key shortcuts.", # TODO: Translate
"mpv-key-hint": u"[ENTER] to send message. [ESC] to escape chat mode.", # TODO: Translate
"alphakey-mode-warning-first-line": u"You can temporarily use old mpv bindings with a-z keys.", # TODO: Translate
"alphakey-mode-warning-second-line": u"Press [TAB] to return to Syncplay chat mode.", # TODO: Translate
"help-label" : u"Помощь", "help-label" : u"Помощь",
"reset-label" : u"Сброс настроек", "reset-label" : u"Сброс настроек",
"run-label" : u"Запустить", "run-label" : u"Запустить",
@ -357,6 +385,22 @@ ru = {
"unpause-ifminusersready-tooltip" : u"Когда вы стартуете не готовым, воспроизведение начнется, если остальные готовы и присутствует достаточное число зрителей.", "unpause-ifminusersready-tooltip" : u"Когда вы стартуете не готовым, воспроизведение начнется, если остальные готовы и присутствует достаточное число зрителей.",
"trusteddomains-arguments-tooltip" : u"Сайты, которые разрешены для автоматического воспроизведения из общего списка воспроизведения.", "trusteddomains-arguments-tooltip" : u"Сайты, которые разрешены для автоматического воспроизведения из общего списка воспроизведения.",
"chatinputenabled-tooltip": u"Enable chat input in mpv (press enter to chat, enter to send, escape to cancel)",# TODO: Translate
"chatdirectinput-tooltip" : u"Skip having to press 'enter' to go into chat input mode in mpv. Press TAB in mpv to temporarily disable this feature.", # TODO: Translate
"font-label-tooltip": u"Font used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.",# TODO: Translate
"set-input-font-tooltip": u"Font family used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.",# TODO: Translate
"set-input-colour-tooltip": u"Font colour used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.",# TODO: Translate
"chatinputposition-tooltip": u"Location in mpv where chat input text will appear when you press enter and type.",# TODO: Translate
"chatinputposition-top-tooltip": u"Place chat input at top of mpv window.", # TODO: Translate
"chatinputposition-middle-tooltip": u"Place chat input in dead centre of mpv window.", # TODO: Translate
"chatinputposition-bottom-tooltip": u"Place chat input at bottom of mpv window.", # TODO: Translate
"chatoutputenabled-tooltip": u"Show chat messages in OSD (if supported by media player).", # TODO: Translate
"font-output-label-tooltip": u"Chat output font.", # TODO: Translate
"set-output-font-tooltip": u"Font used for when displaying chat messages.", # TODO: Translate
"chatoutputmode-tooltip": u"How chat messages are displayed.", # TODO: Translate
"chatoutputmode-chatroom-tooltip": u"Display new lines of chat directly below previous line.", # TODO: Translate
"chatoutputmode-scrolling-tooltip": u"Scroll chat text from right to left.", # TODO: Translate
"help-tooltip" : u"Открыть Руководство Пользователя на Syncplay.pl.", "help-tooltip" : u"Открыть Руководство Пользователя на Syncplay.pl.",
"reset-tooltip" : u"Сбрасывает все настройки Syncplay в начальное состояние.", "reset-tooltip" : u"Сбрасывает все настройки Syncplay в начальное состояние.",
"update-server-list-tooltip" : u"Обновить список публичных серверов от syncplay.pl.", "update-server-list-tooltip" : u"Обновить список публичных серверов от syncplay.pl.",
@ -419,6 +463,7 @@ ru = {
"addurlstoplaylist-menu-label" : u"Добавить ссылку в очередь", "addurlstoplaylist-menu-label" : u"Добавить ссылку в очередь",
"editplaylist-menu-label": u"Редактировать список", "editplaylist-menu-label": u"Редактировать список",
"open-containing-folder": u"Open folder containing this file", # TODO: Traslate
"addusersfiletoplaylist-menu-label" : u"Добавить файл {} в список воспроизведения", # item owner indicator "addusersfiletoplaylist-menu-label" : u"Добавить файл {} в список воспроизведения", # item owner indicator
"addusersstreamstoplaylist-menu-label" : u"Добавить поток {} в список воспроизведения", # item owner indicator "addusersstreamstoplaylist-menu-label" : u"Добавить поток {} в список воспроизведения", # item owner indicator
"openusersstream-menu-label" : u"Открыть поток от {}", # [username]'s "openusersstream-menu-label" : u"Открыть поток от {}", # [username]'s

View File

@ -12,7 +12,7 @@ class BasePlayer(object):
''' '''
Display given message on player's OSD or similar means Display given message on player's OSD or similar means
''' '''
def displayMessage(self, message, duration = (constants.OSD_DURATION*1000)): def displayMessage(self, message, duration = (constants.OSD_DURATION*1000), secondaryOSD=False, mood=constants.MESSAGE_NEUTRAL):
raise NotImplementedError() raise NotImplementedError()
''' '''
@ -34,6 +34,12 @@ class BasePlayer(object):
def setPaused(self, value): def setPaused(self, value):
raise NotImplementedError() raise NotImplementedError()
'''
@type value: list
'''
def setFeatures(self, featureList):
raise NotImplementedError()
''' '''
@type value: float @type value: float
''' '''

View File

@ -306,8 +306,9 @@ class MpcHcApi:
class MPCHCAPIPlayer(BasePlayer): class MPCHCAPIPlayer(BasePlayer):
speedSupported = False speedSupported = False
secondaryOSDSupported = False alertOSDSupported = False
customOpenDialog = False customOpenDialog = False
chatOSDSupported = False
osdMessageSeparator = "; " osdMessageSeparator = "; "
def __init__(self, client): def __init__(self, client):
@ -399,7 +400,7 @@ class MPCHCAPIPlayer(BasePlayer):
def openFile(self, filePath, resetPosition=False): def openFile(self, filePath, resetPosition=False):
self._mpcApi.openFile(filePath) self._mpcApi.openFile(filePath)
def displayMessage(self, message, duration = (constants.OSD_DURATION*1000), secondaryOSD=False): def displayMessage(self, message, duration = (constants.OSD_DURATION*1000), OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL):
self._mpcApi.sendOsd(message, constants.MPC_OSD_POSITION, duration) self._mpcApi.sendOsd(message, constants.MPC_OSD_POSITION, duration)
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1) @retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
@ -411,6 +412,8 @@ class MPCHCAPIPlayer(BasePlayer):
self._mpcApi.pause() self._mpcApi.pause()
else: else:
self._mpcApi.unpause() self._mpcApi.unpause()
def setFeatures(self, featureList):
pass
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1) @retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
def setPosition(self, value): def setPosition(self, value):

View File

@ -1,3 +1,4 @@
# coding:utf8
import subprocess import subprocess
import re import re
import threading import threading
@ -11,7 +12,8 @@ from syncplay.utils import isWindows
class MplayerPlayer(BasePlayer): class MplayerPlayer(BasePlayer):
speedSupported = True speedSupported = True
customOpenDialog = False customOpenDialog = False
secondaryOSDSupported = False alertOSDSupported = False
chatOSDSupported = False
osdMessageSeparator = "; " osdMessageSeparator = "; "
RE_ANSWER = re.compile(constants.MPLAYER_ANSWER_REGEX) RE_ANSWER = re.compile(constants.MPLAYER_ANSWER_REGEX)
@ -88,8 +90,15 @@ class MplayerPlayer(BasePlayer):
def _getProperty(self, property_): def _getProperty(self, property_):
self._listener.sendLine("get_property {}".format(property_)) self._listener.sendLine("get_property {}".format(property_))
def displayMessage(self, message, duration=(constants.OSD_DURATION * 1000), secondaryOSD=False): def displayMessage(self, message, duration=(constants.OSD_DURATION * 1000), OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL):
self._listener.sendLine(u'{} "{!s}" {} {}'.format(self.OSD_QUERY, self._stripNewlines(message), duration, constants.MPLAYER_OSD_LEVEL).encode('utf-8')) messageString = self._sanitizeText(message.replace("\\n", "<NEWLINE>")).replace("<NEWLINE>", "\\n")
self._listener.sendLine(u'{} "{!s}" {} {}'.format(self.OSD_QUERY, messageString, duration, constants.MPLAYER_OSD_LEVEL).encode('utf-8'))
def displayChatMessage(self, username, message):
messageString = u"<{}> {}".format(username, message)
messageString = self._sanitizeText(messageString.replace("\\n", "<NEWLINE>")).replace("<NEWLINE>", "\\n")
duration = int(constants.OSD_DURATION * 1000)
self._listener.sendLine(u'{} "{!s}" {} {}'.format(self.OSD_QUERY, messageString, duration, constants.MPLAYER_OSD_LEVEL).encode('utf-8'))
def setSpeed(self, value): def setSpeed(self, value):
self._setProperty('speed', "{:.2f}".format(value)) self._setProperty('speed', "{:.2f}".format(value))
@ -105,6 +114,9 @@ class MplayerPlayer(BasePlayer):
self.setPaused(self._client.getGlobalPaused()) self.setPaused(self._client.getGlobalPaused())
self.setPosition(self._client.getGlobalPosition()) self.setPosition(self._client.getGlobalPosition())
def setFeatures(self, featureList):
pass
def setPosition(self, value): def setPosition(self, value):
self._position = max(value,0) self._position = max(value,0)
self._setProperty(self.POSITION_QUERY, "{}".format(value)) self._setProperty(self.POSITION_QUERY, "{}".format(value))
@ -130,9 +142,16 @@ class MplayerPlayer(BasePlayer):
def _getPosition(self): def _getPosition(self):
self._getProperty(self.POSITION_QUERY) self._getProperty(self.POSITION_QUERY)
def _stripNewlines(self, text): def _sanitizeText(self, text):
text = text.replace("\r", "") text = text.replace("\r", "")
text = text.replace("\n", "") text = text.replace("\n", "")
text = text.replace("\\\"", "<SYNCPLAY_QUOTE>")
text = text.replace("\"", "<SYNCPLAY_QUOTE>")
text = text.replace("%", "%%")
text = text.replace("\\", "\\\\")
text = text.replace("{", "\\\\{")
text = text.replace("}", "\\\\}")
text = text.replace("<SYNCPLAY_QUOTE>","\\\"")
return text return text
def _quoteArg(self, arg): def _quoteArg(self, arg):
@ -158,6 +177,8 @@ class MplayerPlayer(BasePlayer):
def lineReceived(self, line): def lineReceived(self, line):
if line: if line:
self._client.ui.showDebugMessage("player << {}".format(line)) self._client.ui.showDebugMessage("player << {}".format(line))
line = line.replace("[cplayer] ", "") # -v workaround
line = line.replace("[term-msg] ", "") # -v workaround
line = line.replace(" cplayer: ","") # --msg-module workaround line = line.replace(" cplayer: ","") # --msg-module workaround
line = line.replace(" term-msg: ", "") line = line.replace(" term-msg: ", "")
if "Failed to get value of property" in line or "=(unavailable)" in line or line == "ANS_filename=" or line == "ANS_length=" or line == "ANS_path=": if "Failed to get value of property" in line or "=(unavailable)" in line or line == "ANS_filename=" or line == "ANS_length=" or line == "ANS_path=":
@ -277,6 +298,9 @@ class MplayerPlayer(BasePlayer):
self.lastSendTime = None self.lastSendTime = None
self.lastNotReadyTime = None self.lastNotReadyTime = None
self.__playerController = playerController self.__playerController = playerController
if not self.__playerController._client._config["chatOutputEnabled"]:
self.__playerController.alertOSDSupported = False
self.__playerController.chatOSDSupported = False
if self.__playerController.getPlayerPathErrors(playerPath,filePath): if self.__playerController.getPlayerPathErrors(playerPath,filePath):
raise ValueError() raise ValueError()
if filePath and '://' not in filePath: if filePath and '://' not in filePath:
@ -332,6 +356,18 @@ class MplayerPlayer(BasePlayer):
self.__playerController.lineReceived(line) self.__playerController.lineReceived(line)
self.__playerController.drop() self.__playerController.drop()
def sendChat(self, message):
if message:
if message[:1] == "/" and message <> "/":
command = message[1:]
if command and command[:1] == "/":
message = message[1:]
else:
self.__playerController.reactor.callFromThread(self.__playerController._client.ui.executeCommand,
command)
return
self.__playerController.reactor.callFromThread(self.__playerController._client.sendChat, message)
def isReadyForSend(self): def isReadyForSend(self):
self.checkForReadinessOverride() self.checkForReadinessOverride()
return self.readyToSend return self.readyToSend

View File

@ -1,14 +1,16 @@
# coding:utf8
import re import re
import subprocess import subprocess
from syncplay.players.mplayer import MplayerPlayer from syncplay.players.mplayer import MplayerPlayer
from syncplay.messages import getMessage from syncplay.messages import getMessage
from syncplay import constants from syncplay import constants
from syncplay.utils import isURL from syncplay.utils import isURL, findResourcePath
import os, sys, time import os, sys, time
class MpvPlayer(MplayerPlayer): class MpvPlayer(MplayerPlayer):
RE_VERSION = re.compile('.*mpv (\d+)\.(\d+)\.\d+.*') RE_VERSION = re.compile('.*mpv (\d+)\.(\d+)\.\d+.*')
osdMessageSeparator = "\\n" osdMessageSeparator = "\\n"
osdMessageSeparator = "; " # TODO: Make conditional
@staticmethod @staticmethod
def run(client, playerPath, filePath, args): def run(client, playerPath, filePath, args):
@ -17,6 +19,9 @@ class MpvPlayer(MplayerPlayer):
except: except:
ver = None ver = None
constants.MPV_NEW_VERSION = ver is None or int(ver.group(1)) > 0 or int(ver.group(2)) >= 6 constants.MPV_NEW_VERSION = ver is None or int(ver.group(1)) > 0 or int(ver.group(2)) >= 6
constants.MPV_OSC_VISIBILITY_CHANGE_VERSION = False if ver is None else int(ver.group(1)) > 0 or int(ver.group(2)) >= 28
if not constants.MPV_OSC_VISIBILITY_CHANGE_VERSION:
client.ui.showDebugMessage(u"This version of mpv is not known to be compatible with changing the OSC visibility. Please use mpv >=0.28.0.")
if constants.MPV_NEW_VERSION: if constants.MPV_NEW_VERSION:
return NewMpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args) return NewMpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args)
else: else:
@ -30,6 +35,7 @@ class MpvPlayer(MplayerPlayer):
args.extend(constants.MPV_SLAVE_ARGS) args.extend(constants.MPV_SLAVE_ARGS)
if constants.MPV_NEW_VERSION: if constants.MPV_NEW_VERSION:
args.extend(constants.MPV_SLAVE_ARGS_NEW) args.extend(constants.MPV_SLAVE_ARGS_NEW)
args.extend([u"--script={}".format(findResourcePath("syncplayintf.lua"))])
return args return args
@staticmethod @staticmethod
@ -108,6 +114,25 @@ class OldMpvPlayer(MpvPlayer):
class NewMpvPlayer(OldMpvPlayer): class NewMpvPlayer(OldMpvPlayer):
lastResetTime = None lastResetTime = None
lastMPVPositionUpdate = None lastMPVPositionUpdate = None
alertOSDSupported = True
chatOSDSupported = True
def displayMessage(self, message, duration=(constants.OSD_DURATION * 1000), OSDType=constants.OSD_NOTIFICATION,
mood=constants.MESSAGE_NEUTRAL):
if not self._client._config["chatOutputEnabled"]:
super(self.__class__, self).displayMessage(message=message,duration=duration,OSDType=OSDType,mood=mood)
return
messageString = self._sanitizeText(message.replace("\\n", "<NEWLINE>")).replace("\\\\",constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER).replace("<NEWLINE>", "\\n")
self._listener.sendLine(u'script-message-to syncplayintf {}-osd-{} "{}"'.format(OSDType, mood, messageString))
def displayChatMessage(self, username, message):
if not self._client._config["chatOutputEnabled"]:
super(self.__class__, self).displayChatMessage(username,message)
return
username = self._sanitizeText(username.replace("\\",constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER))
message = self._sanitizeText(message.replace("\\",constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER))
messageString = u"<{}> {}".format(username, message)
self._listener.sendLine(u'script-message-to syncplayintf chat "{}"'.format(messageString))
def setPaused(self, value): def setPaused(self, value):
if self._paused == value: if self._paused == value:
@ -199,6 +224,9 @@ class NewMpvPlayer(OldMpvPlayer):
self._clearFileLoaded() self._clearFileLoaded()
self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath)), notReadyAfterThis=True) self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath)), notReadyAfterThis=True)
def setFeatures(self, featureList):
self.sendMpvOptions()
def setPosition(self, value): def setPosition(self, value):
if value < constants.DO_NOT_RESET_POSITION_THRESHOLD and self._recentlyReset(): if value < constants.DO_NOT_RESET_POSITION_THRESHOLD and self._recentlyReset():
self._client.ui.showDebugMessage("Did not seek as recently reset and {} below 'do not reset position' threshold".format(value)) self._client.ui.showDebugMessage("Did not seek as recently reset and {} below 'do not reset position' threshold".format(value))
@ -222,9 +250,29 @@ class NewMpvPlayer(OldMpvPlayer):
else: else:
self._storePosition(0) self._storePosition(0)
def sendMpvOptions(self):
options = []
for option in constants.MPV_SYNCPLAYINTF_OPTIONS_TO_SEND:
options.append(u"{}={}".format(option, self._client._config[option]))
for option in constants.MPV_SYNCPLAYINTF_CONSTANTS_TO_SEND:
options.append(option)
for option in constants.MPV_SYNCPLAYINTF_LANGUAGE_TO_SEND:
options.append(u"{}={}".format(option, getMessage(option)))
options.append(u"OscVisibilityChangeCompatible={}".format(constants.MPV_OSC_VISIBILITY_CHANGE_VERSION))
options_string = ", ".join(options)
self._listener.sendLine(u'script-message-to syncplayintf set_syncplayintf_options "{}"'.format(options_string))
self._setOSDPosition()
def _handleUnknownLine(self, line): def _handleUnknownLine(self, line):
self.mpvErrorCheck(line) self.mpvErrorCheck(line)
if "<chat>" in line:
line = line.decode("utf-8").replace(constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER, "\\").encode("utf-8")
self._listener.sendChat(line[6:-7])
if "<get_syncplayintf_options>" in line:
self.sendMpvOptions()
if line == "<SyncplayUpdateFile>" or "Playing:" in line: if line == "<SyncplayUpdateFile>" or "Playing:" in line:
self._listener.setReadyToSend(False) self._listener.setReadyToSend(False)
self._clearFileLoaded() self._clearFileLoaded()
@ -236,6 +284,11 @@ class NewMpvPlayer(OldMpvPlayer):
elif "Failed" in line or "failed" in line or "No video or audio streams selected" in line or "error" in line: elif "Failed" in line or "failed" in line or "No video or audio streams selected" in line or "error" in line:
self._listener.setReadyToSend(True) self._listener.setReadyToSend(True)
def _setOSDPosition(self):
if self._client._config['chatMoveOSD'] and (self._client._config['chatOutputEnabled'] or (self._client._config['chatInputEnabled'] and self._client._config['chatInputPosition'] == constants.INPUT_POSITION_TOP)):
self._setProperty("osd-align-y", "bottom")
self._setProperty("osd-margin-y", int(self._client._config['chatOSDMargin']))
def _recentlyReset(self): def _recentlyReset(self):
if not self.lastResetTime: if not self.lastResetTime:
return False return False

View File

@ -16,7 +16,8 @@ from syncplay.utils import isBSD, isLinux, isWindows, isMacOS
class VlcPlayer(BasePlayer): class VlcPlayer(BasePlayer):
speedSupported = True speedSupported = True
customOpenDialog = False customOpenDialog = False
secondaryOSDSupported = True chatOSDSupported = False
alertOSDSupported = True
osdMessageSeparator = "; " osdMessageSeparator = "; "
RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX) RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX)
@ -118,9 +119,9 @@ class VlcPlayer(BasePlayer):
else: else:
return self._position return self._position
def displayMessage(self, message, duration=constants.OSD_DURATION * 1000, secondaryOSD=False): def displayMessage(self, message, duration=constants.OSD_DURATION * 1000, OSDType=constants.OSD_DURATION, mood=constants.MESSAGE_NEUTRAL):
duration /= 1000 duration /= 1000
if secondaryOSD == False: if OSDType != constants.OSD_ALERT:
self._listener.sendLine('display-osd: {}, {}, {}'.format('top-right', duration, message.encode('utf8'))) self._listener.sendLine('display-osd: {}, {}, {}'.format('top-right', duration, message.encode('utf8')))
else: else:
self._listener.sendLine('display-secondary-osd: {}, {}, {}'.format('center', duration, message.encode('utf8'))) self._listener.sendLine('display-secondary-osd: {}, {}, {}'.format('center', duration, message.encode('utf8')))
@ -128,6 +129,9 @@ class VlcPlayer(BasePlayer):
def setSpeed(self, value): def setSpeed(self, value):
self._listener.sendLine("set-rate: {:.2n}".format(value)) self._listener.sendLine("set-rate: {:.2n}".format(value))
def setFeatures(self, featureList):
pass
def setPosition(self, value): def setPosition(self, value):
self._lastVLCPositionUpdate = time.time() self._lastVLCPositionUpdate = time.time()
self._listener.sendLine("set-position: {}".format(value).replace(".",self.radixChar)) self._listener.sendLine("set-position: {}".format(value).replace(".",self.radixChar))

View File

@ -5,8 +5,8 @@ import syncplay
from functools import wraps from functools import wraps
import time import time
from syncplay.messages import getMessage from syncplay.messages import getMessage
from syncplay.constants import PING_MOVING_AVERAGE_WEIGHT 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.utils import meetsMinVersion
class JSONCommandProtocol(LineReceiver): class JSONCommandProtocol(LineReceiver):
def handleMessages(self, messages): def handleMessages(self, messages):
@ -107,6 +107,7 @@ class SyncClientProtocol(JSONCommandProtocol):
if room: hello["room"] = {"name" :room} if room: hello["room"] = {"name" :room}
hello["version"] = "1.2.255" # Used so newer clients work on 1.2.X server hello["version"] = "1.2.255" # Used so newer clients work on 1.2.X server
hello["realversion"] = syncplay.version hello["realversion"] = syncplay.version
hello["features"] = self._client.getFeatures()
self.sendMessage({"Hello": hello}) self.sendMessage({"Hello": hello})
def _SetUser(self, users): def _SetUser(self, users):
@ -147,6 +148,11 @@ class SyncClientProtocol(JSONCommandProtocol):
self._client.playlist.changeToPlaylistIndex(values['index'], values['user']) self._client.playlist.changeToPlaylistIndex(values['index'], values['user'])
elif command == "playlistChange": elif command == "playlistChange":
self._client.playlist.changePlaylist(values['files'], values['user']) self._client.playlist.changePlaylist(values['files'], values['user'])
elif command == "features":
self._client.setUserFeatures(values["username"],values['features'])
def sendFeaturesUpdate(self, features):
self.sendSet({"features": features})
def sendSet(self, setting): def sendSet(self, setting):
self.sendMessage({"Set": setting}) self.sendMessage({"Set": setting})
@ -173,7 +179,8 @@ class SyncClientProtocol(JSONCommandProtocol):
file_ = user[1]['file'] if user[1]['file'] <> {} else None file_ = user[1]['file'] if user[1]['file'] <> {} else None
isController = user[1]['controller'] if 'controller' in user[1] else False isController = user[1]['controller'] if 'controller' in user[1] else False
isReady = user[1]['isReady'] if 'isReady' in user[1] else None isReady = user[1]['isReady'] if 'isReady' in user[1] else None
self._client.userlist.addUser(userName, roomName, file_, noMessage=True, isController=isController, isReady=isReady) features = user[1]['features'] if 'features' in user[1] else None
self._client.userlist.addUser(userName, roomName, file_, noMessage=True, isController=isController, isReady=isReady, features=features)
self._client.userlist.showUserList() self._client.userlist.showUserList()
def sendList(self): def sendList(self):
@ -249,9 +256,9 @@ class SyncClientProtocol(JSONCommandProtocol):
} }
}) })
def handleChat(self,message): def handleChat(self,message):
username = message['username']
userMessage = message['message'] userMessage = message['message']
messageString = u"<{}> {}".format(message['username'], userMessage) self._client.ui.showChatMessage(username, userMessage)
self._client.ui.showMessage(messageString)
def setReady(self, isReady, manuallyInitiated=True): def setReady(self, isReady, manuallyInitiated=True):
self.sendSet({ self.sendSet({
@ -286,6 +293,7 @@ class SyncServerProtocol(JSONCommandProtocol):
def __init__(self, factory): def __init__(self, factory):
self._factory = factory self._factory = factory
self._version = None self._version = None
self._features = None
self._logged = False self._logged = False
self.clientIgnoringOnTheFly = 0 self.clientIgnoringOnTheFly = 0
self.serverIgnoringOnTheFly = 0 self.serverIgnoringOnTheFly = 0
@ -319,6 +327,16 @@ class SyncServerProtocol(JSONCommandProtocol):
def connectionLost(self, reason): def connectionLost(self, reason):
self._factory.removeWatcher(self._watcher) self._factory.removeWatcher(self._watcher)
def getFeatures(self):
if not self._features:
self._features = {}
self._features["sharedPlaylists"] = meetsMinVersion(self._version, SHARED_PLAYLIST_MIN_VERSION)
self._features["chat"] = meetsMinVersion(self._version, CHAT_MIN_VERSION)
self._features["featureList"] = False
self._features["readiness"] = meetsMinVersion(self._version, USER_READY_MIN_VERSION)
self._features["managedRooms"] = meetsMinVersion(self._version, CONTROLLED_ROOMS_MIN_VERSION)
return self._features
def isLogged(self): def isLogged(self):
return self._logged return self._logged
@ -345,7 +363,8 @@ class SyncServerProtocol(JSONCommandProtocol):
roomName = None roomName = None
version = hello["version"] if hello.has_key("version") else None version = hello["version"] if hello.has_key("version") else None
version = hello["realversion"] if hello.has_key("realversion") else version version = hello["realversion"] if hello.has_key("realversion") else version
return username, serverPassword, roomName, version features = hello["features"] if hello.has_key("features") else None
return username, serverPassword, roomName, version, features
def _checkPassword(self, serverPassword): def _checkPassword(self, serverPassword):
if self._factory.password: if self._factory.password:
@ -358,7 +377,7 @@ class SyncServerProtocol(JSONCommandProtocol):
return True return True
def handleHello(self, hello): def handleHello(self, hello):
username, serverPassword, roomName, version = self._extractHelloArguments(hello) username, serverPassword, roomName, version, features = self._extractHelloArguments(hello)
if not username or not roomName or not version: if not username or not roomName or not version:
self.dropWithError(getMessage("hello-server-error")) self.dropWithError(getMessage("hello-server-error"))
return return
@ -366,14 +385,22 @@ class SyncServerProtocol(JSONCommandProtocol):
if not self._checkPassword(serverPassword): if not self._checkPassword(serverPassword):
return return
self._version = version self._version = version
self.setFeatures(features)
self._factory.addWatcher(self, username, roomName) self._factory.addWatcher(self, username, roomName)
self._logged = True self._logged = True
self.sendHello(version) self.sendHello(version)
@requireLogged
def handleChat(self,chatMessage): def handleChat(self,chatMessage):
if not self._factory.disableChat: if not self._factory.disableChat:
self._factory.sendChat(self._watcher,chatMessage) self._factory.sendChat(self._watcher,chatMessage)
def setFeatures(self, features):
self._features = features
def sendFeaturesUpdate(self):
self.sendSet({"features": self.getFeatures()})
def setWatcher(self, watcher): def setWatcher(self, watcher):
self._watcher = watcher self._watcher = watcher
@ -410,6 +437,9 @@ class SyncServerProtocol(JSONCommandProtocol):
self._factory.setPlaylist(self._watcher, set_[1]['files']) self._factory.setPlaylist(self._watcher, set_[1]['files'])
elif command == "playlistIndex": elif command == "playlistIndex":
self._factory.setPlaylistIndex(self._watcher, set_[1]['index']) self._factory.setPlaylistIndex(self._watcher, set_[1]['index'])
elif command == "features":
#TODO: Check
self._watcher.setFeatures(set_[1])
def sendSet(self, setting): def sendSet(self, setting):
self.sendMessage({"Set": setting}) self.sendMessage({"Set": setting})
@ -476,7 +506,8 @@ class SyncServerProtocol(JSONCommandProtocol):
"position": 0, "position": 0,
"file": watcher.getFile() if watcher.getFile() else {}, "file": watcher.getFile() if watcher.getFile() else {},
"controller": watcher.isController(), "controller": watcher.isController(),
"isReady": watcher.isReady() "isReady": watcher.isReady(),
"features": watcher.getFeatures()
} }
userlist[room.getName()][watcher.getName()] = userFile userlist[room.getName()][watcher.getName()] = userFile

View File

@ -48,6 +48,11 @@ class SyncFactory(Factory):
features["readiness"] = not self.disableReady features["readiness"] = not self.disableReady
features["managedRooms"] = True features["managedRooms"] = True
features["chat"] = not self.disableChat features["chat"] = not self.disableChat
features["maxChatMessageLength"] = constants.MAX_CHAT_MESSAGE_LENGTH
features["maxUsernameLength"] = constants.MAX_USERNAME_LENGTH
features["maxRoomNameLength"] = constants.MAX_ROOM_NAME_LENGTH
features["maxFilenameLength"] = constants.MAX_FILENAME_LENGTH
return features return features
def getMotd(self, userIp, username, room, clientVersion): def getMotd(self, userIp, username, room, clientVersion):
@ -108,7 +113,7 @@ class SyncFactory(Factory):
self._roomManager.broadcast(watcher, l) self._roomManager.broadcast(watcher, l)
def sendJoinMessage(self, watcher): def sendJoinMessage(self, watcher):
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"joined": True, "version": watcher.getVersion()}) if w != watcher else None 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.broadcast(watcher, l)
self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False)) self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False))
@ -413,6 +418,10 @@ class Watcher(object):
def setReady(self, ready): def setReady(self, ready):
self._ready = ready self._ready = ready
def getFeatures(self):
features = self._connector.getFeatures()
return features
def isReady(self): def isReady(self):
if self._server.disableReady: if self._server.disableReady:
return None return None

View File

@ -8,6 +8,7 @@ from syncplay.messages import getMessage, setLanguage, isValidLanguage
from syncplay.players.playerFactory import PlayerFactory from syncplay.players.playerFactory import PlayerFactory
from syncplay.utils import isMacOS from syncplay.utils import isMacOS
import codecs import codecs
import re
class InvalidConfigValue(Exception): class InvalidConfigValue(Exception):
def __init__(self, message): def __init__(self, message):
@ -64,6 +65,29 @@ class ConfigurationGetter(object):
"showNonControllerOSD" : False, "showNonControllerOSD" : False,
"showContactInfo" : True, "showContactInfo" : True,
"showDurationNotification" : True, "showDurationNotification" : True,
"chatInputEnabled" : True,
"chatInputFontFamily" : 'sans-serif',
"chatInputRelativeFontSize" : constants.DEFAULT_CHAT_FONT_SIZE,
"chatInputFontWeight" : constants.DEFAULT_CHAT_FONT_WEIGHT,
"chatInputFontUnderline": False,
"chatInputFontColor": constants.DEFAULT_CHAT_INPUT_FONT_COLOR,
"chatInputPosition": constants.INPUT_POSITION_TOP,
"chatDirectInput": False,
"chatOutputEnabled": True,
"chatOutputFontFamily": 'sans-serif',
"chatOutputRelativeFontSize": constants.DEFAULT_CHAT_FONT_SIZE,
"chatOutputFontWeight": constants.DEFAULT_CHAT_FONT_WEIGHT,
"chatOutputFontUnderline": False,
"chatOutputMode": constants.CHATROOM_MODE,
"chatMaxLines": 7,
"chatTopMargin": 25,
"chatLeftMargin": 20,
"chatBottomMargin": 30,
"chatMoveOSD": True,
"chatOSDMargin": 110,
"notificationTimeout": 3,
"alertTimeout": 5,
"chatTimeout": 7,
"publicServers" : [] "publicServers" : []
} }
@ -106,7 +130,13 @@ class ConfigurationGetter(object):
"sharedPlaylistEnabled", "sharedPlaylistEnabled",
"loopAtEndOfPlaylist", "loopAtEndOfPlaylist",
"loopSingleFiles", "loopSingleFiles",
"onlySwitchToTrustedDomains" "onlySwitchToTrustedDomains",
"chatInputEnabled",
"chatInputFontUnderline",
"chatDirectInput",
"chatMoveOSD",
"chatOutputEnabled",
"chatOutputFontUnderline"
] ]
self._tristate = [ self._tristate = [
"checkForUpdatesAutomatically", "checkForUpdatesAutomatically",
@ -125,6 +155,22 @@ class ConfigurationGetter(object):
"rewindThreshold", "rewindThreshold",
"fastforwardThreshold", "fastforwardThreshold",
"autoplayMinUsers", "autoplayMinUsers",
"chatInputRelativeFontSize",
"chatInputFontWeight",
"chatOutputFontWeight",
"chatOutputRelativeFontSize",
"chatMaxLines",
"chatTopMargin",
"chatLeftMargin",
"chatBottomMargin",
"chatOSDMargin",
"notificationTimeout",
"alertTimeout",
"chatTimeout"
]
self._hexadecimal = [
"chatInputFontColor"
] ]
self._iniStructure = { self._iniStructure = {
@ -143,7 +189,19 @@ class ConfigurationGetter(object):
"onlySwitchToTrustedDomains", "trustedDomains","publicServers"], "onlySwitchToTrustedDomains", "trustedDomains","publicServers"],
"gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD",
"showDifferentRoomOSD", "showSameRoomOSD", "showDifferentRoomOSD", "showSameRoomOSD",
"showNonControllerOSD", "showDurationNotification"], "showNonControllerOSD", "showDurationNotification",
"chatInputEnabled","chatInputFontUnderline",
"chatInputFontFamily", "chatInputRelativeFontSize",
"chatInputFontWeight", "chatInputFontColor",
"chatInputPosition","chatDirectInput",
"chatOutputFontFamily", "chatOutputRelativeFontSize",
"chatOutputFontWeight", "chatOutputFontUnderline",
"chatOutputMode", "chatMaxLines",
"chatTopMargin", "chatLeftMargin",
"chatBottomMargin", "chatDirectInput",
"chatMoveOSD", "chatOSDMargin",
"notificationTimeout", "alertTimeout",
"chatTimeout","chatOutputEnabled"],
"general": ["language", "checkForUpdatesAutomatically", "general": ["language", "checkForUpdatesAutomatically",
"lastCheckedForUpdates"] "lastCheckedForUpdates"]
} }
@ -197,6 +255,11 @@ class ConfigurationGetter(object):
for key in self._numeric: for key in self._numeric:
self._config[key] = float(self._config[key]) self._config[key] = float(self._config[key])
for key in self._hexadecimal:
match = re.search(r'^#(?:[0-9a-fA-F]){6}$', self._config[key])
if not match:
self._config[key] = u"#FFFFFF"
for key in self._required: for key in self._required:
if key == "playerPath": if key == "playerPath":
player = None player = None

View File

@ -12,7 +12,7 @@ import sys
import threading import threading
from syncplay.messages import getMessage, getLanguages, setLanguage, getInitialLanguage from syncplay.messages import getMessage, getLanguages, setLanguage, getInitialLanguage
from syncplay import constants from syncplay import constants
from syncplay.utils import isBSD, isLinux, isMacOS from syncplay.utils import isBSD, isLinux, isMacOS, isWindows
from syncplay.utils import resourcespath, posixresourcespath from syncplay.utils import resourcespath, posixresourcespath
class GuiConfiguration: class GuiConfiguration:
def __init__(self, config, error=None, defaultConfig=None): def __init__(self, config, error=None, defaultConfig=None):
@ -289,13 +289,16 @@ class ConfigDialog(QtWidgets.QDialog):
def loadLastUpdateCheckDate(self): def loadLastUpdateCheckDate(self):
settings = QSettings("Syncplay", "Interface") settings = QSettings("Syncplay", "Interface")
settings.beginGroup("Update") settings.beginGroup("Update")
self.lastCheckedForUpdates = settings.value("lastCheckedQt", None) try:
if self.lastCheckedForUpdates: self.lastCheckedForUpdates = settings.value("lastCheckedQt", None)
if self.config["lastCheckedForUpdates"] is not None and self.config["lastCheckedForUpdates"] is not "": if self.lastCheckedForUpdates:
if self.lastCheckedForUpdates.toPython() > datetime.strptime(self.config["lastCheckedForUpdates"], "%Y-%m-%d %H:%M:%S.%f"): if self.config["lastCheckedForUpdates"] is not None and self.config["lastCheckedForUpdates"] is not "":
if self.lastCheckedForUpdates.toPython() > datetime.strptime(self.config["lastCheckedForUpdates"], "%Y-%m-%d %H:%M:%S.%f"):
self.config["lastCheckedForUpdates"] = self.lastCheckedForUpdates.toString("yyyy-MM-d HH:mm:ss.z")
else:
self.config["lastCheckedForUpdates"] = self.lastCheckedForUpdates.toString("yyyy-MM-d HH:mm:ss.z") self.config["lastCheckedForUpdates"] = self.lastCheckedForUpdates.toString("yyyy-MM-d HH:mm:ss.z")
else: except:
self.config["lastCheckedForUpdates"] = self.lastCheckedForUpdates.toString("yyyy-MM-d HH:mm:ss.z") self.lastCheckedForUpdates = None
def loadSavedPublicServerList(self): def loadSavedPublicServerList(self):
settings = QSettings("Syncplay", "Interface") settings = QSettings("Syncplay", "Interface")
@ -515,7 +518,7 @@ class ConfigDialog(QtWidgets.QDialog):
def connectChildren(self, widget): def connectChildren(self, widget):
widgetName = str(widget.objectName()) widgetName = str(widget.objectName())
if self.subitems.has_key(widgetName) and isinstance(widget, QCheckBox): if self.subitems.has_key(widgetName):
widget.stateChanged.connect(lambda: self.updateSubwidgets(self, widget)) widget.stateChanged.connect(lambda: self.updateSubwidgets(self, widget))
self.updateSubwidgets(self, widget) self.updateSubwidgets(self, widget)
@ -869,6 +872,155 @@ class ConfigDialog(QtWidgets.QDialog):
self.syncSettingsLayout.setAlignment(Qt.AlignTop) self.syncSettingsLayout.setAlignment(Qt.AlignTop)
self.stackedLayout.addWidget(self.syncSettingsFrame) self.stackedLayout.addWidget(self.syncSettingsFrame)
def addChatTab(self):
self.chatFrame = QtWidgets.QFrame()
self.chatLayout = QtWidgets.QVBoxLayout()
self.chatLayout.setAlignment(Qt.AlignTop)
# Input
self.chatInputGroup = QtWidgets.QGroupBox(getMessage("chat-title"))
self.chatInputLayout = QtWidgets.QGridLayout()
self.chatLayout.addWidget(self.chatInputGroup)
self.chatInputGroup.setLayout(self.chatInputLayout)
self.chatInputEnabledCheckbox = QCheckBox(getMessage("chatinputenabled-label"))
self.chatInputEnabledCheckbox.setObjectName("chatInputEnabled")
self.chatInputLayout.addWidget(self.chatInputEnabledCheckbox, 1, 0, 1,1, Qt.AlignLeft)
self.chatDirectInputCheckbox = QCheckBox(getMessage("chatdirectinput-label"))
self.chatDirectInputCheckbox.setObjectName("chatDirectInput")
self.chatDirectInputCheckbox.setStyleSheet(
constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png"))
self.chatInputLayout.addWidget(self.chatDirectInputCheckbox, 2, 0, 1,1, Qt.AlignLeft)
self.inputFontLayout = QtWidgets.QHBoxLayout()
self.inputFontLayout.setContentsMargins(0, 0, 0, 0)
self.inputFontFrame = QtWidgets.QFrame()
self.inputFontFrame.setLayout(self.inputFontLayout)
self.inputFontFrame.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
self.chatFontLabel = QLabel(getMessage("chatinputfont-label"), self)
self.chatFontLabel.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png"))
self.chatFontLabel.setObjectName("font-label")
self.chatInputFontButton = QtWidgets.QPushButton(getMessage("chatfont-label"))
self.chatInputFontButton.setObjectName("set-input-font")
self.chatInputFontButtonGroup = QtWidgets.QButtonGroup()
self.chatInputFontButtonGroup.addButton(self.chatInputFontButton)
self.chatInputFontButton.released.connect(lambda: self.fontDialog("chatInput"))
self.chatInputColourButton = QtWidgets.QPushButton(getMessage("chatcolour-label"))
self.chatInputColourButton.setObjectName("set-input-colour")
self.chatInputColourButtonGroup = QtWidgets.QButtonGroup()
self.chatInputColourButtonGroup.addButton(self.chatInputColourButton)
self.chatInputColourButton.released.connect(lambda: self.colourDialog("chatInput"))
self.inputFontLayout.addWidget(self.chatFontLabel, Qt.AlignLeft)
self.inputFontLayout.addWidget(self.chatInputFontButton, Qt.AlignLeft)
self.inputFontLayout.addWidget(self.chatInputColourButton, Qt.AlignLeft)
self.chatInputLayout.addWidget(self.inputFontFrame, 3, 0, 1, 3, Qt.AlignLeft)
self.chatInputPositionFrame = QtWidgets.QFrame()
self.chatInputPositionLayout = QtWidgets.QHBoxLayout()
self.chatInputPositionLayout.setContentsMargins(0, 0, 0, 0)
self.chatInputPositionFrame.setLayout(self.chatInputPositionLayout)
self.chatInputPositionFrame.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
self.chatInputPositionLabel = QLabel(getMessage("chatinputposition-label"), self)
self.chatInputPositionLabel.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png"))
self.chatInputPositionGroup = QButtonGroup()
self.chatInputTopOption = QRadioButton(getMessage("chat-top-option"))
self.chatInputMiddleOption = QRadioButton(getMessage("chat-middle-option"))
self.chatInputBottomOption = QRadioButton(getMessage("chat-bottom-option"))
self.chatInputPositionGroup.addButton(self.chatInputTopOption)
self.chatInputPositionGroup.addButton(self.chatInputMiddleOption)
self.chatInputPositionGroup.addButton(self.chatInputBottomOption)
self.chatInputPositionLabel.setObjectName("chatinputposition")
self.chatInputTopOption.setObjectName("chatinputposition-top" + constants.CONFIG_NAME_MARKER + "chatInputPosition" + constants.CONFIG_VALUE_MARKER + constants.INPUT_POSITION_TOP)
self.chatInputMiddleOption.setObjectName("chatinputposition-middle" + constants.CONFIG_NAME_MARKER + "chatInputPosition" + constants.CONFIG_VALUE_MARKER + constants.INPUT_POSITION_MIDDLE)
self.chatInputBottomOption.setObjectName("chatinputposition-bottom" + constants.CONFIG_NAME_MARKER + "chatInputPosition" + constants.CONFIG_VALUE_MARKER + constants.INPUT_POSITION_BOTTOM)
self.chatInputPositionLayout.addWidget(self.chatInputPositionLabel)
self.chatInputPositionLayout.addWidget(self.chatInputTopOption)
self.chatInputPositionLayout.addWidget(self.chatInputMiddleOption)
self.chatInputPositionLayout.addWidget(self.chatInputBottomOption)
self.chatInputLayout.addWidget(self.chatInputPositionFrame)
self.subitems['chatInputEnabled'] = [self.chatInputPositionLabel.objectName(), self.chatInputTopOption.objectName(),
self.chatInputMiddleOption.objectName(), self.chatInputBottomOption.objectName(),
self.chatInputFontButton.objectName(), self.chatFontLabel.objectName(),
self.chatInputColourButton.objectName(), self.chatDirectInputCheckbox.objectName()]
# Output
self.chatOutputGroup = QtWidgets.QGroupBox(u"Chat message output")
self.chatOutputLayout = QtWidgets.QGridLayout()
self.chatLayout.addWidget(self.chatOutputGroup)
self.chatOutputGroup.setLayout(self.chatOutputLayout)
self.chatOutputEnabledCheckbox = QCheckBox(getMessage("chatoutputenabled-label"))
self.chatOutputEnabledCheckbox.setObjectName("chatOutputEnabled")
self.chatOutputLayout.addWidget(self.chatOutputEnabledCheckbox, 1, 0, 1,1, Qt.AlignLeft)
self.outputFontLayout = QtWidgets.QHBoxLayout()
self.outputFontLayout.setContentsMargins(0, 0, 0, 0)
self.outputFontFrame = QtWidgets.QFrame()
self.outputFontFrame.setLayout(self.outputFontLayout)
self.outputFontFrame.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
self.chatOutputFontLabel = QLabel(getMessage("chatoutputfont-label"), self)
self.chatOutputFontLabel.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png"))
self.chatOutputFontLabel.setObjectName("font-output-label")
self.chatOutputFontButton = QtWidgets.QPushButton(getMessage("chatfont-label"))
self.chatOutputFontButton.setObjectName("set-output-font")
self.chatOutputFontButtonGroup = QtWidgets.QButtonGroup()
self.chatOutputFontButtonGroup.addButton(self.chatOutputFontButton)
self.chatOutputFontButton.released.connect(lambda: self.fontDialog("chatOutput"))
self.chatOutputColourButton = QtWidgets.QPushButton(getMessage("chatcolour-label"))
self.outputFontLayout.addWidget(self.chatOutputFontLabel, Qt.AlignLeft)
self.outputFontLayout.addWidget(self.chatOutputFontButton, Qt.AlignLeft)
self.chatOutputLayout.addWidget(self.outputFontFrame, 2, 0, 1, 3, Qt.AlignLeft)
self.chatOutputModeLabel = QLabel(getMessage("chatoutputposition-label"), self)
self.chatOutputModeLabel.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png"))
self.chatOutputModeGroup = QButtonGroup()
self.chatOutputChatroomOption = QRadioButton(getMessage("chat-chatroom-option"))
self.chatOutputScrollingOption = QRadioButton(getMessage("chat-scrolling-option"))
self.chatOutputModeGroup.addButton(self.chatOutputChatroomOption)
self.chatOutputModeGroup.addButton(self.chatOutputScrollingOption)
self.chatOutputModeLabel.setObjectName("chatoutputmode")
self.chatOutputChatroomOption.setObjectName("chatoutputmode-chatroom" + constants.CONFIG_NAME_MARKER + "chatOutputMode" + constants.CONFIG_VALUE_MARKER + constants.CHATROOM_MODE)
self.chatOutputScrollingOption.setObjectName("chatoutputmode-scrolling" + constants.CONFIG_NAME_MARKER + "chatOutputMode" + constants.CONFIG_VALUE_MARKER + constants.SCROLLING_MODE)
self.chatOutputModeFrame = QtWidgets.QFrame()
self.chatOutputModeLayout = QtWidgets.QHBoxLayout()
self.chatOutputModeLayout.setContentsMargins(0, 0, 0, 0)
self.chatOutputModeFrame.setLayout(self.chatOutputModeLayout)
self.chatOutputModeFrame.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
self.chatOutputModeLayout.addWidget(self.chatOutputModeLabel)
self.chatOutputModeLayout.addWidget(self.chatOutputChatroomOption)
self.chatOutputModeLayout.addWidget(self.chatOutputScrollingOption)
self.chatOutputLayout.addWidget(self.chatOutputModeFrame)
self.subitems['chatOutputEnabled'] = [self.chatOutputModeLabel.objectName(), self.chatOutputChatroomOption.objectName(),
self.chatOutputScrollingOption.objectName(),self.chatOutputFontButton.objectName(),
self.chatOutputFontLabel.objectName()]
# chatFrame
self.chatFrame.setLayout(self.chatLayout)
self.stackedLayout.addWidget(self.chatFrame)
def fontDialog(self, configName):
font = QtGui.QFont()
font.setFamily(self.config[configName+ u"FontFamily"])
font.setPointSize(self.config[configName + u"RelativeFontSize"])
font.setWeight(self.config[configName + u"FontWeight"])
font.setUnderline(self.config[configName + u"FontUnderline"])
value, ok = QtWidgets.QFontDialog.getFont(font)
if ok:
self.config[configName + u"FontFamily"] = value.family()
self.config[configName + u"RelativeFontSize"] = value.pointSize()
self.config[configName + u"FontWeight"] = value.weight()
self.config[configName + u"FontUnderline"] = value.underline()
def colourDialog(self, configName):
oldColour = QtGui.QColor()
oldColour.setNamedColor(self.config[configName+ u"FontColor"])
colour = QtWidgets.QColorDialog.getColor(oldColour, self)
if colour.isValid():
self.config[configName + u"FontColor"] = colour.name()
def addMessageTab(self): def addMessageTab(self):
self.messageFrame = QtWidgets.QFrame() self.messageFrame = QtWidgets.QFrame()
self.messageLayout = QtWidgets.QVBoxLayout() self.messageLayout = QtWidgets.QVBoxLayout()
@ -967,18 +1119,20 @@ class ConfigDialog(QtWidgets.QDialog):
self.helpButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'help.png'), getMessage("help-label")) self.helpButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'help.png'), getMessage("help-label"))
self.helpButton.setObjectName("help") self.helpButton.setObjectName("help")
self.helpButton.setMaximumSize(self.helpButton.sizeHint()) self.helpButton.setMaximumSize(self.helpButton.sizeHint())
self.helpButton.pressed.connect(self.openHelp) self.helpButton.released.connect(self.openHelp)
self.resetButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'cog_delete.png'),getMessage("reset-label")) self.resetButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'cog_delete.png'),getMessage("reset-label"))
self.resetButton.setMaximumSize(self.resetButton.sizeHint()) self.resetButton.setMaximumSize(self.resetButton.sizeHint())
self.resetButton.setObjectName("reset") self.resetButton.setObjectName("reset")
self.resetButton.pressed.connect(self.resetSettings) self.resetButton.released.connect(self.resetSettings)
self.runButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'accept.png'), getMessage("run-label"))
self.runButton.released.connect(self._runWithoutStoringConfig)
self.runButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'accept.png'), getMessage("run-label")) self.runButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'accept.png'), getMessage("run-label"))
self.runButton.pressed.connect(self._runWithoutStoringConfig) self.runButton.pressed.connect(self._runWithoutStoringConfig)
self.runButton.setToolTip(getMessage("nostore-tooltip")) self.runButton.setToolTip(getMessage("nostore-tooltip"))
self.storeAndRunButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'accept.png'), getMessage("storeandrun-label")) self.storeAndRunButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'accept.png'), getMessage("storeandrun-label"))
self.storeAndRunButton.pressed.connect(self._saveDataAndLeave) self.storeAndRunButton.released.connect(self._saveDataAndLeave)
self.bottomButtonLayout.addWidget(self.helpButton) self.bottomButtonLayout.addWidget(self.helpButton)
self.bottomButtonLayout.addWidget(self.resetButton) self.bottomButtonLayout.addWidget(self.resetButton)
self.bottomButtonLayout.addWidget(self.runButton) self.bottomButtonLayout.addWidget(self.runButton)
@ -1008,7 +1162,8 @@ class ConfigDialog(QtWidgets.QDialog):
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"house.png"),getMessage("basics-label"))) self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"house.png"),getMessage("basics-label")))
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"control_pause_blue.png"),getMessage("readiness-label"))) self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"control_pause_blue.png"),getMessage("readiness-label")))
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"film_link.png"),getMessage("sync-label"))) self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"film_link.png"),getMessage("sync-label")))
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"comments.png"),getMessage("messages-label"))) self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"user_comment.png"), getMessage("chat-label")))
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"error.png"),getMessage("messages-label")))
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"cog.png"),getMessage("misc-label"))) self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"cog.png"),getMessage("misc-label")))
self.tabListLayout.addWidget(self.tabListWidget) self.tabListLayout.addWidget(self.tabListWidget)
self.tabListFrame.setLayout(self.tabListLayout) self.tabListFrame.setLayout(self.tabListLayout)
@ -1108,6 +1263,12 @@ class ConfigDialog(QtWidgets.QDialog):
self.QtWidgets = QtWidgets self.QtWidgets = QtWidgets
self.QtGui = QtGui self.QtGui = QtGui
self.error = error self.error = error
if isWindows():
resourcespath = utils.findWorkingDir() + "\\resources\\"
else:
resourcespath = utils.findWorkingDir() + u"/resources/"
self.posixresourcespath = utils.findWorkingDir().replace(u"\\","/") + u"/resources/"
self.resourcespath = resourcespath
super(ConfigDialog, self).__init__() super(ConfigDialog, self).__init__()
@ -1127,6 +1288,7 @@ class ConfigDialog(QtWidgets.QDialog):
self.addBasicTab() self.addBasicTab()
self.addReadinessTab() self.addReadinessTab()
self.addSyncTab() self.addSyncTab()
self.addChatTab()
self.addMessageTab() self.addMessageTab()
self.addMiscTab() self.addMiscTab()
self.tabList() self.tabList()

View File

@ -38,7 +38,7 @@ class ConsoleUI(threading.Thread):
self.PromptResult = data self.PromptResult = data
self.promptMode.set() self.promptMode.set()
elif self._syncplayClient: elif self._syncplayClient:
self._executeCommand(data) self.executeCommand(data)
except EOFError: except EOFError:
pass pass
@ -136,7 +136,7 @@ class ConsoleUI(threading.Thread):
return True return True
return False return False
def _executeCommand(self, data): def executeCommand(self, data):
command = re.match(constants.UI_COMMAND_REGEX, data) command = re.match(constants.UI_COMMAND_REGEX, data)
if not command: if not command:
return return
@ -145,7 +145,7 @@ class ConsoleUI(threading.Thread):
self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek) self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek)
self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos
elif command.group('command') in constants.COMMANDS_LIST: elif command.group('command') in constants.COMMANDS_LIST:
self._syncplayClient.getUserList() self.getUserlist()
elif command.group('command') in constants.COMMANDS_CHAT: elif command.group('command') in constants.COMMANDS_CHAT:
message= command.group('parameter') message= command.group('parameter')
self._syncplayClient.sendChat(message) self._syncplayClient.sendChat(message)
@ -158,8 +158,8 @@ class ConsoleUI(threading.Thread):
room = self._syncplayClient.userlist.currentUser.file["name"] room = self._syncplayClient.userlist.currentUser.file["name"]
else: else:
room = self._syncplayClient.defaultRoom room = self._syncplayClient.defaultRoom
self._syncplayClient.setRoom(room, resetAutoplay=True) self._syncplayClient.setRoom(room, resetAutoplay=True)
self._syncplayClient.ui.updateRoomName(room)
self._syncplayClient.sendRoom() self._syncplayClient.sendRoom()
elif command.group('command') in constants.COMMANDS_CREATE: elif command.group('command') in constants.COMMANDS_CREATE:
roombasename = command.group('parameter') roombasename = command.group('parameter')
@ -191,3 +191,5 @@ class ConsoleUI(threading.Thread):
self.showMessage(getMessage("syncplay-version-notification").format(syncplay.version), True) self.showMessage(getMessage("syncplay-version-notification").format(syncplay.version), True)
self.showMessage(getMessage("more-info-notification").format(syncplay.projectURL), True) self.showMessage(getMessage("more-info-notification").format(syncplay.projectURL), True)
def getUserlist(self):
self._syncplayClient.getUserList()

View File

@ -16,10 +16,27 @@ import os
from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize, isURL from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize, isURL
from functools import wraps from functools import wraps
from twisted.internet import task from twisted.internet import task
from syncplay.ui.consoleUI import ConsoleUI
if isMacOS() and IsPySide: if isMacOS() and IsPySide:
from Foundation import NSURL from Foundation import NSURL
lastCheckedForUpdates = None lastCheckedForUpdates = None
class ConsoleInGUI(ConsoleUI):
def showMessage(self, message, noTimestamp=False):
self._syncplayClient.ui.showMessage(message, True)
def showDebugMessage(self, message):
self._syncplayClient.ui.showDebugMessage(message)
def showErrorMessage(self, message, criticalerror=False):
self._syncplayClient.ui.showErrorMessage(message, criticalerror)
def updateRoomName(self, room=""):
self._syncplayClient.ui.updateRoomName(room)
def getUserlist(self):
self._syncplayClient.showUserList(self)
class UserlistItemDelegate(QtWidgets.QStyledItemDelegate): class UserlistItemDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self): def __init__(self):
QtWidgets.QStyledItemDelegate.__init__(self) QtWidgets.QStyledItemDelegate.__init__(self)
@ -84,8 +101,7 @@ class UserlistItemDelegate(QtWidgets.QStyledItemDelegate):
QtWidgets.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) QtWidgets.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex)
class AboutDialog(QtWidgets.QDialog): class AboutDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
def __init__(self, parent=None):
super(AboutDialog, self).__init__(parent) super(AboutDialog, self).__init__(parent)
if isMacOS(): if isMacOS():
self.setWindowTitle("") self.setWindowTitle("")
@ -120,17 +136,17 @@ class AboutDialog(QtWidgets.QDialog):
self.setSizeGripEnabled(False) self.setSizeGripEnabled(False)
self.setLayout(aboutLayout) self.setLayout(aboutLayout)
def openLicense(self): def openLicense(self):
if isWindows(): if isWindows():
QtGui.QDesktopServices.openUrl(QUrl("file:///" + resourcespath + u"license.rtf")) QtGui.QDesktopServices.openUrl(QUrl("file:///" + resourcespath + u"license.rtf"))
else: else:
QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + u"license.rtf")) QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + u"license.rtf"))
def openDependencies(self): def openDependencies(self):
if isWindows(): if isWindows():
QtGui.QDesktopServices.openUrl(QUrl("file:///" + resourcespath + u"third-party-notices.rtf")) QtGui.QDesktopServices.openUrl(QUrl("file:///" + resourcespath + u"third-party-notices.rtf"))
else: else:
QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + u"third-party-notices.rtf")) QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + u"third-party-notices.rtf"))
class MainWindow(QtWidgets.QMainWindow): class MainWindow(QtWidgets.QMainWindow):
insertPosition = None insertPosition = None
@ -347,6 +363,8 @@ class MainWindow(QtWidgets.QMainWindow):
def addClient(self, client): def addClient(self, client):
self._syncplayClient = client self._syncplayClient = client
if self.console:
self.console.addClient(client)
self.roomInput.setText(self._syncplayClient.getRoom()) self.roomInput.setText(self._syncplayClient.getRoom())
self.config = self._syncplayClient.getConfig() self.config = self._syncplayClient.getConfig()
try: try:
@ -385,6 +403,8 @@ class MainWindow(QtWidgets.QMainWindow):
self.chatInput.setReadOnly(True) self.chatInput.setReadOnly(True)
if not featureList["sharedPlaylists"]: if not featureList["sharedPlaylists"]:
self.playlistGroup.setEnabled(False) self.playlistGroup.setEnabled(False)
self.chatInput.setMaxLength(constants.MAX_CHAT_MESSAGE_LENGTH)
self.roomInput.setMaxLength(constants.MAX_ROOM_NAME_LENGTH)
def showMessage(self, message, noTimestamp=False): def showMessage(self, message, noTimestamp=False):
message = unicode(message) message = unicode(message)
@ -1126,10 +1146,22 @@ class MainWindow(QtWidgets.QMainWindow):
self._syncplayClient.playlist.changePlaylist(newPlaylist) self._syncplayClient.playlist.changePlaylist(newPlaylist)
self._syncplayClient.fileSwitch.updateInfo() self._syncplayClient.fileSwitch.updateInfo()
def executeCommand(self, command):
self.showMessage(u"/{}".format(command))
self.console.executeCommand(command)
def sendChatMessage(self): def sendChatMessage(self):
if self.chatInput.text() <> "": chatText = self.chatInput.text()
self._syncplayClient.sendChat(self.chatInput.text()) self.chatInput.setText("")
self.chatInput.setText("") if chatText <> "":
if chatText[:1] == "/" and chatText <> "/":
command = chatText[1:]
if command and command[:1] == "/":
chatText = chatText[1:]
else:
self.executeCommand(command)
return
self._syncplayClient.sendChat(chatText)
def addTopLayout(self, window): def addTopLayout(self, window):
window.topSplit = self.topSplitter(Qt.Horizontal, self) window.topSplit = self.topSplitter(Qt.Horizontal, self)
@ -1705,6 +1737,8 @@ class MainWindow(QtWidgets.QMainWindow):
def __init__(self): def __init__(self):
super(MainWindow, self).__init__() super(MainWindow, self).__init__()
self.console = ConsoleInGUI()
self.console.setDaemon(True)
self.newWatchlist = [] self.newWatchlist = []
self.publicServerList = [] self.publicServerList = []
self.lastCheckedForUpdates = None self.lastCheckedForUpdates = None

View File

@ -130,6 +130,13 @@ def formatSize (bytes, precise=False):
def isASCII(s): def isASCII(s):
return all(ord(c) < 128 for c in s) return all(ord(c) < 128 for c in s)
def findResourcePath(resourceName):
if resourceName == "syncplay.lua":
resourcePath = os.path.join(findWorkingDir(), "lua", "intf" , "resources", resourceName)
else:
resourcePath = os.path.join(findWorkingDir(),"resources", resourceName)
return resourcePath
def findWorkingDir(): def findWorkingDir():
frozen = getattr(sys, 'frozen', '') frozen = getattr(sys, 'frozen', '')
if not frozen: if not frozen:
@ -152,6 +159,14 @@ def getResourcesPath():
resourcespath = getResourcesPath() resourcespath = getResourcesPath()
posixresourcespath = findWorkingDir().replace(u"\\","/") + u"/resources/" posixresourcespath = findWorkingDir().replace(u"\\","/") + u"/resources/"
def getDefaultMonospaceFont():
if platform.system() == "Windows":
return constants.DEFAULT_WINDOWS_MONOSPACE_FONT
elif platform.system() == "Darwin":
return constants.DEFAULT_OSX_MONOSPACE_FONT
else:
return constants.FALLBACK_MONOSPACE_FONT
def limitedPowerset(s, minLength): def limitedPowerset(s, minLength):
return itertools.chain.from_iterable(itertools.combinations(s, r) for r in xrange(len(s), minLength, -1)) return itertools.chain.from_iterable(itertools.combinations(s, r) for r in xrange(len(s), minLength, -1))
@ -189,20 +204,28 @@ def blackholeStdoutForFrozenWindow():
def truncateText(unicodeText, maxLength): def truncateText(unicodeText, maxLength):
try: try:
unicodeText = unicodedata.normalize('NFC', unicodeText) unicodeText = unicodeText.decode('utf-8')
except: except:
pass pass
try: try:
maxSaneLength= maxLength*5 return(unicode(unicodeText.encode("utf-8"), "utf-8", errors="ignore")[:maxLength])
if len(unicodeText) > maxSaneLength:
unicodeText = unicode(unicodeText.encode("utf-8")[:maxSaneLength], "utf-8", errors="ignore")
while len(unicodeText) > maxLength:
unicodeText = unicode(unicodeText.encode("utf-8")[:-1], "utf-8", errors="ignore")
return unicodeText
except: except:
pass pass
return "" return ""
def splitText(unicodeText, maxLength):
try:
unicodeText = unicodeText.decode('utf-8')
except:
pass
try:
unicodeText = unicode(unicodeText.encode("utf-8"), "utf-8", errors="ignore")
unicodeArray = [unicodeText[i:i + maxLength] for i in range(0, len(unicodeText), maxLength)]
return(unicodeArray)
except:
pass
return [""]
# Relate to file hashing / difference checking: # Relate to file hashing / difference checking:
def stripfilename(filename, stripURL): def stripfilename(filename, stripURL):