Make repl.lua copyright notice more prominent
This commit is contained in:
parent
79c34f823e
commit
f5c6b8fcf9
@ -47,6 +47,7 @@ common:
|
||||
chmod 755 $(LIB_PATH)/syncplay/
|
||||
cp -r resources/hicolor $(SHARE_PATH)/icons/
|
||||
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 resources/hicolor/48x48/apps/syncplay.png $(SHARE_PATH)/app-install/icons/
|
||||
cp resources/hicolor/48x48/apps/syncplay.png $(SHARE_PATH)/pixmaps/
|
||||
|
||||
@ -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/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/user_comment.png',
|
||||
'resources/error.png',
|
||||
'resources/film_folder_edit.png',
|
||||
'resources/film_edit.png',
|
||||
'resources/folder_film.png',
|
||||
@ -679,7 +681,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock
|
||||
'resources/email_go.png',
|
||||
'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)
|
||||
intf_resources = ["resources/lua/intf/syncplay.lua"]
|
||||
|
||||
|
||||
BIN
resources/error.png
Normal file
BIN
resources/error.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 666 B |
979
resources/syncplayintf.lua
Normal file
979
resources/syncplayintf.lua
Normal 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
BIN
resources/user_comment.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 743 B |
@ -1,4 +1,4 @@
|
||||
version = '1.5.1'
|
||||
version = '1.5.2'
|
||||
milestone = 'Yoitsu'
|
||||
release_number = '51'
|
||||
release_number = '55'
|
||||
projectURL = 'http://syncplay.pl/'
|
||||
|
||||
@ -145,8 +145,8 @@ class SyncplayClient(object):
|
||||
|
||||
def initPlayer(self, player):
|
||||
self._player = player
|
||||
if not self._player.secondaryOSDSupported:
|
||||
constants.OSD_WARNING_MESSAGE_DURATION = constants.NO_SECONDARY_OSD_WARNING_DURATION
|
||||
if not self._player.alertOSDSupported:
|
||||
constants.OSD_WARNING_MESSAGE_DURATION = constants.NO_ALERT_OSD_WARNING_DURATION
|
||||
self.scheduleAskPlayer()
|
||||
self.__playerReady.callback(player)
|
||||
|
||||
@ -205,7 +205,7 @@ class SyncplayClient(object):
|
||||
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:
|
||||
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\
|
||||
not (not self.playlist.notJustChangedPlaylist() and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD):
|
||||
pauseChange = self._toggleReady(pauseChange, paused)
|
||||
@ -546,15 +546,32 @@ class SyncplayClient(object):
|
||||
"sharedPlaylists": utils.meetsMinVersion(self.serverVersion, constants.SHARED_PLAYLIST_MIN_VERSION),
|
||||
"chat": utils.meetsMinVersion(self.serverVersion, constants.CHAT_MIN_VERSION),
|
||||
"readiness": utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION),
|
||||
"managedRooms": utils.meetsMinVersion(self.serverVersion, constants.CONTROLLED_ROOMS_MIN_VERSION)
|
||||
"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:
|
||||
self.serverFeatures.update(featureList)
|
||||
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))
|
||||
elif not self.serverFeatures["sharedPlaylists"]:
|
||||
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)
|
||||
if self._player:
|
||||
self._player.setFeatures(self.serverFeatures)
|
||||
|
||||
def getSanitizedCurrentUserFile(self):
|
||||
if self.userlist.currentUser.file:
|
||||
@ -583,16 +600,29 @@ class SyncplayClient(object):
|
||||
def getUsername(self):
|
||||
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):
|
||||
self.userlist.currentUser.room = roomName
|
||||
if resetAutoplay:
|
||||
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):
|
||||
room = self.userlist.currentUser.room
|
||||
if self._protocol and self._protocol.logged and room:
|
||||
@ -632,8 +662,8 @@ class SyncplayClient(object):
|
||||
if self._protocol and self._protocol.logged:
|
||||
self._protocol.sendList()
|
||||
|
||||
def showUserList(self):
|
||||
self.userlist.showUserList()
|
||||
def showUserList(self, altUI=None):
|
||||
self.userlist.showUserList(altUI)
|
||||
|
||||
def getPassword(self):
|
||||
if self.thisIsPublicServer():
|
||||
@ -700,19 +730,30 @@ class SyncplayClient(object):
|
||||
if promptForAction:
|
||||
self.ui.promptFor(getMessage("enter-to-exit-prompt"))
|
||||
|
||||
def requireMinServerVersion(minVersion):
|
||||
def requireMinVersionDecorator(f):
|
||||
def requireServerFeature(featureRequired):
|
||||
def requireServerFeatureDecorator(f):
|
||||
@wraps(f)
|
||||
def wrapper(self, *args, **kwds):
|
||||
if not utils.meetsMinVersion(self.serverVersion,minVersion):
|
||||
if self.serverVersion != "0.0.0":
|
||||
self.ui.showErrorMessage(getMessage("not-supported-by-server-error").format(minVersion, self.serverVersion))
|
||||
else:
|
||||
self.ui.showDebugMessage("Tried to check server version too soon (threshold: {})".format(minVersion))
|
||||
if self.serverVersion == "0.0.0":
|
||||
self.ui.showDebugMessage(
|
||||
"Tried to check server version too soon (testing support for: {})".format(featureRequired))
|
||||
return None
|
||||
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 f(self, *args, **kwds)
|
||||
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):
|
||||
oldState = self.sharedPlaylistIsEnabled()
|
||||
@ -780,7 +821,7 @@ class SyncplayClient(object):
|
||||
allReadyMessage = getMessage("all-users-ready").format(self.userlist.readyUserCount())
|
||||
autoplayingMessage = getMessage("autoplaying-notification").format(int(self.autoplayTimeLeft))
|
||||
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:
|
||||
self.setPaused(False)
|
||||
self.stopAutoplayCountdown()
|
||||
@ -792,11 +833,11 @@ class SyncplayClient(object):
|
||||
self.ui.updateAutoPlayState(False)
|
||||
self.stopAutoplayCountdown()
|
||||
|
||||
@requireMinServerVersion(constants.USER_READY_MIN_VERSION)
|
||||
@requireServerFeature("readiness")
|
||||
def toggleReady(self, manuallyInitiated=True):
|
||||
self._protocol.setReady(not self.userlist.currentUser.isReady(), manuallyInitiated)
|
||||
|
||||
@requireMinServerVersion(constants.USER_READY_MIN_VERSION)
|
||||
@requireServerFeature("readiness")
|
||||
def changeReadyState(self, newState, manuallyInitiated=True):
|
||||
oldState = self.userlist.currentUser.isReady()
|
||||
if newState != oldState:
|
||||
@ -811,7 +852,12 @@ class SyncplayClient(object):
|
||||
if oldReadyState != isReady:
|
||||
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):
|
||||
controlPassword = utils.RandomStringGenerator.generate_room_password()
|
||||
self.lastControlPasswordAttempt = controlPassword
|
||||
@ -830,7 +876,7 @@ class SyncplayClient(object):
|
||||
else:
|
||||
return ""
|
||||
|
||||
@requireMinServerVersion(constants.CONTROLLED_ROOMS_MIN_VERSION)
|
||||
@requireServerFeature("managedRooms")
|
||||
def identifyAsController(self, controlPassword):
|
||||
controlPassword = self.stripControlPassword(controlPassword)
|
||||
self.ui.showMessage(getMessage("identifying-as-controller-notification").format(controlPassword))
|
||||
@ -924,7 +970,7 @@ class SyncplayClient(object):
|
||||
|
||||
def _checkIfYouReAloneInTheRoom(self, OSDOnly):
|
||||
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:
|
||||
self._ui.showMessage(getMessage("alone-in-the-room"), True)
|
||||
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():
|
||||
return
|
||||
osdMessage = None
|
||||
messageMood = constants.MESSAGE_GOODNEWS
|
||||
fileDifferencesForRoom = self._userlist.getFileDifferencesForRoom()
|
||||
if not self._userlist.areAllFilesInRoomSame() and fileDifferencesForRoom is not None:
|
||||
messageMood = constants.MESSAGE_BADNEWS
|
||||
fileDifferencesMessage = getMessage("room-file-differences").format(fileDifferencesForRoom)
|
||||
if self._userlist.currentUser.canControl() and self._userlist.isReadinessSupported():
|
||||
if self._userlist.areAllUsersInRoomReady():
|
||||
@ -970,9 +1018,10 @@ class SyncplayClient(object):
|
||||
if self._userlist.areAllUsersInRoomReady():
|
||||
osdMessage = getMessage("all-users-ready").format(self._userlist.readyUserCount())
|
||||
else:
|
||||
messageMood = constants.MESSAGE_BADNEWS
|
||||
osdMessage = getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady())
|
||||
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):
|
||||
if constants.OSD_WARNING_MESSAGE_DURATION > self._warnings[warningName]["displayedFor"]:
|
||||
@ -1001,6 +1050,7 @@ class SyncplayUser(object):
|
||||
self.room = room
|
||||
self.file = file_
|
||||
self._controller = False
|
||||
self._features = {}
|
||||
|
||||
def setFile(self, filename, duration, size, path=None):
|
||||
file_ = {
|
||||
@ -1054,6 +1104,9 @@ class SyncplayUser(object):
|
||||
def setReady(self, ready):
|
||||
self.ready = ready
|
||||
|
||||
def setFeatures(self, features):
|
||||
self._features = features
|
||||
|
||||
class SyncplayUserlist(object):
|
||||
def __init__(self, ui, client):
|
||||
self.currentUser = SyncplayUser()
|
||||
@ -1132,7 +1185,7 @@ class SyncplayUserlist(object):
|
||||
if differentDuration: differences.append(getMessage("file-difference-duration"))
|
||||
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 isController is not None:
|
||||
self.currentUser.setControllerStatus(isController)
|
||||
@ -1143,7 +1196,7 @@ class SyncplayUserlist(object):
|
||||
user.setControllerStatus(isController)
|
||||
self._users[username] = user
|
||||
user.setReady(isReady)
|
||||
|
||||
user.setFeatures(features)
|
||||
if not noMessage:
|
||||
self.__showUserChangeMessage(username, room, file_)
|
||||
self.userListChange(room)
|
||||
@ -1310,7 +1363,7 @@ class SyncplayUserlist(object):
|
||||
def hasRoomStateChanged(self):
|
||||
return self._roomUsersChanged
|
||||
|
||||
def showUserList(self):
|
||||
def showUserList(self, altUI=None):
|
||||
rooms = {}
|
||||
for user in self._users.itervalues():
|
||||
if user.room not in rooms:
|
||||
@ -1320,7 +1373,10 @@ class SyncplayUserlist(object):
|
||||
rooms[self.currentUser.room] = []
|
||||
rooms[self.currentUser.room].append(self.currentUser)
|
||||
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()
|
||||
|
||||
def clearList(self):
|
||||
@ -1336,10 +1392,10 @@ class UiManager(object):
|
||||
def __init__(self, client, ui):
|
||||
self._client = client
|
||||
self.__ui = ui
|
||||
self.lastPrimaryOSDMessage = None
|
||||
self.lastPrimaryOSDEndTime = None
|
||||
self.lastSecondaryOSDMessage = None
|
||||
self.lastSecondaryOSDEndTime = None
|
||||
self.lastNotificatinOSDMessage = None
|
||||
self.lastNotificationOSDEndTime = None
|
||||
self.lastAlertOSDMessage = None
|
||||
self.lastAlertOSDEndTime = None
|
||||
self.lastError = ""
|
||||
|
||||
def setPlaylist(self, newPlaylist, newIndexFilename=None):
|
||||
@ -1358,8 +1414,16 @@ class UiManager(object):
|
||||
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()))
|
||||
|
||||
def showMessage(self, message, noPlayer=False, noTimestamp=False, secondaryOSD=False):
|
||||
if not noPlayer: self.showOSDMessage(message, duration=constants.OSD_DURATION, secondaryOSD=secondaryOSD)
|
||||
def showChatMessage(self, username, userMessage):
|
||||
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)
|
||||
|
||||
def updateAutoPlayState(self, newState):
|
||||
@ -1368,28 +1432,28 @@ class UiManager(object):
|
||||
def showUserList(self, 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()
|
||||
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
|
||||
if not self._client._player:
|
||||
return
|
||||
if constants.SHOW_OSD and self._client and self._client._player:
|
||||
if not self._client._player.secondaryOSDSupported:
|
||||
if secondaryOSD:
|
||||
self.lastSecondaryOSDMessage = message
|
||||
if not self._client._player.alertOSDSupported:
|
||||
if OSDType == constants.OSD_ALERT:
|
||||
self.lastAlertOSDMessage = message
|
||||
if autoplayConditionsMet:
|
||||
self.lastSecondaryOSDEndTime = time.time() + 1.0
|
||||
self.lastAlertOSDEndTime = time.time() + 1.0
|
||||
else:
|
||||
self.lastSecondaryOSDEndTime = time.time() + constants.NO_SECONDARY_OSD_WARNING_DURATION
|
||||
if self.lastPrimaryOSDEndTime and time.time() < self.lastPrimaryOSDEndTime:
|
||||
message = u"{}{}{}".format(message, self._client._player.osdMessageSeparator, self.lastPrimaryOSDMessage)
|
||||
self.lastAlertOSDEndTime = time.time() + constants.NO_ALERT_OSD_WARNING_DURATION
|
||||
if self.lastNotificationOSDEndTime and time.time() < self.lastNotificationOSDEndTime:
|
||||
message = u"{}{}{}".format(message, self._client._player.osdMessageSeparator, self.lastNotificatinOSDMessage)
|
||||
else:
|
||||
self.lastPrimaryOSDMessage = message
|
||||
self.lastPrimaryOSDEndTime = time.time() + constants.OSD_DURATION
|
||||
if self.lastSecondaryOSDEndTime and time.time() < self.lastSecondaryOSDEndTime:
|
||||
message = u"{}{}{}".format(self.lastSecondaryOSDMessage, self._client._player.osdMessageSeparator, message)
|
||||
self._client._player.displayMessage(message, int(duration * 1000), secondaryOSD)
|
||||
self.lastNotificatinOSDMessage = message
|
||||
self.lastNotificationOSDEndTime = time.time() + constants.OSD_DURATION
|
||||
if self.lastAlertOSDEndTime and time.time() < self.lastAlertOSDEndTime:
|
||||
message = u"{}{}{}".format(self.lastAlertOSDMessage, self._client._player.osdMessageSeparator, message)
|
||||
self._client._player.displayMessage(message, int(duration * 1000), OSDType, mood)
|
||||
|
||||
def setControllerStatus(self, username, isController):
|
||||
self.__ui.setControllerStatus(username, isController)
|
||||
@ -1411,6 +1475,9 @@ class UiManager(object):
|
||||
def updateRoomName(self, room=""):
|
||||
self.__ui.updateRoomName(room)
|
||||
|
||||
def executeCommand(self, command):
|
||||
self.__ui.executeCommand(command)
|
||||
|
||||
def drop(self):
|
||||
self.__ui.drop()
|
||||
|
||||
@ -1799,7 +1866,7 @@ class FileSwitchManager(object):
|
||||
if os.path.isfile(filepath):
|
||||
return filepath
|
||||
|
||||
if highPriority and self.folderSearchEnabled:
|
||||
if highPriority and self.folderSearchEnabled and self.mediaDirectories is not None:
|
||||
directoryList = self.mediaDirectories
|
||||
# Spin up hard drives to prevent premature timeout
|
||||
randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+u".txt"
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
# coding:utf8
|
||||
# You might want to change these
|
||||
DEFAULT_PORT = 8999
|
||||
OSD_DURATION = 3.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
|
||||
MPLAYER_OSD_LEVEL = 1
|
||||
UI_TIME_FORMAT = "[%X] "
|
||||
@ -56,12 +57,23 @@ PLAYLIST_MAX_CHARACTERS = 10000
|
||||
PLAYLIST_MAX_ITEMS = 250
|
||||
MAXIMUM_TAB_WIDTH = 350
|
||||
TAB_PADDING = 30
|
||||
DEFAULT_WINDOWS_MONOSPACE_FONT = "Consolas"
|
||||
DEFAULT_OSX_MONOSPACE_FONT = "Menlo"
|
||||
FALLBACK_MONOSPACE_FONT = "Monospace"
|
||||
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_CHAT_MESSAGE_LENGTH = 50 # Number of displayed characters
|
||||
MAX_USERNAME_LENGTH = 16 # Number of displayed characters
|
||||
# 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 = 125 # Number of displayed characters
|
||||
MAX_USERNAME_LENGTH = 20 # Number of displayed characters
|
||||
MAX_ROOM_NAME_LENGTH = 35 # 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:
|
||||
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"
|
||||
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
|
||||
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_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_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',
|
||||
'--play-and-pause', '--start-time=0']
|
||||
VLC_SLAVE_MACOS_ARGS = ['--verbose=2', '--no-file-logging']
|
||||
@ -200,6 +225,9 @@ UNPAUSE_IFALREADYREADY_MODE = "IfAlreadyReady"
|
||||
UNPAUSE_IFOTHERSREADY_MODE = "IfOthersReady"
|
||||
UNPAUSE_IFMINUSERSREADY_MODE = "IfMinUsersReady"
|
||||
UNPAUSE_ALWAYS_MODE = "Always"
|
||||
INPUT_POSITION_TOP = "Top"
|
||||
INPUT_POSITION_MIDDLE = "Middle"
|
||||
INPUT_POSITION_BOTTOM = "Bottom"
|
||||
|
||||
VLC_EOF_DURATION_THRESHOLD = 2.0
|
||||
|
||||
@ -217,6 +245,17 @@ FILEITEM_SWITCH_FILE_SWITCH = 1
|
||||
FILEITEM_SWITCH_STREAM_SWITCH = 2
|
||||
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_DOWNLOAD_URL = "http://syncplay.pl/download/"
|
||||
SYNCPLAY_PUBLIC_SERVER_LIST_URL = u"http://syncplay.pl/listpublicservers?{}" # Params
|
||||
|
||||
@ -66,4 +66,6 @@ def getMessage(type_, locale=None):
|
||||
if messages["en"].has_key(type_):
|
||||
return unicode(messages["en"][type_])
|
||||
else:
|
||||
raise KeyError(type_)
|
||||
print u"WARNING: Cannot find message '{}'!".format(type_)
|
||||
return "!{}".format(type_) # TODO: Remove
|
||||
#raise KeyError(type_)
|
||||
@ -17,23 +17,23 @@ de = {
|
||||
"connected-successful-notification" : u"Erfolgreich mit Server verbunden",
|
||||
"retrying-notification" : u"%s, versuche erneut in %d Sekunden...", # Seconds
|
||||
|
||||
"rewind-notification" : u"Zurückgespult wegen Zeitdifferenz mit <{}>", # User
|
||||
"fastforward-notification" : u"Vorgespult wegen Zeitdifferenz mit <{}>", # User
|
||||
"slowdown-notification" : u"Verlangsamt wegen Zeitdifferenz mit <{}>", # User
|
||||
"rewind-notification" : u"Zurückgespult wegen Zeitdifferenz mit {}", # User
|
||||
"fastforward-notification" : u"Vorgespult wegen Zeitdifferenz mit {}", # User
|
||||
"slowdown-notification" : u"Verlangsamt wegen Zeitdifferenz mit {}", # User
|
||||
"revert-notification" : u"Normalgeschwindigkeit",
|
||||
|
||||
"pause-notification" : u"<{}> pausierte", # User
|
||||
"unpause-notification" : u"<{}> startete", # User
|
||||
"seek-notification" : u"<{}> sprang von {} nach {}", # User, from time, to time
|
||||
"pause-notification" : u"{} pausierte", # User
|
||||
"unpause-notification" : u"{} startete", # User
|
||||
"seek-notification" : u"{} sprang von {} nach {}", # User, from time, to time
|
||||
|
||||
"current-offset-notification" : u"Aktueller Offset: {} Sekunden", # Offset
|
||||
|
||||
"media-directory-list-updated-notification" : u"Syncplay media directories have been updated.", # TODO: Translate
|
||||
|
||||
"room-join-notification" : u"<{}> hat den Raum '{}' betreten", # User
|
||||
"left-notification" : u"<{}> ist gegangen", # User
|
||||
"left-paused-notification" : u"<{}> ist gegangen, <{}> pausierte", # User who left, User who paused
|
||||
"playing-notification" : u"<{}> spielt '{}' ({})", # User, file, duration
|
||||
"room-join-notification" : u"{} hat den Raum '{}' betreten", # User
|
||||
"left-notification" : u"{} ist gegangen", # User
|
||||
"left-paused-notification" : u"{} ist gegangen, {} pausierte", # User who left, User who paused
|
||||
"playing-notification" : u"{} spielt '{}' ({})", # User, file, duration
|
||||
"playing-notification/room-addendum" : u" in Raum: '{}'", # Room
|
||||
|
||||
"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
|
||||
|
||||
"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.",
|
||||
"authenticated-as-controller-notification" : u"<{}> authentifizierte sich als Raumleiter",
|
||||
"failed-to-identify-as-controller-notification" : u"{} konnte sich nicht als Raumleiter identifizieren.",
|
||||
"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
|
||||
|
||||
"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: {}",
|
||||
"room-file-differences" : u"Unterschiedlich in: {}", # File differences (filename, size, and/or duration)
|
||||
"file-difference-filename" : u"Name",
|
||||
@ -225,6 +225,7 @@ de = {
|
||||
"messages-label" : u"Nachrichten",
|
||||
"messages-osd-title" : u"OSD-(OnScreenDisplay)-Einstellungen",
|
||||
"messages-other-title" : u"Weitere Display-Einstellungen",
|
||||
"chat-label" : u"Chat", # TODO: Translate
|
||||
"privacy-label" : u"Privatsphäre",
|
||||
"privacy-title" : u"Privatsphäreneinstellungen",
|
||||
"unpause-title" : u"Wenn du Play drückst, auf Bereit setzen und:",
|
||||
@ -234,6 +235,27 @@ de = {
|
||||
"unpause-always" : u"Immer wiedergeben",
|
||||
"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",
|
||||
"reset-label" : u"Standardwerte zurücksetzen",
|
||||
"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.",
|
||||
"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]",
|
||||
"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.",
|
||||
@ -420,6 +458,7 @@ de = {
|
||||
"addurlstoplaylist-menu-label" : u"Add URL(s) to bottom of 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
|
||||
"addusersstreamstoplaylist-menu-label" : u"Add {} stream to playlist", # item owner indicator
|
||||
"openusersstream-menu-label" : u"Open {} stream", # [username]'s
|
||||
|
||||
@ -17,23 +17,23 @@ en = {
|
||||
"connected-successful-notification" : "Successfully connected to server",
|
||||
"retrying-notification" : "%s, Retrying in %d seconds...", # Seconds
|
||||
|
||||
"rewind-notification" : "Rewinded due to time difference with <{}>", # User
|
||||
"fastforward-notification" : "Fast-forwarded due to time difference with <{}>", # User
|
||||
"slowdown-notification" : "Slowing down due to time difference with <{}>", # User
|
||||
"rewind-notification" : "Rewinded due to time difference with {}", # User
|
||||
"fastforward-notification" : "Fast-forwarded due to time difference with {}", # User
|
||||
"slowdown-notification" : "Slowing down due to time difference with {}", # User
|
||||
"revert-notification" : "Reverting speed back to normal",
|
||||
|
||||
"pause-notification" : u"<{}> paused", # User
|
||||
"unpause-notification" : u"<{}> unpaused", # User
|
||||
"seek-notification" : u"<{}> jumped from {} to {}", # User, from time, to time
|
||||
"pause-notification" : u"{} paused", # User
|
||||
"unpause-notification" : u"{} unpaused", # User
|
||||
"seek-notification" : u"{} jumped from {} to {}", # User, from time, to time
|
||||
|
||||
"current-offset-notification" : "Current offset: {} seconds", # Offset
|
||||
|
||||
"media-directory-list-updated-notification" : u"Syncplay media directories have been updated.",
|
||||
|
||||
"room-join-notification" : u"<{}> has joined the room: '{}'", # User
|
||||
"left-notification" : u"<{}> has left", # User
|
||||
"left-paused-notification" : u"<{}> left, <{}> paused", # User who left, User who paused
|
||||
"playing-notification" : u"<{}> is playing '{}' ({})", # User, file, duration
|
||||
"room-join-notification" : u"{} has joined the room: '{}'", # User
|
||||
"left-notification" : u"{} has left", # User
|
||||
"left-paused-notification" : u"{} left, {} paused", # User who left, User who paused
|
||||
"playing-notification" : u"{} is playing '{}' ({})", # User, file, duration
|
||||
"playing-notification/room-addendum" : u" in room: '{}'", # Room
|
||||
|
||||
"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
|
||||
|
||||
"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.",
|
||||
"authenticated-as-controller-notification" : u"<{}> authenticated 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",
|
||||
"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
|
||||
"room-file-differences" : u"File differences: {}", # File differences (filename, size, and/or duration)
|
||||
"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-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-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-osd-title" : "On-screen 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-title" : "Privacy settings",
|
||||
"unpause-title" : u"If you press play, set as ready and:",
|
||||
@ -234,6 +240,27 @@ en = {
|
||||
"unpause-always" : u"Always unpause",
|
||||
"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",
|
||||
"reset-label" : "Restore defaults",
|
||||
"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.",
|
||||
"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.",
|
||||
"reset-tooltip" : "Reset all settings to the default configuration.",
|
||||
"update-server-list-tooltip" : u"Connect to syncplay.pl to update list of public servers.",
|
||||
|
||||
@ -17,23 +17,23 @@ ru = {
|
||||
"connected-successful-notification" : u"Соединение с сервером установлено",
|
||||
"retrying-notification" : u"%s, следующая попытка через %d секунд(ы)...", # Seconds
|
||||
|
||||
"rewind-notification" : u"Перемотано из-за разницы во времени с <{}>", # User
|
||||
"fastforward-notification" : u"Ускорено из-за разницы во времени с <{}>", # User
|
||||
"slowdown-notification" : u"Воспроизведение замедлено из-за разницы во времени с <{}>", # User
|
||||
"rewind-notification" : u"Перемотано из-за разницы во времени с {}", # User
|
||||
"fastforward-notification" : u"Ускорено из-за разницы во времени с {}", # User
|
||||
"slowdown-notification" : u"Воспроизведение замедлено из-за разницы во времени с {}", # User
|
||||
"revert-notification" : u"Возвращаемся к нормальной скорости воспроизведения",
|
||||
|
||||
"pause-notification" : u"<{}> приостановил воспроизведение", # User
|
||||
"unpause-notification" : u"<{}> возобновил воспроизведение", # User
|
||||
"seek-notification" : u"<{}> перемотал с {} на {}", # User, from time, to time
|
||||
"pause-notification" : u"{} приостановил воспроизведение", # User
|
||||
"unpause-notification" : u"{} возобновил воспроизведение", # User
|
||||
"seek-notification" : u"{} перемотал с {} на {}", # User, from time, to time
|
||||
|
||||
"current-offset-notification" : u"Текущее смещение: {} секунд(ы)", # Offset
|
||||
|
||||
"media-directory-list-updated-notification" : u"Папки воспроизведения обновлены.",
|
||||
|
||||
"room-join-notification" : u"<{}> зашел в комнату: '{}'", # User
|
||||
"left-notification" : u"<{}> покинул комнату", # User
|
||||
"left-paused-notification" : u"<{}> покинул комнату, <{}> приостановил воспроизведение", # User who left, User who paused
|
||||
"playing-notification" : u"<{}> включил '{}' ({})", # User, file, duration
|
||||
"room-join-notification" : u"{} зашел в комнату: '{}'", # User
|
||||
"left-notification" : u"{} покинул комнату", # User
|
||||
"left-paused-notification" : u"{} покинул комнату, {} приостановил воспроизведение", # User who left, User who paused
|
||||
"playing-notification" : u"{} включил '{}' ({})", # User, file, duration
|
||||
"playing-notification/room-addendum" : u" в комнате: '{}'", # Room
|
||||
|
||||
"not-all-ready" : u"Не готовы: {}", # Usernames
|
||||
@ -44,11 +44,11 @@ ru = {
|
||||
"autoplaying-notification" : u"Автовоспроизведение через {}...", # Number of seconds until playback will start
|
||||
|
||||
"identifying-as-controller-notification" : u"Идентификация как оператора комнаты с паролем '{}'...",
|
||||
"failed-to-identify-as-controller-notification" : u"<{}> не прошел идентификацию в качестве оператора комнаты.",
|
||||
"authenticated-as-controller-notification" : u"<{}> вошел как оператор комнаты.",
|
||||
"failed-to-identify-as-controller-notification" : u"{} не прошел идентификацию в качестве оператора комнаты.",
|
||||
"authenticated-as-controller-notification" : u"{} вошел как оператор комнаты.",
|
||||
"created-controlled-room-notification" : u"Создана управляемая комната '{}' с паролем '{}'. Сохраните эти данные!", # RoomName, operatorPassword
|
||||
|
||||
"file-different-notification" : u"Вероятно, файл, который Вы смотрите, отличается от того, который смотрит <{}>.", # User
|
||||
"file-different-notification" : u"Вероятно, файл, который Вы смотрите, отличается от того, который смотрит {}.", # User
|
||||
"file-differences-notification" : u"Ваш файл отличается: {}", # Differences
|
||||
"room-file-differences" : u"Несовпадения файла: {}", # File differences (filename, size, and/or duration)
|
||||
"file-difference-filename" : u"имя",
|
||||
@ -135,7 +135,13 @@ ru = {
|
||||
"vlc-failed-versioncheck" : u"Данная версия VLC не поддерживается Syncplay. Пожалуйста, используйте VLC версии 2 или выше.",
|
||||
"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-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-osd-title" : u"Настройки OSD",
|
||||
"messages-other-title" : u"Другие настройки отображения",
|
||||
"chat-label" : u"Chat", # TODO: Translate
|
||||
"privacy-label" : u"Приватность",
|
||||
"privacy-title" : u"Настройки приватности",
|
||||
"unpause-title" : u"Если вы стартуете, то:",
|
||||
@ -237,6 +244,27 @@ ru = {
|
||||
"syncplay-trusteddomains-title": u"Доверенные сайты (стрим-сервисы, видеохостинги, файлы в сети)",
|
||||
"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"Помощь",
|
||||
"reset-label" : u"Сброс настроек",
|
||||
"run-label" : u"Запустить",
|
||||
@ -357,6 +385,22 @@ ru = {
|
||||
"unpause-ifminusersready-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.",
|
||||
"reset-tooltip" : u"Сбрасывает все настройки Syncplay в начальное состояние.",
|
||||
"update-server-list-tooltip" : u"Обновить список публичных серверов от syncplay.pl.",
|
||||
@ -419,6 +463,7 @@ ru = {
|
||||
"addurlstoplaylist-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
|
||||
"addusersstreamstoplaylist-menu-label" : u"Добавить поток {} в список воспроизведения", # item owner indicator
|
||||
"openusersstream-menu-label" : u"Открыть поток от {}", # [username]'s
|
||||
|
||||
@ -12,7 +12,7 @@ class BasePlayer(object):
|
||||
'''
|
||||
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()
|
||||
|
||||
'''
|
||||
@ -34,6 +34,12 @@ class BasePlayer(object):
|
||||
def setPaused(self, value):
|
||||
raise NotImplementedError()
|
||||
|
||||
'''
|
||||
@type value: list
|
||||
'''
|
||||
def setFeatures(self, featureList):
|
||||
raise NotImplementedError()
|
||||
|
||||
'''
|
||||
@type value: float
|
||||
'''
|
||||
|
||||
@ -306,8 +306,9 @@ class MpcHcApi:
|
||||
|
||||
class MPCHCAPIPlayer(BasePlayer):
|
||||
speedSupported = False
|
||||
secondaryOSDSupported = False
|
||||
alertOSDSupported = False
|
||||
customOpenDialog = False
|
||||
chatOSDSupported = False
|
||||
osdMessageSeparator = "; "
|
||||
|
||||
def __init__(self, client):
|
||||
@ -399,7 +400,7 @@ class MPCHCAPIPlayer(BasePlayer):
|
||||
def openFile(self, filePath, resetPosition=False):
|
||||
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)
|
||||
|
||||
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
||||
@ -411,6 +412,8 @@ class MPCHCAPIPlayer(BasePlayer):
|
||||
self._mpcApi.pause()
|
||||
else:
|
||||
self._mpcApi.unpause()
|
||||
def setFeatures(self, featureList):
|
||||
pass
|
||||
|
||||
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
||||
def setPosition(self, value):
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
# coding:utf8
|
||||
import subprocess
|
||||
import re
|
||||
import threading
|
||||
@ -11,7 +12,8 @@ from syncplay.utils import isWindows
|
||||
class MplayerPlayer(BasePlayer):
|
||||
speedSupported = True
|
||||
customOpenDialog = False
|
||||
secondaryOSDSupported = False
|
||||
alertOSDSupported = False
|
||||
chatOSDSupported = False
|
||||
osdMessageSeparator = "; "
|
||||
|
||||
RE_ANSWER = re.compile(constants.MPLAYER_ANSWER_REGEX)
|
||||
@ -88,8 +90,15 @@ class MplayerPlayer(BasePlayer):
|
||||
def _getProperty(self, property_):
|
||||
self._listener.sendLine("get_property {}".format(property_))
|
||||
|
||||
def displayMessage(self, message, duration=(constants.OSD_DURATION * 1000), secondaryOSD=False):
|
||||
self._listener.sendLine(u'{} "{!s}" {} {}'.format(self.OSD_QUERY, self._stripNewlines(message), duration, constants.MPLAYER_OSD_LEVEL).encode('utf-8'))
|
||||
def displayMessage(self, message, duration=(constants.OSD_DURATION * 1000), OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL):
|
||||
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):
|
||||
self._setProperty('speed', "{:.2f}".format(value))
|
||||
@ -105,6 +114,9 @@ class MplayerPlayer(BasePlayer):
|
||||
self.setPaused(self._client.getGlobalPaused())
|
||||
self.setPosition(self._client.getGlobalPosition())
|
||||
|
||||
def setFeatures(self, featureList):
|
||||
pass
|
||||
|
||||
def setPosition(self, value):
|
||||
self._position = max(value,0)
|
||||
self._setProperty(self.POSITION_QUERY, "{}".format(value))
|
||||
@ -130,9 +142,16 @@ class MplayerPlayer(BasePlayer):
|
||||
def _getPosition(self):
|
||||
self._getProperty(self.POSITION_QUERY)
|
||||
|
||||
def _stripNewlines(self, text):
|
||||
def _sanitizeText(self, text):
|
||||
text = text.replace("\r", "")
|
||||
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
|
||||
|
||||
def _quoteArg(self, arg):
|
||||
@ -158,6 +177,8 @@ class MplayerPlayer(BasePlayer):
|
||||
def lineReceived(self, line):
|
||||
if 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(" 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=":
|
||||
@ -277,6 +298,9 @@ class MplayerPlayer(BasePlayer):
|
||||
self.lastSendTime = None
|
||||
self.lastNotReadyTime = None
|
||||
self.__playerController = playerController
|
||||
if not self.__playerController._client._config["chatOutputEnabled"]:
|
||||
self.__playerController.alertOSDSupported = False
|
||||
self.__playerController.chatOSDSupported = False
|
||||
if self.__playerController.getPlayerPathErrors(playerPath,filePath):
|
||||
raise ValueError()
|
||||
if filePath and '://' not in filePath:
|
||||
@ -332,6 +356,18 @@ class MplayerPlayer(BasePlayer):
|
||||
self.__playerController.lineReceived(line)
|
||||
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):
|
||||
self.checkForReadinessOverride()
|
||||
return self.readyToSend
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
# coding:utf8
|
||||
import re
|
||||
import subprocess
|
||||
from syncplay.players.mplayer import MplayerPlayer
|
||||
from syncplay.messages import getMessage
|
||||
from syncplay import constants
|
||||
from syncplay.utils import isURL
|
||||
from syncplay.utils import isURL, findResourcePath
|
||||
import os, sys, time
|
||||
|
||||
class MpvPlayer(MplayerPlayer):
|
||||
RE_VERSION = re.compile('.*mpv (\d+)\.(\d+)\.\d+.*')
|
||||
osdMessageSeparator = "\\n"
|
||||
osdMessageSeparator = "; " # TODO: Make conditional
|
||||
|
||||
@staticmethod
|
||||
def run(client, playerPath, filePath, args):
|
||||
@ -17,6 +19,9 @@ class MpvPlayer(MplayerPlayer):
|
||||
except:
|
||||
ver = None
|
||||
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:
|
||||
return NewMpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args)
|
||||
else:
|
||||
@ -30,6 +35,7 @@ class MpvPlayer(MplayerPlayer):
|
||||
args.extend(constants.MPV_SLAVE_ARGS)
|
||||
if constants.MPV_NEW_VERSION:
|
||||
args.extend(constants.MPV_SLAVE_ARGS_NEW)
|
||||
args.extend([u"--script={}".format(findResourcePath("syncplayintf.lua"))])
|
||||
return args
|
||||
|
||||
@staticmethod
|
||||
@ -108,6 +114,25 @@ class OldMpvPlayer(MpvPlayer):
|
||||
class NewMpvPlayer(OldMpvPlayer):
|
||||
lastResetTime = 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):
|
||||
if self._paused == value:
|
||||
@ -199,6 +224,9 @@ class NewMpvPlayer(OldMpvPlayer):
|
||||
self._clearFileLoaded()
|
||||
self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath)), notReadyAfterThis=True)
|
||||
|
||||
def setFeatures(self, featureList):
|
||||
self.sendMpvOptions()
|
||||
|
||||
def setPosition(self, value):
|
||||
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))
|
||||
@ -222,9 +250,29 @@ class NewMpvPlayer(OldMpvPlayer):
|
||||
else:
|
||||
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):
|
||||
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:
|
||||
self._listener.setReadyToSend(False)
|
||||
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:
|
||||
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):
|
||||
if not self.lastResetTime:
|
||||
return False
|
||||
|
||||
@ -16,7 +16,8 @@ from syncplay.utils import isBSD, isLinux, isWindows, isMacOS
|
||||
class VlcPlayer(BasePlayer):
|
||||
speedSupported = True
|
||||
customOpenDialog = False
|
||||
secondaryOSDSupported = True
|
||||
chatOSDSupported = False
|
||||
alertOSDSupported = True
|
||||
osdMessageSeparator = "; "
|
||||
|
||||
RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX)
|
||||
@ -118,9 +119,9 @@ class VlcPlayer(BasePlayer):
|
||||
else:
|
||||
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
|
||||
if secondaryOSD == False:
|
||||
if OSDType != constants.OSD_ALERT:
|
||||
self._listener.sendLine('display-osd: {}, {}, {}'.format('top-right', duration, message.encode('utf8')))
|
||||
else:
|
||||
self._listener.sendLine('display-secondary-osd: {}, {}, {}'.format('center', duration, message.encode('utf8')))
|
||||
@ -128,6 +129,9 @@ class VlcPlayer(BasePlayer):
|
||||
def setSpeed(self, value):
|
||||
self._listener.sendLine("set-rate: {:.2n}".format(value))
|
||||
|
||||
def setFeatures(self, featureList):
|
||||
pass
|
||||
|
||||
def setPosition(self, value):
|
||||
self._lastVLCPositionUpdate = time.time()
|
||||
self._listener.sendLine("set-position: {}".format(value).replace(".",self.radixChar))
|
||||
|
||||
@ -5,8 +5,8 @@ import syncplay
|
||||
from functools import wraps
|
||||
import time
|
||||
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):
|
||||
def handleMessages(self, messages):
|
||||
@ -107,6 +107,7 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
if room: hello["room"] = {"name" :room}
|
||||
hello["version"] = "1.2.255" # Used so newer clients work on 1.2.X server
|
||||
hello["realversion"] = syncplay.version
|
||||
hello["features"] = self._client.getFeatures()
|
||||
self.sendMessage({"Hello": hello})
|
||||
|
||||
def _SetUser(self, users):
|
||||
@ -147,6 +148,11 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
self._client.playlist.changeToPlaylistIndex(values['index'], values['user'])
|
||||
elif command == "playlistChange":
|
||||
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):
|
||||
self.sendMessage({"Set": setting})
|
||||
@ -173,7 +179,8 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
file_ = user[1]['file'] if user[1]['file'] <> {} else None
|
||||
isController = user[1]['controller'] if 'controller' in user[1] else False
|
||||
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()
|
||||
|
||||
def sendList(self):
|
||||
@ -249,9 +256,9 @@ class SyncClientProtocol(JSONCommandProtocol):
|
||||
}
|
||||
})
|
||||
def handleChat(self,message):
|
||||
username = message['username']
|
||||
userMessage = message['message']
|
||||
messageString = u"<{}> {}".format(message['username'], userMessage)
|
||||
self._client.ui.showMessage(messageString)
|
||||
self._client.ui.showChatMessage(username, userMessage)
|
||||
|
||||
def setReady(self, isReady, manuallyInitiated=True):
|
||||
self.sendSet({
|
||||
@ -286,6 +293,7 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
def __init__(self, factory):
|
||||
self._factory = factory
|
||||
self._version = None
|
||||
self._features = None
|
||||
self._logged = False
|
||||
self.clientIgnoringOnTheFly = 0
|
||||
self.serverIgnoringOnTheFly = 0
|
||||
@ -319,6 +327,16 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
def connectionLost(self, reason):
|
||||
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):
|
||||
return self._logged
|
||||
|
||||
@ -345,7 +363,8 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
roomName = None
|
||||
version = hello["version"] if hello.has_key("version") else None
|
||||
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):
|
||||
if self._factory.password:
|
||||
@ -358,7 +377,7 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
return True
|
||||
|
||||
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:
|
||||
self.dropWithError(getMessage("hello-server-error"))
|
||||
return
|
||||
@ -366,14 +385,22 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
if not self._checkPassword(serverPassword):
|
||||
return
|
||||
self._version = version
|
||||
self.setFeatures(features)
|
||||
self._factory.addWatcher(self, username, roomName)
|
||||
self._logged = True
|
||||
self.sendHello(version)
|
||||
|
||||
@requireLogged
|
||||
def handleChat(self,chatMessage):
|
||||
if not self._factory.disableChat:
|
||||
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):
|
||||
self._watcher = watcher
|
||||
|
||||
@ -410,6 +437,9 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
self._factory.setPlaylist(self._watcher, set_[1]['files'])
|
||||
elif command == "playlistIndex":
|
||||
self._factory.setPlaylistIndex(self._watcher, set_[1]['index'])
|
||||
elif command == "features":
|
||||
#TODO: Check
|
||||
self._watcher.setFeatures(set_[1])
|
||||
|
||||
def sendSet(self, setting):
|
||||
self.sendMessage({"Set": setting})
|
||||
@ -476,7 +506,8 @@ class SyncServerProtocol(JSONCommandProtocol):
|
||||
"position": 0,
|
||||
"file": watcher.getFile() if watcher.getFile() else {},
|
||||
"controller": watcher.isController(),
|
||||
"isReady": watcher.isReady()
|
||||
"isReady": watcher.isReady(),
|
||||
"features": watcher.getFeatures()
|
||||
}
|
||||
userlist[room.getName()][watcher.getName()] = userFile
|
||||
|
||||
|
||||
@ -48,6 +48,11 @@ class SyncFactory(Factory):
|
||||
features["readiness"] = not self.disableReady
|
||||
features["managedRooms"] = True
|
||||
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
|
||||
|
||||
def getMotd(self, userIp, username, room, clientVersion):
|
||||
@ -108,7 +113,7 @@ class SyncFactory(Factory):
|
||||
self._roomManager.broadcast(watcher, l)
|
||||
|
||||
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.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False))
|
||||
|
||||
@ -413,6 +418,10 @@ class Watcher(object):
|
||||
def setReady(self, ready):
|
||||
self._ready = ready
|
||||
|
||||
def getFeatures(self):
|
||||
features = self._connector.getFeatures()
|
||||
return features
|
||||
|
||||
def isReady(self):
|
||||
if self._server.disableReady:
|
||||
return None
|
||||
|
||||
@ -8,6 +8,7 @@ from syncplay.messages import getMessage, setLanguage, isValidLanguage
|
||||
from syncplay.players.playerFactory import PlayerFactory
|
||||
from syncplay.utils import isMacOS
|
||||
import codecs
|
||||
import re
|
||||
|
||||
class InvalidConfigValue(Exception):
|
||||
def __init__(self, message):
|
||||
@ -64,6 +65,29 @@ class ConfigurationGetter(object):
|
||||
"showNonControllerOSD" : False,
|
||||
"showContactInfo" : 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" : []
|
||||
}
|
||||
|
||||
@ -106,7 +130,13 @@ class ConfigurationGetter(object):
|
||||
"sharedPlaylistEnabled",
|
||||
"loopAtEndOfPlaylist",
|
||||
"loopSingleFiles",
|
||||
"onlySwitchToTrustedDomains"
|
||||
"onlySwitchToTrustedDomains",
|
||||
"chatInputEnabled",
|
||||
"chatInputFontUnderline",
|
||||
"chatDirectInput",
|
||||
"chatMoveOSD",
|
||||
"chatOutputEnabled",
|
||||
"chatOutputFontUnderline"
|
||||
]
|
||||
self._tristate = [
|
||||
"checkForUpdatesAutomatically",
|
||||
@ -125,6 +155,22 @@ class ConfigurationGetter(object):
|
||||
"rewindThreshold",
|
||||
"fastforwardThreshold",
|
||||
"autoplayMinUsers",
|
||||
"chatInputRelativeFontSize",
|
||||
"chatInputFontWeight",
|
||||
"chatOutputFontWeight",
|
||||
"chatOutputRelativeFontSize",
|
||||
"chatMaxLines",
|
||||
"chatTopMargin",
|
||||
"chatLeftMargin",
|
||||
"chatBottomMargin",
|
||||
"chatOSDMargin",
|
||||
"notificationTimeout",
|
||||
"alertTimeout",
|
||||
"chatTimeout"
|
||||
]
|
||||
|
||||
self._hexadecimal = [
|
||||
"chatInputFontColor"
|
||||
]
|
||||
|
||||
self._iniStructure = {
|
||||
@ -143,7 +189,19 @@ class ConfigurationGetter(object):
|
||||
"onlySwitchToTrustedDomains", "trustedDomains","publicServers"],
|
||||
"gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD",
|
||||
"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",
|
||||
"lastCheckedForUpdates"]
|
||||
}
|
||||
@ -197,6 +255,11 @@ class ConfigurationGetter(object):
|
||||
for key in self._numeric:
|
||||
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:
|
||||
if key == "playerPath":
|
||||
player = None
|
||||
|
||||
@ -12,7 +12,7 @@ import sys
|
||||
import threading
|
||||
from syncplay.messages import getMessage, getLanguages, setLanguage, getInitialLanguage
|
||||
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
|
||||
class GuiConfiguration:
|
||||
def __init__(self, config, error=None, defaultConfig=None):
|
||||
@ -518,7 +518,7 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
|
||||
def connectChildren(self, widget):
|
||||
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))
|
||||
self.updateSubwidgets(self, widget)
|
||||
|
||||
@ -872,6 +872,155 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
self.syncSettingsLayout.setAlignment(Qt.AlignTop)
|
||||
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):
|
||||
self.messageFrame = QtWidgets.QFrame()
|
||||
self.messageLayout = QtWidgets.QVBoxLayout()
|
||||
@ -970,18 +1119,20 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
self.helpButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'help.png'), getMessage("help-label"))
|
||||
self.helpButton.setObjectName("help")
|
||||
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.setMaximumSize(self.resetButton.sizeHint())
|
||||
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.pressed.connect(self._runWithoutStoringConfig)
|
||||
self.runButton.setToolTip(getMessage("nostore-tooltip"))
|
||||
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.resetButton)
|
||||
self.bottomButtonLayout.addWidget(self.runButton)
|
||||
@ -1011,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"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"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.tabListLayout.addWidget(self.tabListWidget)
|
||||
self.tabListFrame.setLayout(self.tabListLayout)
|
||||
@ -1111,6 +1263,12 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
self.QtWidgets = QtWidgets
|
||||
self.QtGui = QtGui
|
||||
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__()
|
||||
|
||||
@ -1130,6 +1288,7 @@ class ConfigDialog(QtWidgets.QDialog):
|
||||
self.addBasicTab()
|
||||
self.addReadinessTab()
|
||||
self.addSyncTab()
|
||||
self.addChatTab()
|
||||
self.addMessageTab()
|
||||
self.addMiscTab()
|
||||
self.tabList()
|
||||
|
||||
@ -38,7 +38,7 @@ class ConsoleUI(threading.Thread):
|
||||
self.PromptResult = data
|
||||
self.promptMode.set()
|
||||
elif self._syncplayClient:
|
||||
self._executeCommand(data)
|
||||
self.executeCommand(data)
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
@ -136,7 +136,7 @@ class ConsoleUI(threading.Thread):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _executeCommand(self, data):
|
||||
def executeCommand(self, data):
|
||||
command = re.match(constants.UI_COMMAND_REGEX, data)
|
||||
if not command:
|
||||
return
|
||||
@ -145,7 +145,7 @@ class ConsoleUI(threading.Thread):
|
||||
self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek)
|
||||
self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos
|
||||
elif command.group('command') in constants.COMMANDS_LIST:
|
||||
self._syncplayClient.getUserList()
|
||||
self.getUserlist()
|
||||
elif command.group('command') in constants.COMMANDS_CHAT:
|
||||
message= command.group('parameter')
|
||||
self._syncplayClient.sendChat(message)
|
||||
@ -158,8 +158,8 @@ class ConsoleUI(threading.Thread):
|
||||
room = self._syncplayClient.userlist.currentUser.file["name"]
|
||||
else:
|
||||
room = self._syncplayClient.defaultRoom
|
||||
|
||||
self._syncplayClient.setRoom(room, resetAutoplay=True)
|
||||
self._syncplayClient.ui.updateRoomName(room)
|
||||
self._syncplayClient.sendRoom()
|
||||
elif command.group('command') in constants.COMMANDS_CREATE:
|
||||
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("more-info-notification").format(syncplay.projectURL), True)
|
||||
|
||||
def getUserlist(self):
|
||||
self._syncplayClient.getUserList()
|
||||
@ -16,10 +16,27 @@ import os
|
||||
from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize, isURL
|
||||
from functools import wraps
|
||||
from twisted.internet import task
|
||||
from syncplay.ui.consoleUI import ConsoleUI
|
||||
if isMacOS() and IsPySide:
|
||||
from Foundation import NSURL
|
||||
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):
|
||||
def __init__(self):
|
||||
QtWidgets.QStyledItemDelegate.__init__(self)
|
||||
@ -84,8 +101,7 @@ class UserlistItemDelegate(QtWidgets.QStyledItemDelegate):
|
||||
QtWidgets.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex)
|
||||
|
||||
class AboutDialog(QtWidgets.QDialog):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, parent=None):
|
||||
super(AboutDialog, self).__init__(parent)
|
||||
if isMacOS():
|
||||
self.setWindowTitle("")
|
||||
@ -120,17 +136,17 @@ class AboutDialog(QtWidgets.QDialog):
|
||||
self.setSizeGripEnabled(False)
|
||||
self.setLayout(aboutLayout)
|
||||
|
||||
def openLicense(self):
|
||||
if isWindows():
|
||||
QtGui.QDesktopServices.openUrl(QUrl("file:///" + resourcespath + u"license.rtf"))
|
||||
else:
|
||||
QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + u"license.rtf"))
|
||||
def openLicense(self):
|
||||
if isWindows():
|
||||
QtGui.QDesktopServices.openUrl(QUrl("file:///" + resourcespath + u"license.rtf"))
|
||||
else:
|
||||
QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + u"license.rtf"))
|
||||
|
||||
def openDependencies(self):
|
||||
def openDependencies(self):
|
||||
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:
|
||||
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):
|
||||
insertPosition = None
|
||||
@ -347,6 +363,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
def addClient(self, client):
|
||||
self._syncplayClient = client
|
||||
if self.console:
|
||||
self.console.addClient(client)
|
||||
self.roomInput.setText(self._syncplayClient.getRoom())
|
||||
self.config = self._syncplayClient.getConfig()
|
||||
try:
|
||||
@ -385,6 +403,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.chatInput.setReadOnly(True)
|
||||
if not featureList["sharedPlaylists"]:
|
||||
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):
|
||||
message = unicode(message)
|
||||
@ -1126,10 +1146,22 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self._syncplayClient.playlist.changePlaylist(newPlaylist)
|
||||
self._syncplayClient.fileSwitch.updateInfo()
|
||||
|
||||
def executeCommand(self, command):
|
||||
self.showMessage(u"/{}".format(command))
|
||||
self.console.executeCommand(command)
|
||||
|
||||
def sendChatMessage(self):
|
||||
if self.chatInput.text() <> "":
|
||||
self._syncplayClient.sendChat(self.chatInput.text())
|
||||
self.chatInput.setText("")
|
||||
chatText = self.chatInput.text()
|
||||
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):
|
||||
window.topSplit = self.topSplitter(Qt.Horizontal, self)
|
||||
@ -1705,6 +1737,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
def __init__(self):
|
||||
super(MainWindow, self).__init__()
|
||||
self.console = ConsoleInGUI()
|
||||
self.console.setDaemon(True)
|
||||
self.newWatchlist = []
|
||||
self.publicServerList = []
|
||||
self.lastCheckedForUpdates = None
|
||||
|
||||
@ -130,6 +130,13 @@ def formatSize (bytes, precise=False):
|
||||
def isASCII(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():
|
||||
frozen = getattr(sys, 'frozen', '')
|
||||
if not frozen:
|
||||
@ -152,6 +159,14 @@ def getResourcesPath():
|
||||
resourcespath = getResourcesPath()
|
||||
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):
|
||||
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):
|
||||
try:
|
||||
unicodeText = unicodedata.normalize('NFC', unicodeText)
|
||||
unicodeText = unicodeText.decode('utf-8')
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
maxSaneLength= maxLength*5
|
||||
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
|
||||
return(unicode(unicodeText.encode("utf-8"), "utf-8", errors="ignore")[:maxLength])
|
||||
except:
|
||||
pass
|
||||
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:
|
||||
|
||||
def stripfilename(filename, stripURL):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user