diff --git a/addon.xml b/addon.xml index 330ff55..102b63a 100644 --- a/addon.xml +++ b/addon.xml @@ -1,11 +1,11 @@ - + @@ -16,6 +16,6 @@ http://forum.kodi.tv/showthread.php?tid=211971 https://github.com/enen92/service.sleeptimer all - GNU GENERAL PUBLIC LICENSE. Version 2, June 1991 + GPL-2.0-only diff --git a/resources/language/English/strings.po b/resources/language/English/strings.po index e9b55bb..7ea95c7 100644 --- a/resources/language/English/strings.po +++ b/resources/language/English/strings.po @@ -16,116 +16,124 @@ msgstr "" msgctxt "#30000" msgid "Sleep Timer" -"" +msgstr "" msgctxt "#30001" msgid "Cancel to continue playing" -"" +msgstr "" msgctxt "#30002" msgid "Service check time (minutes)" -"" +msgstr "" msgctxt "#30003" msgid "Waiting time for the dialog (before stop) (seconds)" -"" +msgstr "" msgctxt "#30004" msgid "Slow mute audio before stop playing" -"" +msgstr "" msgctxt "#30005" msgid "General" -"" +msgstr "" msgctxt "#30006" -msgid "Time between 1% volume change (millisec)" -"" +msgid "Time it takes to reach 0% volume (minutes)" +msgstr "" msgctxt "#30007" msgid "Next check time if dialog is cancelled (minutes)" -"" +msgstr "" msgctxt "#30008" msgid "Enable Screensaver after stopping" -"" +msgstr "" msgctxt "#30009" msgid "Audio/Video Supervision settings" -"" +msgstr "" msgctxt "#30010" msgid "Enable Video Supervision" -"" +msgstr "" msgctxt "#30011" msgid "Enable Audio Supervision" -"" +msgstr "" msgctxt "#30012" msgid "Maximum Playing time (minutes) before asking to stop" -"" +msgstr "" msgctxt "#30013" msgid "Video Playbacktime" -"" +msgstr "" msgctxt "#30014" msgid "Audio Playbacktime" -"" +msgstr "" msgctxt "#30015" msgid "Debug" -"" +msgstr "" msgctxt "#30016" -msgid "Debug mode" -"" +msgid "Debug mode (overwrites some settings!)" +msgstr "" msgctxt "#30017" msgid "'Enable' debugging sets:" -"" +msgstr "" msgctxt "#30018" msgid "Audio and Video-Check enabled" -"" +msgstr "" msgctxt "#30019" msgid "Service check time (minutes): '1'" -"" +msgstr "" msgctxt "#30020" msgid "Waiting time for the dialog (before stop) (seconds): '30'" -"" +msgstr "" msgctxt "#30021" msgid "It doesn't matter, what you set in the other Tabs!" -"" +msgstr "" msgctxt "#30022" msgid "Execute custom cmd after playback stop" -"" +msgstr "" msgctxt "#30023" msgid "Custom CMD" -"" +msgstr "" msgctxt "#30024" msgid "Supervision Mode" -"" +msgstr "" msgctxt "#30025" msgid "Hour start (eg. 23:00)" -"" +msgstr "" msgctxt "#30026" msgid "Hour supervision end (eg. 03:20)" -"" +msgstr "" msgctxt "#30027" msgid "Always" -"" +msgstr "" msgctxt "#30028" msgid "Specific Time" -"" \ No newline at end of file +msgstr "" + +msgctxt "#30029" +msgid "Set the minimum volume %" +msgstr "" + +msgctxt "#30030" +msgid "Alternative mode (user interaction is detected differently)" +msgstr "" diff --git a/resources/language/Portuguese/strings.po b/resources/language/Portuguese/strings.po index 5828ff1..83957c5 100644 --- a/resources/language/Portuguese/strings.po +++ b/resources/language/Portuguese/strings.po @@ -38,8 +38,8 @@ msgid "General" msgstr "Geral" msgctxt "#30006" -msgid "Time between 1% volume change (millisec)" -msgstr "Tempo entre a alteração de 1% volume (millisec)" +msgid "Time it takes to reach 0% volume (minutes)" +msgstr "Tempo que leva para atingir o volume de 0% (minutos)" msgctxt "#30007" msgid "Next check time if dialog is cancelled (minutes)" @@ -128,3 +128,7 @@ msgstr "Sempre" msgctxt "#30028" msgid "Specific Time" msgstr "Tempo especifico" + +msgctxt "#30029" +msgid "Set the minimum volume %" +msgstr "Defina o volume mínimo %" \ No newline at end of file diff --git a/resources/settings.xml b/resources/settings.xml index a9443d3..e1200e6 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -1,10 +1,11 @@ - + - + + @@ -14,14 +15,16 @@ - - + + - + + + diff --git a/service.py b/service.py index 6dee062..db58bad 100644 --- a/service.py +++ b/service.py @@ -18,6 +18,7 @@ import time import datetime +import math import xbmc import xbmcplugin import xbmcgui @@ -30,8 +31,8 @@ addon_id = 'service.sleeptimer' selfAddon = xbmcaddon.Addon(addon_id) -datapath = xbmc.translatePath(selfAddon.getAddonInfo('profile')).decode('utf-8') -addonfolder = xbmc.translatePath(selfAddon.getAddonInfo('path')).decode('utf-8') +datapath = xbmcvfs.translatePath(selfAddon.getAddonInfo('profile')) +addonfolder = xbmcvfs.translatePath(selfAddon.getAddonInfo('path')) debug=selfAddon.getSetting('debug_mode') __version__ = selfAddon.getAddonInfo('version') @@ -39,7 +40,8 @@ check_time_next = int(selfAddon.getSetting('check_time_next')) time_to_wait = int(selfAddon.getSetting('waiting_time_dialog')) audiochange = selfAddon.getSetting('audio_change') -audiochangerate = int(selfAddon.getSetting('audio_change_rate')) +muteVol = int(selfAddon.getSetting('mute_volume')) +audiointervallength = int(selfAddon.getSetting('audio_interval_length')) global audio_enable audio_enable = str(selfAddon.getSetting('audio_enable')) video_enable = str(selfAddon.getSetting('video_enable')) @@ -48,19 +50,24 @@ enable_screensaver = selfAddon.getSetting('enable_screensaver') custom_cmd = selfAddon.getSetting('custom_cmd') cmd = selfAddon.getSetting('cmd') +useAlternativeMode = selfAddon.getSetting('alternativemode') # Functions: def translate(text): return selfAddon.getLocalizedString(text).encode('utf-8') def _log( message ): - print addon_id + ": " + str(message) + xbmc.log(addon_id + ": " + str(message), level=xbmc.LOGDEBUG) + +def _debug( message ): + if debug == 'true': + _log ( "DEBUG: " + str(message) ) # print the actual playing file in DEBUG-mode def print_act_playing_file(): if debug == 'true': actPlayingFile = xbmc.Player().getPlayingFile() - _log ( "DEBUG: File: " + str(actPlayingFile) ) + _log (str(actPlayingFile)) # wait for abort - xbmc.sleep or time.sleep doesn't work # and prevents Kodi from exiting @@ -104,12 +111,92 @@ def should_i_supervise(kodi_time,supervise_start_time,supervise_end_time): else: return False + +class AlternativeDetectionMode( xbmc.Player ): + + def __init__( self, *args ): + _log ( "Init Alternative mode" ) + self.resetTime() + self.lastEnded = None + + def getSecondsFromNow(self, x): + return (datetime.datetime.now()-x).total_seconds() + + def resetTime(self): + self.lastUserInteractionTime = datetime.datetime.now() + + def onPlayBackSeekChapter(self, chapter): + _debug( "onPlayBackSeekChapter" ) + self.resetTime() + + def onPlayBackSeek(self, time, seekOffset): + _debug( "onPlayBackSeek" ) + self.resetTime() + + def onPlayBackResumed( self ): + # Will be called when xbmc starts playing a file + _debug( "onPlayBackResumed" ) + self.resetTime() + + def onPlayBackPaused( self ): + # Will be called when xbmc stops playing a file + _debug( "onPlayBackPaused" ) + self.resetTime() + + def onPlayBackStopped( self ): + # Will be called when user stops xbmc playing a file + _debug( "onPlayBackStopped" ) + self.resetTime() + + def onPlayBackStarted( self ): + # Will be called when user stops xbmc playing a file + if self.lastEnded is None: + _debug("onPlayBackStarted: No ended movie detected before => user interaction") + self.resetTime() + return + + delayFromLastEnded = self.getSecondsFromNow(self.lastEnded) + _debug( "onPlayBackStarted: Last Ended was detected within " + str(delayFromLastEnded) + "seconds" ) + + if delayFromLastEnded is not None and delayFromLastEnded < 60: + _debug("onPlayBackStarted: Last movie endend => No user interaction") + #do nothing + else: + _debug("onPlayBackStarted: Last movie did not end during 60s => User interaction") + self.resetTime() + + + def onPlayBackEnded( self ): + _debug( "onPlayBackEnded" ) + _debug("Storing last Ended time") + self.lastEnded = datetime.datetime.now() + #self.resetTime() + + def getGlobalIdleTime(self): + result = int(self.getSecondsFromNow(self.lastUserInteractionTime)) + + _debug ( "XBMC Idle Time " + repr(xbmc.getGlobalIdleTime())) + _debug ( "Alternative Idle Time " + repr(result) ) + return result + + + +def getIdleTimeInSeconds(alternativeMode): + if useAlternativeMode: + idle_time = alternativeMode.getGlobalIdleTime() + else: + idle_time = xbmc.getGlobalIdleTime() + return idle_time + class service: def __init__(self): FirstCycle = True next_check = False + monitor = xbmc.Monitor() + alternativeMode = AlternativeDetectionMode() + diff_between_idle_and_check_time = None - while True: + while not monitor.abortRequested(): kodi_time = get_kodi_time() try: supervise_start_time = int(selfAddon.getSetting('hour_start_sup').split(':')[0]+selfAddon.getSetting('hour_start_sup').split(':')[1]) @@ -140,26 +227,26 @@ def __init__(self): _log ( "DEBUG: ################################################################" ) # Set this low values for easier debugging! _log ( "DEBUG: debug is enabled! Override Settings:" ) - enable_audio = 'true' + #enable_audio = 'true' _log ( "DEBUG: -> enable_audio: " + str(enable_audio) ) - maxaudio_time_in_minutes = 1 + #maxaudio_time_in_minutes = 1 _log ( "DEBUG: -> maxaudio_time_in_minutes: " + str(maxaudio_time_in_minutes) ) - enable_video = 'true' + #enable_video = 'true' _log ( "DEBUG: -> enable_video: " + str(enable_audio) ) - maxvideo_time_in_minutes = 1 + #maxvideo_time_in_minutes = 1 _log ( "DEBUG: -> maxvideo_time_in_minutes: " + str(maxvideo_time_in_minutes) ) - iCheckTime = 1 + #iCheckTime = 1 _log ( "DEBUG: -> check_time: " + str(iCheckTime) ) _log ( "DEBUG: ----------------------------------------------------------------" ) # wait 15s before start to let Kodi finish the intro-movie - if xbmc.Monitor().waitForAbort(15): + if monitor.waitForAbort(15): break max_time_in_minutes = -1 FirstCycle = False - - idle_time = xbmc.getGlobalIdleTime() + + idle_time = getIdleTimeInSeconds(alternativeMode) idle_time_in_minutes = int(idle_time)/60 if xbmc.Player().isPlaying(): @@ -182,8 +269,7 @@ def __init__(self): what_is_playing = "audio" max_time_in_minutes = maxaudio_time_in_minutes else: - if debug == 'true': - _log ( "DEBUG: Player is playing Audio, but check is disabled" ) + _debug ( "DEBUG: Player is playing Audio, but check is disabled" ) do_next_check(iCheckTime) continue @@ -195,8 +281,7 @@ def __init__(self): what_is_playing = "video" max_time_in_minutes = maxvideo_time_in_minutes else: - if debug == 'true': - _log ( "DEBUG: Player is playing Video, but check is disabled" ) + _debug ( "DEBUG: Player is playing Video, but check is disabled" ) do_next_check(iCheckTime) continue @@ -211,20 +296,16 @@ def __init__(self): do_next_check(iCheckTime) continue - if debug == 'true': - _log ( "DEBUG: what_is_playing: " + str(what_is_playing) ) - - if debug == 'true': - _log ( "DEBUG: idle_time: '" + str(idle_time) + "s'; idle_time_in_minutes: '" + str(idle_time_in_minutes) + "'" ) - _log ( "DEBUG: max_time_in_minutes: " + str(max_time_in_minutes) ) + _debug ( "DEBUG: what_is_playing: " + str(what_is_playing) ) + _debug ( "DEBUG: idle_time: '" + str(idle_time) + "s'; idle_time_in_minutes: '" + str(idle_time_in_minutes) + "'" ) + _debug ( "DEBUG: max_time_in_minutes: " + str(max_time_in_minutes) ) # only display the Progressdialog, if audio or video is enabled AND idle limit is reached # Check if what_is_playing is not "other" and idle time exceeds limit if ( what_is_playing != "other" and idle_time_in_minutes >= max_time_in_minutes ): - if debug == 'true': - _log ( "DEBUG: idle_time exceeds max allowed. Display Progressdialog" ) + _debug ( "DEBUG: idle_time exceeds max allowed. Display Progressdialog" ) ret = msgdialogprogress.create(translate(30000),translate(30001)) secs=0 @@ -238,12 +319,12 @@ def __init__(self): percent = increment*secs/100 secs_left = str((time_to_wait - secs)) remaining_display = str(secs_left) + " seconds left." - msgdialogprogress.update(percent,translate(30001),remaining_display) + msgdialogprogress.update(int(percent),translate(30001)) xbmc.sleep(1000) if (msgdialogprogress.iscanceled()): cancelled = True - if debug == 'true': - _log ( "DEBUG: Progressdialog cancelled" ) + alternativeMode.resetTime() + _debug ( "DEBUG: Progressdialog cancelled" ) break if cancelled == True: iCheckTime = check_time_next @@ -260,43 +341,66 @@ def __init__(self): if audiochange == 'true': resp = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": { "properties": [ "volume"] }, "id": 1}') dct = json.loads(resp) - muteVol = 10 - if (dct.has_key("result")) and (dct["result"].has_key("volume")): + if ("result" in dct) and ("volume" in dct["result"]): curVol = dct["result"]["volume"] + + _debug ( "DEBUG: Original volume value is " + str(curVol) ) for i in range(curVol - 1, muteVol - 1, -1): + _debug ( "DEBUG: Reducing volume to " + str(i)) xbmc.executebuiltin('SetVolume(%d,showVolumeBar)' % (i)) - # move down slowly - xbmc.sleep(audiochangerate) + # move down slowly ((total mins / steps) * ms in a min) + # (curVol-muteVol) runs the full timer where a user might control their volume via kodi instead of cutting it short when assuming a set volume of 100% + xbmc.sleep(round(audiointervallength / (curVol - muteVol) * 60000)) + + #check if user pressed something while audio volume is going down and abort sleep process + idle_time_in_minutes = int(getIdleTimeInSeconds(alternativeMode))/60 + if idle_time_in_minutes < max_time_in_minutes: + _debug ( "DEBUG: User pressed a key while volume is going down. Aborting sleep process" ) + #set volume back + _debug ( "DEBUG: Setting back original volume" + str(dct["result"]["volume"])) + xbmc.executebuiltin('SetVolume(%d,showVolumeBar)' % (dct["result"]["volume"])) + iCheckTime = check_time_next + _log ( "Progressdialog cancelled, next check in " + str(iCheckTime) + " min" ) + # set next_check, so that it opens the dialog after "iCheckTime" + next_check = True + cancelled = True + break + if cancelled: + continue # stop player anyway - xbmc.sleep(5000) # wait 5s before stopping - xbmc.executebuiltin('PlayerControl(Stop)') + _debug ( "DEBUG: Waiting before stop" + str(curVol) ) + monitor.waitForAbort(5) # wait 5s before stopping if audiochange == 'true': - xbmc.sleep(2000) # wait 2s before changing the volume back - if (dct.has_key("result")) and (dct["result"].has_key("volume")): + #monitor.waitForAbort(2) # wait 2s before changing the volume back + if ("result" in dct) and ("volume" in dct["result"]): curVol = dct["result"]["volume"] + _debug ( "DEBUG: Reset volume to original value" + str(curVol) ) # we can move upwards fast, because there is nothing playing xbmc.executebuiltin('SetVolume(%d,showVolumeBar)' % (curVol)) + else: + _debug ( "DEBUG: DID NOT Reset volume 1 to original value" + str(curVol) ) + else: + _debug ( "DEBUG: DID NOT Reset volume 2 to original value" + str(curVol) ) + + _debug ( "DEBUG: Stopping... " + str(curVol) ) + xbmc.executebuiltin('PlayerControl(Stop)') if enable_screensaver == 'true': - if debug == 'true': - _log ( "DEBUG: Activating screensaver" ) - xbmc.executebuiltin('ActivateScreensaver') - - #Run a custom cmd after playback is stopped + _debug ( "DEBUG: Activating screensaver" ) + xbmc.executebuiltin('ActivateScreensaver') + + # Run a custom cmd after playback is stopped if custom_cmd == 'true': - if debug == 'true': - _log ( "DEBUG: Running custom script" ) + _debug ( "DEBUG: Running custom script" ) os.system(cmd) else: - if debug == 'true': - _log ( "DEBUG: Playing the stream, time does not exceed max limit" ) + _debug ( "DEBUG: Playing the stream, time does not exceed max limit" ) else: - if debug == 'true': - _log ( "DEBUG: Not playing any media file" ) + _debug ( "DEBUG: Not playing any media file" ) # reset max_time_in_minutes max_time_in_minutes = -1 @@ -306,5 +410,9 @@ def __init__(self): _log ( "DEBUG: diff_between_idle_and_check_time: " + str(diff_between_idle_and_check_time) ) do_next_check(iCheckTime) + monitor.waitForAbort(1) + else: + do_next_check(check_time) + monitor.waitForAbort(1) service()