Source code for flexget.plugins.input.apple_trailers

import re

import feedparser
from loguru import logger
from requests.auth import AuthBase

from flexget import plugin
from flexget.entry import Entry
from flexget.event import event
from flexget.utils.cached_input import cached
from flexget.utils.requests import RequestException

logger = logger.bind(name='apple_trailers')


[docs] class AppleTrailers: """Add support for Apple.com movie trailers. Configuration: quality Set the desired resolution - 480p, 720p or 1080p. default '720p' genres List of genres used to filter the entries. If set, the trailer must match at least one listed genre to be accepted. Genres that can be used: Action and Adventure, Comedy, Documentary, Drama, Family, Fantasy, Foreign, Horror, Musical, Romance, Science Fiction, Thriller. default '' (all) Example:: apple_trailers: quality: 720p genres: ['Action and Adventure'] Alternatively, a simpler configuration format can be used. This uses the default genre filter, all:: apple_trailers: 720p This plugin adds the following fields to the entry: ``movie_name``, ``movie_year``, ``genres``, ``apple_trailers_name``, ``movie_studio`` movie_name Name of the movie movie_year Year the movie was/will be released genres Comma-separated list of genres that apply to the movie apple_trailers_name Contains the Apple-supplied name of the clip, such as 'Clip 2', 'Trailer', 'Winter Olympic Preview' movie_studio Name of the studio that makes the movie """ movie_data_url = 'http://trailers.apple.com/trailers/feeds/data/' rss_url = 'http://trailers.apple.com/trailers/home/rss/newtrailers.rss' qualities = {'480p': 'sd', '720p': 'hd720', '1080p': 'hd1080'} schema = { 'oneOf': [ { 'type': 'object', 'properties': { 'quality': { 'type': 'string', 'enum': list(qualities.keys()), 'default': '720p', }, 'genres': {'type': 'array', 'items': {'type': 'string'}}, }, 'additionalProperties': False, }, {'title': 'justquality', 'type': 'string', 'enum': list(qualities.keys())}, ] }
[docs] def broken(self, error_message): raise plugin.PluginError(f'Plugin is most likely broken. Got: {error_message}')
@plugin.priority(127) @cached('apple_trailers') def on_task_input(self, task, config): # Turn simple config into full config if isinstance(config, str): config = {'quality': config} try: r = task.requests.get(self.rss_url) except RequestException as e: raise plugin.PluginError(f'Retrieving Apple Trailers RSS feed failed: {e}') rss = feedparser.parse(r.content) if rss.get('bozo_exception', False): raise plugin.PluginError('Got bozo_exception (bad feed)') filmid_regex = re.compile(r'(FilmId\s*\=\s*\')(\d+)(?=\')') studio_regex = re.compile(r'(?:[0-9]*\s*)(.+)') # use the following dict to save json object in case multiple trailers have been released for the same movie # no need to do multiple requests for the same thing! trailers = {} entries = [] for item in rss.entries: entry = Entry() movie_url = item['link'] entry['title'] = item['title'] entry['movie_name'], entry['apple_trailers_name'] = entry['title'].split(' - ', 1) if not trailers.get(movie_url): try: movie_page = task.requests.get(movie_url).text match = filmid_regex.search(movie_page) if match: json_url = self.movie_data_url + match.group(2) + '.json' movie_data = task.requests.get(json_url).json() trailers[movie_url] = {'json_url': json_url, 'json': movie_data} else: self.broken('FilmId not found for {}'.format(entry['movie_name'])) except RequestException as e: logger.error('Failed to get trailer {}: {}', entry['title'], e.args[0]) continue else: movie_data = trailers[movie_url]['json'] genres = {genre.get('name') for genre in movie_data.get('details').get('genres')} config_genres = set(config.get('genres', [])) if genres and config_genres and not set.intersection(config_genres, genres): logger.debug('Config genre(s) do not match movie genre(s)') continue desired_quality = config['quality'] # find the trailer url for clip in movie_data.get('clips'): if clip.get('title') == entry['apple_trailers_name']: try: trailer_url = clip['versions']['enus']['sizes'][ self.qualities[desired_quality] ] src = trailer_url.get('src') src_alt = trailer_url.get('srcAlt') # .mov tends to be a streaming video file, but the real video file is the same url, but # they prepend 'h' to the quality if src.split('.')[-1] == 'mov': entry['url'] = src.replace(desired_quality, 'h' + desired_quality) elif src_alt.split('.')[-1] == 'mov': entry['url'] = src_alt.replace(desired_quality, 'h' + desired_quality) else: continue # just continue until we reach the else part of the for-else break except KeyError as e: self.broken(e.args[0]) else: logger.error('Trailer "{}" not found', entry['apple_trailers_name']) continue # set some entry fields if present # studio is usually also the copyright holder studio = studio_regex.match(movie_data.get('page').get('copyright')) if studio: entry['movie_studio'] = studio.group(1) release_date = movie_data.get('page').get('release_date') if release_date: entry['release_date'] = release_date if genres: entry['genres'] = ', '.join(list(genres)) # set the correct header without modifying the task.requests obj entry['download_auth'] = AppleTrailersHeader() entries.append(entry) return entries
[docs] class AppleTrailersHeader(AuthBase): def __call__(self, request): request.headers['User-Agent'] = 'QuickTime/7.7' return request
[docs] @event('plugin.register') def register_plugin(): plugin.register(AppleTrailers, 'apple_trailers', api_ver=2)