Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[service.plexskipintro] 1.0.0 #2221

Open
wants to merge 5 commits into
base: matrix
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions service.plexskipintro/.github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Kodi forums, add-ons section
url: https://forum.kodi.tv/forumdisplay.php?fid=27
about: Please ask and answer questions here.
- name: Pull requests
url: https://github.com/xbmc/repo-scripts/pulls
about: When you want to submit a new or updated add-on, please open a pull request here.
34 changes: 34 additions & 0 deletions service.plexskipintro/.github/ISSUE_TEMPLATE/violating_addon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
name: Report a violating add-on
about: Report an add-on that violates the Kodi add-on rules.
title: ''
labels: 'violation'
assignees: ''

---

<!--- Before reporting an add-on for violation of the Kodi add-on rules, please --->
<!--- make sure that you are familiar with the rules for Kodi add-ons: --->
<!--- https://kodi.wiki/view/Add-on_rules --->

### Add-on details:
<!--- Provide some details of the violating add-on. --->
- Add-on name:
- Add-on ID:
- Version number:
- Kodi/repository version:

<!--- EXAMPLE HOW TO FILL ADD-ON DETAILS:
- Add-on name: Global Search
- Add-on ID: script.globalsearch
- Version number: 8.0.0
- Kodi/repository version: Leia
-->

### Rules that have been violated by the add-on:
<!--- Mention the rules violated based on: https://kodi.wiki/view/Add-on_rules --->


### Explain why the add-on is violating these rules:
<!--- Provide a short description/reason of why this add-on is broken. --->

674 changes: 674 additions & 0 deletions service.plexskipintro/LICENSE.txt

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions service.plexskipintro/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# KodiPlexSkipIntro

Kodi plugin that uses plex to get intro start/end times and provides a button to skip intros

## Name
KodiPlexSkipIntro

[![.](https://github.com/Darkmadda/PlexSkipIntro/blob/main/resources/media/plexskipintroSS.png?raw=true)](#)
## Description
This add-on will display a button on screen when an tv intro starts allowing you to skip the intro. The button will display for 10 seconds (this period can be changed in the settings)

## Installation
Download repository as zip file, then install add-on from zip file

## Usage
Wait for intro to start and click the skip intro if desired or wait for the timeout to pass and the button will display.

You will need to get a plex auth token and the base url for plex. look [here](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/) to find out how to obtain this (the screenshot will help)

[![.](https://github.com/Darkmadda/PlexSkipIntro/blob/main/resources/media/plexskipintroTokenSS.png?raw=true)](#)

## License
GNU GENERAL PUBLIC LICENSE
22 changes: 22 additions & 0 deletions service.plexskipintro/addon.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="service.plexskipintro" version="1.0.0" name="Plex TV Skip Intro" provider-name="dinomight">
<requires>
<import addon="xbmc.python" version="3.0.0" />
<import addon="script.module.requests" version="2.22.0" />
</requires>
<extension point="xbmc.service" library="service.py"/>
<extension point="xbmc.addon.metadata">
<description lang="en">Prompt a Skip Intro dialog Netflix style using plex metadata. Skips intro for your favourite shows</description>
<summary lang="en">Skip TV Shows Intro</summary>
<platform>all</platform>
<forum>-https://forum.kodi.tv/showthread.php?tid=368916</forum>
<website>https://github.com/Darkmadda/PlexSkipIntro</website>
<source>https://github.com/Darkmadda/PlexSkipIntro</source>
<news>https://github.com/Darkmadda/PlexSkipIntro/releases</news>
<license>GPL-3.0-ONLY</license>
<assets>
<icon>resources/icon.png</icon>
<fanart>resources/fanart.jpg</fanart>
</assets>
</extension>
</addon>
1 change: 1 addition & 0 deletions service.plexskipintro/lib/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from lib.addon import *
107 changes: 107 additions & 0 deletions service.plexskipintro/lib/addon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import xbmc, xbmcaddon, xbmcgui
from threading import Timer
from plexapi.server import PlexServer
from lib.definitions import *
import pprint
import time

def closeDialog():
global Dialog
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are all these globals in this file?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these globals are being used to pass around values between defs for various variables such as the times of the intro or the dialog to be displayed/hidden

global timer
global running
global Ran
global default_timeout
Dialog.close()
timer.cancel()
Ran = True
running = False
timer = Timer(default_timeout, closeDialog)

def onPlay():
xbmc.log("PLAY========***************",xbmc.LOGINFO)
global Ran
global introFound
global introStartTime
global introEndTime
Ran = False
introFound = False
myPlayer = xbmc.Player() # make Player() a single call.
while not myPlayer.isPlayingVideo():
time.sleep(1)
if myPlayer.isPlayingVideo():
season_number = myPlayer.getVideoInfoTag().getSeason()
episode_number = myPlayer.getVideoInfoTag().getEpisode()
show = myPlayer.getVideoInfoTag().getTVShowTitle()
baseurl = xbmcaddon.Addon().getSettingString("plex_base_url")
token = xbmcaddon.Addon().getSettingString("auth_token")
plex = PlexServer(baseurl, token)
shows = plex.library.section('TV Shows')
show = shows.search(show)[0]
episode = show.episode(None, season_number, episode_number)
for marker in episode.markers:
if (marker.type == "intro"):
introFound = True
introStartTime = marker.start / 1000
introEndTime = marker.end / 1000

def monitor():
monitor = xbmc.Monitor()
global introFound
global introStartTime
global introEndTime
global Ran
global Dialog
global running
global timer
global default_timeout
Dialog = CustomDialog('script-dialog.xml', addonPath)
while not monitor.abortRequested():
# check every 5 sec
if monitor.waitForAbort(3):
# Abort was requested while waiting. We should exit
break

if xbmc.Player().isPlaying():
if introFound:
if xbmc.Player().getTime() > introStartTime and xbmc.Player().getTime() < introEndTime:
if not running and not Ran:
timeout = introEndTime - xbmc.Player().getTime()
default_timeout
if timeout > default_timeout:
timeout = default_timeout
timer = Timer(timeout, closeDialog)
timer.start()
Dialog.show()
running = True

def onSeek():
global Ran
Ran = False

timer = Timer(default_timeout, closeDialog)

class CustomDialog(xbmcgui.WindowXMLDialog):

def __init__(self, xmlFile, resourcePath):
None

def onInit(self):
instuction = ''

def onAction(self, action):
if action == ACTION_PREVIOUS_MENU or action == ACTION_BACK:
self.close()

def onControl(self, control):
pass

def onFocus(self, control):
pass

def onClick(self, control):
global introEndTime
if control == OK_BUTTON:
xbmc.Player().seekTime(int(introEndTime))

if control in [OK_BUTTON, NEW_BUTTON, DISABLE_BUTTON]:
self.close()
18 changes: 18 additions & 0 deletions service.plexskipintro/lib/definitions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import xbmc, xbmcaddon, xbmcvfs
OK_BUTTON = 201
NEW_BUTTON = 202
DISABLE_BUTTON = 210
ACTION_PREVIOUS_MENU = 10
ACTION_BACK = 92
KODI_VERSION = int(xbmc.getInfoLabel("System.BuildVersion").split(".")[0])
addonInfo = xbmcaddon.Addon().getAddonInfo
settings = xbmcaddon.Addon().getSetting
addonPath = xbmcvfs.translatePath(addonInfo('path'))
introFound = True
introStartTime = 0
introEndTime = 0
chosen = False
Dialog = None
running = False
Ran = False
default_timeout = xbmcaddon.Addon().getSettingInt("default_timeout")
52 changes: 52 additions & 0 deletions service.plexskipintro/plexapi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
import logging
import os
from logging.handlers import RotatingFileHandler
from platform import uname
from uuid import getnode

from plexapi.config import PlexConfig, reset_base_headers
import plexapi.const as const
from plexapi.utils import SecretsFilter

# Load User Defined Config
DEFAULT_CONFIG_PATH = os.path.expanduser('~/.config/plexapi/config.ini')
CONFIG_PATH = os.environ.get('PLEXAPI_CONFIG_PATH', DEFAULT_CONFIG_PATH)
CONFIG = PlexConfig(CONFIG_PATH)

# PlexAPI Settings
PROJECT = 'PlexAPI'
VERSION = __version__ = const.__version__
TIMEOUT = CONFIG.get('plexapi.timeout', 30, int)
X_PLEX_CONTAINER_SIZE = CONFIG.get('plexapi.container_size', 100, int)
X_PLEX_ENABLE_FAST_CONNECT = CONFIG.get('plexapi.enable_fast_connect', False, bool)

# Plex Header Configuation
X_PLEX_PROVIDES = CONFIG.get('header.provides', 'controller')
X_PLEX_PLATFORM = CONFIG.get('header.platform', CONFIG.get('header.platorm', uname()[0]))
X_PLEX_PLATFORM_VERSION = CONFIG.get('header.platform_version', uname()[2])
X_PLEX_PRODUCT = CONFIG.get('header.product', PROJECT)
X_PLEX_VERSION = CONFIG.get('header.version', VERSION)
X_PLEX_DEVICE = CONFIG.get('header.device', X_PLEX_PLATFORM)
X_PLEX_DEVICE_NAME = CONFIG.get('header.device_name', uname()[1])
X_PLEX_IDENTIFIER = CONFIG.get('header.identifier', str(hex(getnode())))
BASE_HEADERS = reset_base_headers()

# Logging Configuration
log = logging.getLogger('plexapi')
logfile = CONFIG.get('log.path')
logformat = CONFIG.get('log.format', '%(asctime)s %(module)12s:%(lineno)-4s %(levelname)-9s %(message)s')
loglevel = CONFIG.get('log.level', 'INFO').upper()
loghandler = logging.NullHandler()

if logfile: # pragma: no cover
logbackups = CONFIG.get('log.backup_count', 3, int)
logbytes = CONFIG.get('log.rotate_bytes', 512000, int)
loghandler = RotatingFileHandler(os.path.expanduser(logfile), 'a', logbytes, logbackups)

loghandler.setFormatter(logging.Formatter(logformat))
log.addHandler(loghandler)
log.setLevel(loglevel)
logfilter = SecretsFilter()
if CONFIG.get('log.show_secrets', '').lower() != 'true':
log.addFilter(logfilter)
96 changes: 96 additions & 0 deletions service.plexskipintro/plexapi/alert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
import json
import threading

from plexapi import log


class AlertListener(threading.Thread):
""" Creates a websocket connection to the PlexServer to optionally receive alert notifications.
These often include messages from Plex about media scans as well as updates to currently running
Transcode Sessions. This class implements threading.Thread, therefore to start monitoring
alerts you must call .start() on the object once it's created. When calling
`PlexServer.startAlertListener()`, the thread will be started for you.

Known `state`-values for timeline entries, with identifier=`com.plexapp.plugins.library`:

:0: The item was created
:1: Reporting progress on item processing
:2: Matching the item
:3: Downloading the metadata
:4: Processing downloaded metadata
:5: The item processed
:9: The item deleted

When metadata agent is not set for the library processing ends with state=1.

Parameters:
server (:class:`~plexapi.server.PlexServer`): PlexServer this listener is connected to.
callback (func): Callback function to call on received messages. The callback function
will be sent a single argument 'data' which will contain a dictionary of data
received from the server. :samp:`def my_callback(data): ...`
callbackError (func): Callback function to call on errors. The callback function
will be sent a single argument 'error' which will contain the Error object.
:samp:`def my_callback(error): ...`
"""
key = '/:/websockets/notifications'

def __init__(self, server, callback=None, callbackError=None):
super(AlertListener, self).__init__()
self.daemon = True
self._server = server
self._callback = callback
self._callbackError = callbackError
self._ws = None

def run(self):
try:
import websocket
except ImportError:
log.warning("Can't use the AlertListener without websocket")
return
# create the websocket connection
url = self._server.url(self.key, includeToken=True).replace('http', 'ws')
log.info('Starting AlertListener: %s', url)
self._ws = websocket.WebSocketApp(url, on_message=self._onMessage,
on_error=self._onError)
self._ws.run_forever()

def stop(self):
""" Stop the AlertListener thread. Once the notifier is stopped, it cannot be directly
started again. You must call :func:`~plexapi.server.PlexServer.startAlertListener`
from a PlexServer instance.
"""
log.info('Stopping AlertListener.')
self._ws.close()

def _onMessage(self, *args):
""" Called when websocket message is received.
In earlier releases, websocket-client returned a tuple of two parameters: a websocket.app.WebSocketApp
object and the message as a STR. Current releases appear to only return the message.
We are assuming the last argument in the tuple is the message.
This is to support compatibility with current and previous releases of websocket-client.
"""
message = args[-1]
try:
data = json.loads(message)['NotificationContainer']
log.debug('Alert: %s %s %s', *data)
if self._callback:
self._callback(data)
except Exception as err: # pragma: no cover
log.error('AlertListener Msg Error: %s', err)

def _onError(self, *args): # pragma: no cover
""" Called when websocket error is received.
In earlier releases, websocket-client returned a tuple of two parameters: a websocket.app.WebSocketApp
object and the error. Current releases appear to only return the error.
We are assuming the last argument in the tuple is the message.
This is to support compatibility with current and previous releases of websocket-client.
"""
err = args[-1]
try:
log.error('AlertListener Error: %s', err)
if self._callbackError:
self._callbackError(err)
except Exception as err: # pragma: no cover
log.error('AlertListener Error: Error: %s', err)
Loading