Repository: Flexget/Flexget

Issue:
Trakt API image delete issue
```
2016-05-18 09:50 DEBUG    api_trakt                     movie `X-Men: Apocalypse` age 0 expires in 2 days
2016-05-18 09:50 DEBUG    api_trakt                     Searching with params: tmdb=246655
2016-05-18 09:50 DEBUG    utils.requests                 Fetching https://api-v2launch.trakt.tv/search
2016-05-18 09:50 DEBUG    utils.requests                 Fetching https://api-v2launch.trakt.tv/movies/149999
--------------------------------------------------------------------------------
ERROR in app [c:\python27\lib\site-packages\flask\app.py:1423]:
Exception on /trakt/movies/X-Men: Apocalypse/ [GET]
--------------------------------------------------------------------------------
Traceback (most recent call last):
  File "c:\python27\lib\site-packages\flask\app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "c:\python27\lib\site-packages\flask\app.py", line 1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "c:\python27\lib\site-packages\flask_restful\__init__.py", line 477, in wrapper
    resp = resource(*args, **kwargs)
  File "c:\python27\lib\site-packages\flask\views.py", line 84, in view
    return self.dispatch_request(*args, **kwargs)
  File "c:\python27\lib\site-packages\flask_restplus\resource.py", line 42, in dispatch_request
    resp = meth(*args, **kwargs)
  File "c:\python27\lib\site-packages\flexget\utils\database.py", line 34, in wrapper
    return func(*args, **kwargs)
  File "c:\python27\lib\site-packages\flexget\utils\sqlalchemy_utils.py", line 141, in __exit__
    self.commit()
  File "c:\python27\lib\site-packages\sqlalchemy\orm\session.py", line 801, in commit
    self.transaction.commit()
  File "c:\python27\lib\site-packages\sqlalchemy\orm\session.py", line 392, in commit
    self._prepare_impl()
  File "c:\python27\lib\site-packages\sqlalchemy\orm\session.py", line 372, in _prepare_impl
    self.session.flush()
  File "c:\python27\lib\site-packages\sqlalchemy\orm\session.py", line 2019, in flush
    self._flush(objects)
  File "c:\python27\lib\site-packages\sqlalchemy\orm\session.py", line 2137, in _flush
    transaction.rollback(_capture_exception=True)
  File "c:\python27\lib\site-packages\sqlalchemy\util\langhelpers.py", line 60, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "c:\python27\lib\site-packages\sqlalchemy\orm\session.py", line 2101, in _flush
    flush_context.execute()
  File "c:\python27\lib\site-packages\sqlalchemy\orm\unitofwork.py", line 373, in execute
    rec.execute(self)
  File "c:\python27\lib\site-packages\sqlalchemy\orm\unitofwork.py", line 487, in execute
    self.dependency_processor.process_saves(uow, states)
  File "c:\python27\lib\site-packages\sqlalchemy\orm\dependency.py", line 1092, in process_saves
    secondary_update, secondary_delete)
  File "c:\python27\lib\site-packages\sqlalchemy\orm\dependency.py", line 1113, in _run_crud
    result.rowcount)
StaleDataError: DELETE statement on table 'trakt_image_movie' expected to delete 1 row(s); Only 2 were matched.
```

Happened on API call, probably due to movie expiration



Discussion:
That entire mess says nothing at all without context of what it's actually trying to do and where it's doing it.

That's hard to do. All I did was load the movies ui. I can try to reproduce it with debug db sessions I guess 

I know. It was more a comment on how flask generates the traceback lol

I think both images and translations can br used as association proxies, like @gazpachoking did in tmdb 

I'm also seeing this error, but in my case it happens on lazy_lookup:

```
2016-07-24 22:20 ERROR    lazy_lookup   Fill movie queue Unhandled error in lazy lookup plugin
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/flexget/utils/lazy_dict.py", line 37, in __getitem__
    func(self.store)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/metainfo/trakt_lookup.py", line 318, in lazy_watched_lookup
    item = lookup(**lookupargs)
  File "/usr/local/lib/python2.7/dist-packages/flexget/utils/database.py", line 31, in wrapper
    return func(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/api_trakt.py", line 1135, in lookup_movie
    if not session.query(TraktMovieSearchResult).filter(TraktMovieSearchResult.search == title.lower()).first():
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/query.py", line 2659, in first
    ret = list(self[0:1])
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/query.py", line 2457, in __getitem__
    return list(res)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/query.py", line 2760, in __iter__
    self.session._autoflush()
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/session.py", line 1293, in _autoflush
    self.flush()
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/session.py", line 2019, in flush
    self._flush(objects)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/session.py", line 2137, in _flush
    transaction.rollback(_capture_exception=True)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/util/langhelpers.py", line 60, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/session.py", line 2101, in _flush
    flush_context.execute()
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/unitofwork.py", line 373, in execute
    rec.execute(self)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/unitofwork.py", line 487, in execute
    self.dependency_processor.process_saves(uow, states)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/dependency.py", line 1092, in process_saves
    secondary_update, secondary_delete)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/dependency.py", line 1113, in _run_crud
    result.rowcount)
StaleDataError: DELETE statement on table 'trakt_image_movie' expected to delete 4 row(s); Only 5 were matched.
```

ugh I really hate this plugin right now.


Resolving patch:
diff --git a/flexget/plugins/api/trakt_lookup.py b/flexget/plugins/api/trakt_lookup.py
--- a/flexget/plugins/api/trakt_lookup.py
+++ b/flexget/plugins/api/trakt_lookup.py
@@ -6,7 +6,7 @@
 from flask_restplus import inputs
 
 from flexget.api import api, APIResource
-from flexget.plugins.internal.api_trakt import ApiTrakt as at, list_actors, get_translations
+from flexget.plugins.internal.api_trakt import ApiTrakt as at, list_actors, get_translations_dict
 
 trakt_api = api.namespace('trakt', description='Trakt lookup endpoint')
 
@@ -18,7 +18,8 @@ class objects_container(object):
             "full": {"type": ["string", "null"]},
             "medium": {"type": ["string", "null"]},
             "thumb": {"type": ["string", "null"]}
-        }
+        },
+        'additionalProperties': False
     }
 
     images_object = {
@@ -28,8 +29,11 @@ class objects_container(object):
             "clearart": internal_image_object,
             "fanart": internal_image_object,
             "logo": internal_image_object,
-            "poster": internal_image_object
-        }}
+            "poster": internal_image_object,
+            "thumb": internal_image_object
+        },
+        'additionalProperties': False
+    }
 
     translation_object = {
         'type': 'object',
@@ -39,7 +43,8 @@ class objects_container(object):
                            "overview": {'type': 'string'},
                            "tagline": {'type': 'string'},
                            "title": {'type': 'string'},
-                       }}
+                       },
+                       'additionalProperties': False}
         }
     }
 
@@ -59,8 +64,10 @@ class objects_container(object):
                     "biography": {'type': ['string', 'null']},
                     "homepage": {'type': 'string'},
                     "death": {'type': ['string', 'null']}
-                }
-            }}}
+                },
+                'additionalProperties': False
+            }}
+    }
 
     base_return_object = {
         'type': 'object',
@@ -70,14 +77,23 @@ class objects_container(object):
             'cached_at': {'type': 'string', 'format': 'date-time'},
             'genres': {'type': 'array', 'items': {'type': 'string'}},
             'id': {'type': 'integer'},
-            "overview": {'type': ['string', 'null']},
-            "runtime": {'type': ['integer', 'null']},
-            "rating": {'type': ['number', 'null']},
-            "votes": {'type': ['integer', 'null']},
-            "language": {'type': ['string', 'null']},
-            "updated_at": {'type': 'string', 'format': 'date-time'},
-            "images": images_object
-        }
+            'overview': {'type': ['string', 'null']},
+            'runtime': {'type': ['integer', 'null']},
+            'rating': {'type': ['number', 'null']},
+            'votes': {'type': ['integer', 'null']},
+            'language': {'type': ['string', 'null']},
+            'updated_at': {'type': 'string', 'format': 'date-time'},
+            'images': images_object,
+            'main_image': {'type': ['string', 'null']},
+            'title': {'type': 'string'},
+            'year': {'type': ['integer', 'null']},
+            'homepage': {'type': ['string', 'null']},
+            'slug': {'type': ['string', 'null']},
+            'tmdb_id': {'type': ['integer', 'null']},
+            'imdb_id': {'type': ['string', 'null']}
+
+        },
+        'additionalProperties': False
     }
 
     series_return_object = copy.deepcopy(base_return_object)
@@ -86,15 +102,17 @@ class objects_container(object):
     series_return_object['properties']['first_aired'] = {'type': ['string', 'null'], 'format': 'date-time'}
     series_return_object['properties']['air_day'] = {'type': ['string', 'null']}
     series_return_object['properties']['air_time'] = {'type': ['string', 'null']}
-    series_return_object['properties']['certification'] = {'type': ['string', "null"]}
+    series_return_object['properties']['certification'] = {'type': ['string', 'null']}
     series_return_object['properties']['network'] = {'type': ['string', 'null']}
     series_return_object['properties']['country'] = {'type': ['string', 'null']}
     series_return_object['properties']['status'] = {'type': 'string'}
-    series_return_object['properties']['aired_episodes'] = {'type': 'integer'}
+    series_return_object['properties']['timezone'] = {'type': ['string', 'null']}
+    series_return_object['properties']['number_of_aired_episodes'] = {'type': ['integer', 'null']}
 
     movie_return_object = copy.deepcopy(base_return_object)
     movie_return_object['properties']['tagline'] = {'type': 'string'}
     movie_return_object['properties']['released'] = {'type': 'string'}
+    movie_return_object['properties']['trailer'] = {'type': 'string'}
 
     default_error_object = {
         'type': 'object',
@@ -141,9 +159,9 @@ def get(self, title, session=None):
                     }, 404
         result = series.to_dict()
         if include_actors:
-            result["actors"] = list_actors(series.actors)
+            result['actors'] = list_actors(series.actors)
         if include_translations:
-            result["translations"] = get_translations(series.translate)
+            result['translations'] = get_translations_dict(series.translations, 'show')
         return jsonify(result)
 
 
@@ -167,7 +185,7 @@ def get(self, title, session=None):
                     }, 404
         result = movie.to_dict()
         if include_actors:
-            result["actors"] = list_actors(movie.actors)
+            result['actors'] = list_actors(movie.actors)
         if include_translations:
-            result["translations"] = get_translations(movie.translate)
+            result['translations'] = get_translations_dict(movie.translations, 'movie')
         return jsonify(result)
diff --git a/flexget/plugins/internal/api_trakt.py b/flexget/plugins/internal/api_trakt.py
--- a/flexget/plugins/internal/api_trakt.py
+++ b/flexget/plugins/internal/api_trakt.py
@@ -20,11 +20,11 @@
 from flexget.manager import Session
 from flexget.plugin import get_plugin_by_name
 from flexget.utils import requests
-from flexget.utils.database import with_session
+from flexget.utils.database import with_session, json_synonym
 from flexget.utils.simple_persistence import SimplePersistence
 from flexget.utils.tools import TimedDict
 
-Base = db_schema.versioned_base('api_trakt', 5)
+Base = db_schema.versioned_base('api_trakt', 6)
 AuthBase = db_schema.versioned_base('trakt_auth', 0)
 log = logging.getLogger('api_trakt')
 # Production Site
@@ -174,6 +174,12 @@ def get_access_token(account, token=None, refresh=False, re_auth=False, called_f
                 raise plugin.PluginError('Token exchange with trakt failed: {0}'.format(e))
 
 
+def set_image_attributes(obj, data):
+    for image, images in data['images'].items():
+        for size, url in images.items():
+            setattr(obj, 'image_%s_%s' % (image, size), url)
+
+
 def make_list_slug(name):
     """Return the slug for use in url for given list name."""
     slug = name.lower()
@@ -223,7 +229,7 @@ def get_api_url(*endpoint):
 
 @db_schema.upgrade('api_trakt')
 def upgrade(ver, session):
-    if ver is None or ver <= 4:
+    if ver is None or ver <= 5:
         raise db_schema.UpgradeImpossible
     return ver
 
@@ -251,34 +257,36 @@ def get_entry_ids(entry):
     return ids
 
 
-class TraktTranslation(Base):
-    __tablename__ = 'trakt_translations'
+class TraktMovieTranslation(Base):
+    __tablename__ = 'trakt_movie_translations'
 
     id = Column(Integer, primary_key=True, autoincrement=True)
-    name = Column(Unicode)
+    language = Column(Unicode)
+    overview = Column(Unicode)
+    tagline = Column(Unicode)
+    title = Column(Unicode)
+    movie_id = Column(Integer, ForeignKey('trakt_movies.id'))
 
+    def __init__(self, translation, session):
+        super(TraktMovieTranslation, self).__init__()
+        self.update(translation, session)
 
-show_trans_table = Table('trakt_show_trans', Base.metadata,
-                         Column('show_id', Integer, ForeignKey('trakt_shows.id')),
-                         Column('trans_id', Integer, ForeignKey('trakt_translations.id')))
-Base.register_table(show_trans_table)
-movie_trans_table = Table('trakt_movie_trans', Base.metadata,
-                          Column('movie_id', Integer, ForeignKey('trakt_movies.id')),
-                          Column('trans_id', Integer, ForeignKey('trakt_translations.id')))
-Base.register_table(movie_trans_table)
+    def update(self, translation, session):
+        for col in translation.keys():
+            setattr(self, col, translation.get(col))
 
 
-class TraktTranslate(Base):
-    __tablename__ = 'trakt_translate'
+class TraktShowTranslation(Base):
+    __tablename__ = 'trakt_show_translations'
 
     id = Column(Integer, primary_key=True, autoincrement=True)
     language = Column(Unicode)
     overview = Column(Unicode)
-    tagline = Column(Unicode)
     title = Column(Unicode)
+    show_id = Column(Integer, ForeignKey('trakt_shows.id'))
 
     def __init__(self, translation, session):
-        super(TraktTranslate, self).__init__()
+        super(TraktShowTranslation, self).__init__()
         self.update(translation, session)
 
     def update(self, translation, session):
@@ -286,113 +294,44 @@ def update(self, translation, session):
             setattr(self, col, translation.get(col))
 
 
-def get_translation(ident, style):
+def get_translations(ident, style):
     url = get_api_url(style + 's', ident, 'translations')
+    trakt_translation = TraktShowTranslation if style == 'show' else TraktMovieTranslation
+    trakt_translation_id = getattr(trakt_translation, style + '_id')
     translations = []
     req_session = get_session()
     try:
         results = req_session.get(url, params={'extended': 'full,images'}).json()
         with Session() as session:
             for result in results:
-                translate = session.query(TraktTranslate).filter(and_(
-                    TraktTranslate.language == result.get('language'),
-                    TraktTranslate.title == result.get('title'))).first()
-                if not translate:
-                    translate = TraktTranslate(result, session)
-                translations.append(translate)
+                translation = session.query(trakt_translation).filter(and_(
+                    trakt_translation.language == result.get('language'),
+                    trakt_translation_id == ident)).first()
+                if not translation:
+                    translation = trakt_translation(result, session)
+                translations.append(translation)
         return translations
     except requests.RequestException as e:
-        log.debug('Error adding translations to trakt id {} : {}'.format(ident, e))
-
-
-trans_show_table = Table('show_trans', Base.metadata,
-                         Column('show_id', Integer, ForeignKey('trakt_shows.id')),
-                         Column('trans_id', Integer, ForeignKey('trakt_translate.id')))
-Base.register_table(trans_show_table)
-trans_movie_table = Table('movie_tans', Base.metadata,
-                          Column('movie_id', Integer, ForeignKey('trakt_movies.id')),
-                          Column('trans_id', Integer, ForeignKey('trakt_translate.id')))
-Base.register_table(trans_movie_table)
-
-
-def get_db_trans(trans, session):
-    """Takes a list of genres as strings, returns the database instances for them."""
-    db_trans = []
-    for tran in trans:
-        db_tran = session.query(TraktTranslation).filter(TraktTranslation.name == tran).first()
-        if not db_tran:
-            db_tran = TraktTranslation(name=tran)
-            session.add(db_tran)
-        db_trans.append(db_tran)
-    return db_trans
+        log.debug('Error adding translations to trakt id %s: %s', ident, e)
 
 
 class TraktGenre(Base):
     __tablename__ = 'trakt_genres'
 
-    id = Column(Integer, primary_key=True, autoincrement=True)
-    name = Column(Unicode)
+    name = Column(Unicode, primary_key=True)
 
 
 show_genres_table = Table('trakt_show_genres', Base.metadata,
                           Column('show_id', Integer, ForeignKey('trakt_shows.id')),
-                          Column('genre_id', Integer, ForeignKey('trakt_genres.id')))
+                          Column('genre_id', Unicode, ForeignKey('trakt_genres.name')))
 Base.register_table(show_genres_table)
 
 movie_genres_table = Table('trakt_movie_genres', Base.metadata,
                            Column('movie_id', Integer, ForeignKey('trakt_movies.id')),
-                           Column('genre_id', Integer, ForeignKey('trakt_genres.id')))
+                           Column('genre_id', Unicode, ForeignKey('trakt_genres.name')))
 Base.register_table(movie_genres_table)
 
 
-def get_db_genres(genres, session):
-    """Takes a list of genres as strings, returns the database instances for them."""
-    db_genres = []
-    for genre in genres:
-        genre = genre.replace('-', ' ')
-        db_genre = session.query(TraktGenre).filter(TraktGenre.name == genre).first()
-        if not db_genre:
-            db_genre = TraktGenre(name=genre)
-            session.add(db_genre)
-        db_genres.append(db_genre)
-    return db_genres
-
-
-class TraktImages(Base):
-    __tablename__ = 'trakt_images'
-
-    id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
-    ident = Column(Unicode)
-    style = Column(Unicode)
-    url = Column(Unicode)
-
-    def __init__(self, images):
-        super(TraktImages, self).__init__()
-        self.update(images)
-
-    def update(self, images):
-        for col in images.keys():
-            setattr(self, col, images.get(col))
-
-
-trakt_image_actors = Table('trakt_image_actor', Base.metadata,
-                           Column('trakt_actor', Integer, ForeignKey('trakt_actors.id')),
-                           Column('trakt_image', Integer, ForeignKey('trakt_images.id')))
-Base.register_table(trakt_image_actors)
-trakt_image_shows = Table('trakt_image_show', Base.metadata,
-                          Column('trakt_show', Integer, ForeignKey('trakt_shows.id')),
-                          Column('trakt_image', Integer, ForeignKey('trakt_images.id')))
-Base.register_table(trakt_image_shows)
-trakt_image_movies = Table('trakt_image_movie', Base.metadata,
-                           Column('trakt_movie', Integer, ForeignKey('trakt_movies.id')),
-                           Column('trakt_image', Integer, ForeignKey('trakt_images.id')))
-Base.register_table(trakt_image_movies)
-trakt_image_episodes = Table('trakt_image_episode', Base.metadata,
-                             Column('trakt_episode', Integer, ForeignKey('trakt_episodes.id')),
-                             Column('trakt_image', Integer, ForeignKey('trakt_images.id')))
-Base.register_table(trakt_image_episodes)
-
-
 class TraktActor(Base):
     __tablename__ = 'trakt_actors'
 
@@ -405,12 +344,24 @@ class TraktActor(Base):
     birthday = Column(Date)
     death = Column(Date)
     homepage = Column(Unicode)
-    images = relation(TraktImages, secondary=trakt_image_actors)
+    image_headshot_full = Column(Unicode)
+    image_headshot_medium = Column(Unicode)
+    image_headshot_thumb = Column(Unicode)
+    image_fanart_full = Column(Unicode)
+    image_fanart_medium = Column(Unicode)
+    image_fanart_thumb = Column(Unicode)
 
     def __init__(self, actor, session):
         super(TraktActor, self).__init__()
         self.update(actor, session)
 
+    @property
+    def main_image(self):
+        for size in ['medium', 'full', 'thumb']:
+            for image_type in ['headshot', 'fanart']:
+                if getattr(self, 'image_%s_%s' % (image_type, size)) is not None:
+                    return getattr(self, 'image_%s_%s' % (image_type, size))
+
     def update(self, actor, session):
         if self.id and self.id != actor.get('ids').get('trakt'):
             raise Exception('Tried to update db actors with different actor data')
@@ -428,8 +379,7 @@ def update(self, actor, session):
             self.death = dateutil_parse(actor.get('death'))
         self.homepage = actor.get('homepage')
         if actor.get('images'):
-            img = actor.get('images')
-            self.images = get_db_images(img, session)
+            set_image_attributes(self, actor)
 
     def to_dict(self):
         return {
@@ -437,31 +387,22 @@ def to_dict(self):
             'trakt_id': self.id,
             'imdb_id': self.imdb,
             'tmdb_id': self.tmdb,
-            'images': list_images(self.images)
+            'images': {
+                'headshot': {
+                    'full': self.image_headshot_full,
+                    'medium': self.image_headshot_medium,
+                    'thumb': self.image_headshot_thumb
+                },
+                'fanart': {
+                    'full': self.image_fanart_full,
+                    'medium': self.image_fanart_medium,
+                    'thumb': self.image_fanart_thumb
+                }
+            },
+            "main_image": self.main_image
         }
 
 
-def get_db_images(image, session):
-    try:
-        flat = []
-        images = []
-        if image:
-            for i, s in image.items():
-                for ss, u in s.items():
-                    a = {'ident': i, 'style': ss, 'url': u}
-                    flat.append(a)
-        for i in flat:
-            url = i.get('url')
-            im = session.query(TraktImages).filter(TraktImages.url == url).first()
-            if not im:
-                im = TraktImages(i)
-            images.append(im)
-        return images
-    except TypeError as e:
-        log.debug('Error has Occured during images: %s', e)
-        return
-
-
 show_actors_table = Table('trakt_show_actors', Base.metadata,
                           Column('show_id', Integer, ForeignKey('trakt_shows.id')),
                           Column('actors_id', Integer, ForeignKey('trakt_actors.id')))
@@ -493,20 +434,15 @@ def get_db_actors(ident, style):
         return
 
 
-def list_images(images):
-    res = {}
-    for image in images:
-        res.setdefault(image.ident, {})[image.style] = image.url
-    return res
-
-
-def get_translations(translate):
+def get_translations_dict(translate, style):
     res = {}
     for lang in translate:
-        info = {'overview': lang.overview,
-                'title': lang.title,
-                'tagline': lang.tagline,
-                }
+        info = {
+            'overview': lang.overview,
+            'title': lang.title,
+        }
+        if style == 'movie':
+            info['tagline'] = lang.tagline
         res[lang.language] = info
     return res
 
@@ -524,7 +460,12 @@ def list_actors(actors):
             'biography': actor.biography,
             'homepage': actor.homepage,
             'death': actor.death.strftime("%Y/%m/%d") if actor.death else None,
-            'images': list_images(actor.images)
+            'headshot_full': 'image_headshot_full',
+            'headshot_medium': 'image_headshot_medium',
+            'headshot_thumb': 'image_headshot_thumb',
+            'fanart_full': 'image_fanart_full',
+            'fanart_medium': 'image_fanart_medium',
+            'fanart_thumb': 'image_fanart_thumb',
         }
         res[str(actor.id)] = info
     return res
@@ -543,11 +484,14 @@ class TraktEpisode(Base):
     number = Column(Integer)
     number_abs = Column(Integer)
     overview = Column(Unicode)
-    images = relation(TraktImages, secondary=trakt_image_episodes)
     first_aired = Column(DateTime)
     updated_at = Column(DateTime)
     cached_at = Column(DateTime)
 
+    image_screenshot_full = Column(Unicode)
+    image_screenshot_medium = Column(Unicode)
+    image_screenshot_thumb = Column(Unicode)
+
     series_id = Column(Integer, ForeignKey('trakt_shows.id'), nullable=False)
 
     def __init__(self, trakt_episode, session):
@@ -564,7 +508,7 @@ def update(self, trakt_episode, session):
         self.tmdb_id = trakt_episode['ids']['tmdb']
         self.tvrage_id = trakt_episode['ids']['tvrage']
         if trakt_episode.get('images'):
-            self.images = get_db_images(trakt_episode.get('images'), session)
+            set_image_attributes(self, trakt_episode)
         self.tvdb_id = trakt_episode['ids']['tvdb']
         self.first_aired = None
         if trakt_episode.get('first_aired'):
@@ -600,7 +544,10 @@ class TraktShow(Base):
     runtime = Column(Integer)
     certification = Column(Unicode)
     network = Column(Unicode)
-    images = relation(TraktImages, secondary=trakt_image_shows)
+    image_poster_full = Column(Unicode)
+    image_poster_medium = Column(Unicode)
+    image_poster_thumb = Column(Unicode)
+    image_thumb_full = Column(Unicode)
     country = Column(Unicode)
     status = Column(String)
     rating = Column(Integer)
@@ -609,14 +556,21 @@ class TraktShow(Base):
     homepage = Column(Unicode)
     trailer = Column(Unicode)
     aired_episodes = Column(Integer)
-    translations = relation(TraktTranslation, secondary=show_trans_table)
+    _translations = relation(TraktShowTranslation)
+    _translation_languages = Column('translation_languages', Unicode)
+    translation_languages = json_synonym('_translation_languages')
     episodes = relation(TraktEpisode, backref='show', cascade='all, delete, delete-orphan', lazy='dynamic')
-    _translate = relation(TraktTranslate, secondary=trans_show_table)
     genres = relation(TraktGenre, secondary=show_genres_table)
     _actors = relation(TraktActor, secondary=show_actors_table)
     updated_at = Column(DateTime)
     cached_at = Column(DateTime)
 
+    @property
+    def main_image(self):
+        for size in ['medium', 'full', 'thumb']:
+            if getattr(self, 'image_poster_%s' % size) is not None:
+                return getattr(self, 'image_poster_%s' % size)
+
     def to_dict(self):
         return {
             "id": self.id,
@@ -645,7 +599,17 @@ def to_dict(self):
             "genres": [g.name for g in self.genres],
             "updated_at": self.updated_at,
             "cached_at": self.cached_at,
-            "images": list_images(self.images)
+            "images": {
+                'poster': {
+                    'full': self.image_poster_full,
+                    'medium': self.image_poster_medium,
+                    'thumb': self.image_poster_thumb
+                },
+                'thumb': {
+                    'full': self.image_thumb_full
+                }
+            },
+            "main_image": self.main_image
         }
 
     def __init__(self, trakt_show, session):
@@ -664,7 +628,7 @@ def update(self, trakt_show, session):
         self.tvrage_id = trakt_show['ids']['tvrage']
         self.tvdb_id = trakt_show['ids']['tvdb']
         if trakt_show.get('images'):
-            self.images = get_db_images(trakt_show.get('images'), session)
+            set_image_attributes(self, trakt_show)
         if trakt_show.get('airs'):
             airs = trakt_show.get('airs')
             self.air_day = airs.get('day')
@@ -684,9 +648,9 @@ def update(self, trakt_show, session):
                     'trailer', 'homepage']:
             setattr(self, col, trakt_show.get(col))
 
-        self.genres[:] = get_db_genres(trakt_show.get('genres') or [], session)
-        self.translations[:] = get_db_trans(trakt_show.get('available_translations') or [], session)
+        self.genres = [TraktGenre(name=g.replace(' ', '-')) for g in trakt_show.get('genres', [])]
         self.cached_at = datetime.now()
+        self.translation_languages = trakt_show.get('available_translations', [])
 
     def get_episode(self, season, number, session, only_cached=False):
         # TODO: Does series data being expired mean all episode data should be refreshed?
@@ -730,10 +694,10 @@ def expired(self):
         return self.cached_at < datetime.now() - timedelta(days=refresh_interval)
 
     @property
-    def translate(self):
-        if not self._translate:
-            self._translate[:] = get_translation(self.id, 'show')
-        return self._translate
+    def translations(self):
+        if not self._translations:
+            self._translations = get_translations(self.id, 'show')
+        return self._translations
 
     @property
     def actors(self):
@@ -765,9 +729,19 @@ class TraktMovie(Base):
     language = Column(Unicode)
     updated_at = Column(DateTime)
     cached_at = Column(DateTime)
-    translations = relation(TraktTranslation, secondary=movie_trans_table)
-    _translate = relation(TraktTranslate, secondary=trans_movie_table)
-    images = relation(TraktImages, secondary=trakt_image_movies)
+    _translations = relation(TraktMovieTranslation, backref='movie')
+    _translation_languages = Column('translation_languages', Unicode)
+    translation_languages = json_synonym('_translation_languages')
+    image_fanart_full = Column(Unicode)
+    image_fanart_medium = Column(Unicode)
+    image_fanart_thumb = Column(Unicode)
+    image_poster_full = Column(Unicode)
+    image_poster_medium = Column(Unicode)
+    image_poster_thumb = Column(Unicode)
+    image_logo_full = Column(Unicode)
+    image_clearart_full = Column(Unicode)
+    image_banner_full = Column(Unicode)
+    image_thumb_full = Column(Unicode)
     genres = relation(TraktGenre, secondary=movie_genres_table)
     _actors = relation(TraktActor, secondary=movie_actors_table)
 
@@ -775,6 +749,13 @@ def __init__(self, trakt_movie, session):
         super(TraktMovie, self).__init__()
         self.update(trakt_movie, session)
 
+    @property
+    def main_image(self):
+        for size in ['medium', 'full', 'thumb']:
+            for image_type in ['poster', 'fanart']:
+                if getattr(self, 'image_%s_%s' % (image_type, size)) is not None:
+                    return getattr(self, 'image_%s_%s' % (image_type, size))
+
     def to_dict(self):
         return {
             "id": self.id,
@@ -795,7 +776,31 @@ def to_dict(self):
             "genres": [g.name for g in self.genres],
             "updated_at": self.updated_at,
             "cached_at": self.cached_at,
-            "images": list_images(self.images)
+            "main_image": self.main_image,
+            "images": {
+                'fanart': {
+                    'full': self.image_fanart_full,
+                    'medium': self.image_fanart_medium,
+                    'thumb': self.image_fanart_thumb
+                },
+                'poster': {
+                    'full': self.image_poster_full,
+                    'medium': self.image_poster_medium,
+                    'thumb': self.image_poster_thumb
+                },
+                'logo': {
+                    'full': self.image_logo_full
+                },
+                'clearart': {
+                    'full': self.image_clearart_full
+                },
+                'banner': {
+                    'full': self.image_banner_full
+                },
+                'thumb': {
+                    'full': self.image_thumb_full
+                }
+            }
         }
 
     def update(self, trakt_movie, session):
@@ -813,10 +818,11 @@ def update(self, trakt_movie, session):
         if trakt_movie.get('released'):
             self.released = dateutil_parse(trakt_movie.get('released'), ignoretz=True)
         self.updated_at = dateutil_parse(trakt_movie.get('updated_at'), ignoretz=True)
-        self.genres[:] = get_db_genres(trakt_movie.get('genres', []), session)
-        self.translations[:] = get_db_trans(trakt_movie.get('available_translations', []), session)
+        self.genres = [TraktGenre(name=g.replace(' ', '-')) for g in trakt_movie.get('genres', [])]
         self.cached_at = datetime.now()
-        self.images = get_db_images(trakt_movie.get('images'), session)
+        if trakt_movie.get('images'):
+            set_image_attributes(self, trakt_movie)
+        self.translation_languages = trakt_movie.get('available_translations', [])
 
     @property
     def expired(self):
@@ -836,10 +842,10 @@ def expired(self):
         return self.cached_at < datetime.now() - timedelta(days=refresh_interval)
 
     @property
-    def translate(self):
-        if not self._translate:
-            self._translate[:] = get_translation(self.id, 'movie')
-        return self._translate
+    def translations(self):
+        if not self._translations:
+            self._translations = get_translations(self.id, 'movie')
+        return self._translations
 
     @property
     def actors(self):
@@ -1089,18 +1095,13 @@ def lookup_series(session=None, only_cached=None, **lookup_params):
                 log.debug('Error refreshing show data from trakt, using cached. %s', e)
                 return series
             raise
-        series = session.query(TraktShow).filter(TraktShow.id == trakt_show['ids']['trakt']).first()
-        if series:
-            series.update(trakt_show, session)
-        else:
-            series = TraktShow(trakt_show, session)
-            session.add(series)
+        series = session.merge(TraktShow(trakt_show, session))
         if series and title.lower() == series.title.lower():
             return series
         elif series and title and not found:
             if not session.query(TraktShowSearchResult).filter(TraktShowSearchResult.search == title.lower()).first():
                 log.debug('Adding search result to db')
-                session.add(TraktShowSearchResult(search=title, series=series))
+                session.merge(TraktShowSearchResult(search=title, series=series))
         elif series and found:
             log.debug('Updating search result in db')
             found.series = series
@@ -1130,18 +1131,13 @@ def lookup_movie(session=None, only_cached=None, **lookup_params):
                 log.debug('Error refreshing movie data from trakt, using cached. %s', e)
                 return movie
             raise
-        movie = session.query(TraktMovie).filter(TraktMovie.id == trakt_movie['ids']['trakt']).first()
-        if movie:
-            movie.update(trakt_movie, session)
-        else:
-            movie = TraktMovie(trakt_movie, session)
-            session.add(movie)
+        movie = session.merge(TraktMovie(trakt_movie, session))
         if movie and title.lower() == movie.title.lower():
             return movie
         if movie and title and not found:
             if not session.query(TraktMovieSearchResult).filter(TraktMovieSearchResult.search == title.lower()).first():
                 log.debug('Adding search result to db')
-                session.add(TraktMovieSearchResult(search=title, movie=movie))
+                session.merge(TraktMovieSearchResult(search=title, movie=movie))
         elif movie and found:
             log.debug('Updating search result in db')
             found.movie = movie
diff --git a/flexget/plugins/metainfo/trakt_lookup.py b/flexget/plugins/metainfo/trakt_lookup.py
--- a/flexget/plugins/metainfo/trakt_lookup.py
+++ b/flexget/plugins/metainfo/trakt_lookup.py
@@ -10,7 +10,7 @@
 from flexget.manager import Session
 
 try:
-    from flexget.plugins.internal.api_trakt import ApiTrakt, list_actors, list_images, get_translations
+    from flexget.plugins.internal.api_trakt import ApiTrakt, list_actors, get_translations_dict
 
     lookup_series = ApiTrakt.lookup_series
     lookup_movie = ApiTrakt.lookup_movie
@@ -98,15 +98,18 @@ class PluginTraktLookup(object):
         'trakt_series_language': 'language',
         'trakt_series_aired_episodes': 'aired_episodes',
         'trakt_series_episodes': lambda show: [episodes.title for episodes in show.episodes],
-        'trakt_series_images': lambda im: list_images(im.images),
-        'trakt_languages': lambda i: [t.name for t in i.translations],
+        'trakt_series_poster_full': 'image_poster_full',
+        'trakt_series_poster_medium': 'image_poster_medium',
+        'trakt_series_poster_thumb': 'image_poster_thumb',
+        'trakt_series_thumb': 'image_thumb_full',
+        'trakt_languages': 'translation_languages',
     }
 
     series_actor_map = {
         'trakt_actors': lambda show: list_actors(show.actors),
     }
     show_translate_map = {
-        'trakt_translations': lambda show: get_translations(show.translate),
+        'trakt_translations': lambda show: get_translations_dict(show.translations, 'show'),
     }
 
     # Episode info
@@ -123,7 +126,9 @@ class PluginTraktLookup(object):
         'trakt_season': 'season',
         'trakt_episode': 'number',
         'trakt_ep_id': lambda ep: 'S%02dE%02d' % (ep.season, ep.number),
-        'trakt_ep_images': lambda im: list_images(im.images),
+        'trakt_ep_screenshot_full': 'image_screenshot_full',
+        'trakt_ep_screenshot_medium': 'image_screenshot_medium',
+        'trakt_ep_screenshot_thumb': 'image_screenshot_thumb',
     }
 
     # Movie info
@@ -146,12 +151,21 @@ class PluginTraktLookup(object):
         'trakt_trailer': 'trailer',
         'trakt_language': 'language',
         'trakt_genres': lambda i: [db_genre.name for db_genre in i.genres],
-        'trakt_images': lambda im: list_images(im.images),
-        'trakt_languages': lambda i: [t.name for t in i.translations]
+        'trakt_languages': 'translation_languages',
+        'trakt_fanart_full': 'image_fanart_full',
+        'trakt_fanart_medium': 'image_fanart_medium',
+        'trakt_fanart_thumb': 'image_fanart_thumb',
+        'trakt_poster_full': 'image_poster_full',
+        'trakt_poster_medium': 'image_poster_medium',
+        'trakt_poster_thumb': 'image_poster_thumb',
+        'trakt_logo': 'image_logo_full',
+        'trakt_clearart': 'image_clearart_full',
+        'trakt_banner': 'image_banner_full',
+        'trakt_thumb': 'image_thumb_full'
     }
 
     movie_translate_map = {
-        'trakt_translations': lambda movie: get_translations(movie.translate),
+        'trakt_translations': lambda movie: get_translations_dict(movie.translations, 'movie'),
     }
 
     movie_actor_map = {


Test patch: