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

Look for missing trailers using TMDB (backport) #1352

Draft
wants to merge 2 commits into
base: python2-beta
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Some people argue that PKC is 'hacky' because of the way it directly accesses th
- [Skip intros](https://support.plex.tv/articles/skip-content/)
- [Amazon Alexa voice recognition](https://www.plex.tv/apps/streaming-devices/amazon-alexa)
- [Cinema Trailers & Extras](https://support.plex.tv/articles/202934883-cinema-trailers-extras/)
- If Plex did not provide a trailer, automatically get one using the Kodi add-on [The Movie Database](https://kodi.wiki/view/Add-on:The_Movie_Database)
- [Plex Watch Later / Plex It!](https://support.plex.tv/hc/en-us/sections/200211783-Plex-It-)
- [Plex Companion](https://support.plex.tv/hc/en-us/sections/200276908-Plex-Companion): fling Plex media (or anything else) from other Plex devices to PlexKodiConnect
- Automatically sync Plex playlists to Kodi playlists and vice-versa
Expand Down
1 change: 1 addition & 0 deletions addon.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<import addon="script.module.defusedxml" version="0.5.0"/>
<import addon="plugin.video.plexkodiconnect.movies" version="2.1.3" />
<import addon="plugin.video.plexkodiconnect.tvshows" version="2.1.3" />
<import addon="metadata.themoviedb.org.python" version="1.3.1" />
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video audio image</provides>
Expand Down
22 changes: 11 additions & 11 deletions resources/lib/app/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ def __init__(self, entrypoint=False):
self.monitor = None
# xbmc.Player() instance
self.player = None
# Instance of FanartThread()
self.fanart_thread = None
# Instance of MetadataThread()
self.metadata_thread = None
# Instance of ImageCachingThread()
self.caching_thread = None
# Dialog to skip intro
Expand All @@ -62,24 +62,24 @@ def is_playing(self):
def is_playing_video(self):
return self.player.isPlayingVideo() == 1

def register_fanart_thread(self, thread):
self.fanart_thread = thread
def register_metadata_thread(self, thread):
self.metadata_thread = thread
self.threads.append(thread)

def deregister_fanart_thread(self, thread):
self.fanart_thread.unblock_callers()
self.fanart_thread = None
def deregister_metadata_thread(self, thread):
self.metadata_thread.unblock_callers()
self.metadata_thread = None
self.threads.remove(thread)

def suspend_fanart_thread(self, block=True):
def suspend_metadata_thread(self, block=True):
try:
self.fanart_thread.suspend(block=block)
self.metadata_thread.suspend(block=block)
except AttributeError:
pass

def resume_fanart_thread(self):
def resume_metadata_thread(self):
try:
self.fanart_thread.resume()
self.metadata_thread.resume()
except AttributeError:
pass

Expand Down
1 change: 1 addition & 0 deletions resources/lib/itemtypes/movies.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def add_update(self, xml, section_name=None, section_id=None,
kodi_id=kodi_id,
kodi_fileid=file_id,
kodi_pathid=kodi_pathid,
trailer_synced=bool(api.trailer()),
last_sync=self.last_sync)

def remove(self, plex_id, plex_type=None):
Expand Down
40 changes: 40 additions & 0 deletions resources/lib/itemtypes/movies_tmdb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
import logging
import os
import sys

import xbmcvfs
import xbmcaddon

# Import the existing Kodi add-on metadata.themoviedb.org.python
__ADDON__ = xbmcaddon.Addon(id='metadata.themoviedb.org.python')
__TEMP_PATH__ = os.path.join(__ADDON__.getAddonInfo('path').decode('utf-8'),
'python',
'lib')
__BASE__ = xbmcvfs.translatePath(__TEMP_PATH__.encode('utf-8')).decode('utf-8')
sys.path.append(__BASE__)
import tmdbscraper.tmdb as tmdb

logger = logging.getLogger('PLEX.movies_tmdb')


def get_tmdb_scraper(settings):
language = settings.getSettingString('language').decode('utf-8')
certcountry = settings.getSettingString('tmdbcertcountry').decode('utf-8')
return tmdb.TMDBMovieScraper(__ADDON__, language, certcountry)


# Instantiate once in order to prevent having to re-read the add-on settings
# for every single movie
__SCRAPER__ = get_tmdb_scraper(__ADDON__)


def get_tmdb_details(unique_ids):
details = __SCRAPER__.get_details(unique_ids)
LOG.error('details type. %s', type(details))
if 'error' in details:
logger.debug('Could not get tmdb details for %s. Error: %s',
unique_ids, details)
return details
27 changes: 27 additions & 0 deletions resources/lib/kodi_db/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,33 @@ def get_art(self, kodi_id, kodi_type):
(kodi_id, kodi_type))
return dict(self.cursor.fetchall())

def get_trailer(self, kodi_id, kodi_type):
"""
Returns the trailer's URL for kodi_type from the Kodi database or None
"""
if kodi_type == v.KODI_TYPE_MOVIE:
self.cursor.execute('SELECT c19 FROM movie WHERE idMovie=?',
(kodi_id, ))
else:
raise NotImplementedError('trailers for %s not implemented'
% kodi_type)
try:
return self.cursor.fetchone()[0]
except TypeError:
pass

@db.catch_operationalerrors
def set_trailer(self, kodi_id, kodi_type, url):
"""
Writes the trailer's url to the Kodi DB
"""
if kodi_type == v.KODI_TYPE_MOVIE:
self.cursor.execute('UPDATE movie SET c19=? WHERE idMovie=?',
(url, kodi_id))
else:
raise NotImplementedError('trailers for not implemented'
% kodi_type)

@db.catch_operationalerrors
def modify_streams(self, fileid, streamdetails=None, runtime=None):
"""
Expand Down
2 changes: 1 addition & 1 deletion resources/lib/library_sync/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
from .websocket import store_websocket_message, process_websocket_messages, \
WEBSOCKET_MESSAGES, PLAYSTATE_SESSIONS
from .common import update_kodi_library, PLAYLIST_SYNC_ENABLED
from .fanart import FanartThread, FanartTask
from .additional_metadata import MetadataThread, ProcessMetadataTask
from .sections import force_full_sync, delete_files, clear_window_vars
118 changes: 118 additions & 0 deletions resources/lib/library_sync/additional_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger

from . import additional_metadata_tmdb
from ..plex_db import PlexDB
from .. import backgroundthread, utils
from .. import variables as v, app


logger = getLogger('PLEX.sync.metadata')

BATCH_SIZE = 500

SUPPORTED_METADATA = {
v.PLEX_TYPE_MOVIE: (
('missing_trailers', additional_metadata_tmdb.process_trailers),
('missing_fanart', additional_metadata_tmdb.process_fanart),
),
v.PLEX_TYPE_SHOW: (
('missing_fanart', additional_metadata_tmdb.process_fanart),
),
}


class ProcessingNotDone(Exception):
"""Exception to detect whether we've completed our sync and did not have to
abort or suspend."""
pass


def processing_is_activated(item_getter):
"""Checks the PKC settings whether processing is even activated."""
if item_getter == 'missing_fanart':
return utils.settings('FanartTV') == 'true'
return True


class MetadataThread(backgroundthread.KillableThread):
"""This will potentially take hours!"""
def __init__(self, callback, refresh=False):
self.callback = callback
self.refresh = refresh
super(MetadataThread, self).__init__()

def should_suspend(self):
return self._suspended or app.APP.is_playing_video

def _process_in_batches(self, item_getter, processor, plex_type):
offset = 0
while True:
with PlexDB() as plexdb:
# Keep DB connection open only for a short period of time!
if self.refresh:
# Simply grab every single item if we want to refresh
func = plexdb.every_plex_id
else:
func = getattr(plexdb, item_getter)
batch = list(func(plex_type, offset, BATCH_SIZE))
for plex_id in batch:
# Do the actual, time-consuming processing
if self.should_suspend() or self.should_cancel():
raise ProcessingNotDone()
processor(plex_id, plex_type, self.refresh)
if len(batch) < BATCH_SIZE:
break
offset += BATCH_SIZE

def _loop(self):
for plex_type in SUPPORTED_METADATA:
for item_getter, processor in SUPPORTED_METADATA[plex_type]:
if not processing_is_activated(item_getter):
continue
self._process_in_batches(item_getter, processor, plex_type)

def _run(self):
finished = False
while not finished:
try:
self._loop()
except ProcessingNotDone:
finished = False
else:
finished = True
if self.wait_while_suspended():
break
logger.info('MetadataThread finished completely: %s', finished)
self.callback(finished)

def run(self):
logger.info('Starting MetadataThread')
app.APP.register_metadata_thread(self)
try:
self._run()
except Exception:
utils.ERROR(notify=True)
finally:
app.APP.deregister_metadata_thread(self)


class ProcessMetadataTask(backgroundthread.Task):
"""This task will also be executed while library sync is suspended!"""
def setup(self, plex_id, plex_type, refresh=False):
self.plex_id = plex_id
self.plex_type = plex_type
self.refresh = refresh

def run(self):
if self.plex_type not in SUPPORTED_METADATA:
return
for item_getter, processor in SUPPORTED_METADATA[self.plex_type]:
if self.should_cancel():
# Just don't process this item at all. Next full sync will
# take care of it
return
if not processing_is_activated(item_getter):
continue
processor(self.plex_id, self.plex_type, self.refresh)
Loading