def test_scan_archive_invalid_extension(movies, tmpdir, monkeypatch): monkeypatch.chdir(str(tmpdir)) movie_name = os.path.splitext(movies['interstellar'].name)[0] + '.mp3' tmpdir.ensure(movie_name) with pytest.raises(ValueError) as excinfo: scan_archive(movie_name) assert str(excinfo.value) == '\'.mp3\' is not a valid archive extension'
def test_scan_archive(movies, tmpdir, monkeypatch): video = movies['enders_game'] enders_game = tmpdir.ensure(os.path.splitext(video.name)[0] + '.rar') monkeypatch.setattr('rarfile.RarFile._parse', Mock()) monkeypatch.setattr('rarfile.RarFile.namelist', Mock(return_value=[video.name, 'anotherfile.nfo'])) monkeypatch.setattr('rarfile.RarFile.getinfo', Mock(return_value=Mock(file_size=0))) scanned_video = scan_archive(str(enders_game)) assert type(scanned_video) is Movie assert scanned_video.name == os.path.join(str(tmpdir), video.name) assert scanned_video.format == video.format assert scanned_video.release_group == video.release_group assert scanned_video.resolution == video.resolution assert scanned_video.video_codec == video.video_codec assert scanned_video.audio_codec == video.audio_codec assert scanned_video.imdb_id == video.imdb_id assert scanned_video.hashes == {} assert scanned_video.size == 0 assert scanned_video.subtitle_languages == set() assert scanned_video.title == 'enders game' assert scanned_video.year == 2013
def on_task_output(self, task, config): """ Configuration:: subliminal: languages: List of languages (as IETF codes) in order of preference. At least one is required. alternatives: List of second-choice languages; subs will be downloaded but entries rejected. exact_match: Use file hash only to search for subs, otherwise Subliminal will try to guess by filename. providers: List of providers from where to download subtitles. single: Download subtitles in single mode (no language code added to subtitle filename). directory: Path to directory where to save the subtitles, default is next to the video. hearing_impaired: Prefer subtitles for the hearing impaired when available authentication: > Dictionary of configuration options for different providers. Keys correspond to provider names, and values are dictionaries, usually specifying `username` and `password`. """ if not task.accepted: logger.debug('nothing accepted, aborting') return import subliminal from babelfish import Language from dogpile.cache.exception import RegionAlreadyConfigured from subliminal import save_subtitles, scan_video from subliminal.cli import MutexLock from subliminal.core import ( ARCHIVE_EXTENSIONS, refine, scan_archive, search_external_subtitles, ) from subliminal.score import episode_scores, movie_scores from subliminal.video import VIDEO_EXTENSIONS try: subliminal.region.configure( 'dogpile.cache.dbm', arguments={ 'filename': os.path.join(tempfile.gettempdir(), 'cachefile.dbm'), 'lock_factory': MutexLock, }, ) except RegionAlreadyConfigured: pass # Let subliminal be more verbose if our logger is set to DEBUG if logger.level(task.manager.options.loglevel).no <= logger.level('DEBUG').no: logging.getLogger("subliminal").setLevel(logging.INFO) else: logging.getLogger("subliminal").setLevel(logging.CRITICAL) logging.getLogger("dogpile").setLevel(logging.CRITICAL) logging.getLogger("enzyme").setLevel(logging.WARNING) try: languages = set([Language.fromietf(s) for s in config.get('languages', [])]) alternative_languages = set( [Language.fromietf(s) for s in config.get('alternatives', [])] ) except ValueError as e: raise plugin.PluginError(e) # keep all downloaded subtitles and save to disk when done (no need to write every time) downloaded_subtitles = collections.defaultdict(list) providers_list = config.get('providers', None) provider_configs = config.get('authentication', None) # test if only one language was provided, if so we will download in single mode # (aka no language code added to subtitle filename) # unless we are forced not to by configuration # if we pass 'yes' for single in configuration but choose more than one language # we ignore the configuration and add the language code to the # potentially downloaded files single_mode = config.get('single', '') and len(languages | alternative_languages) <= 1 hearing_impaired = config.get('hearing_impaired', False) with subliminal.core.ProviderPool( providers=providers_list, provider_configs=provider_configs ) as provider_pool: for entry in task.accepted: if 'location' not in entry: logger.warning('Cannot act on entries that do not represent a local file.') continue if not os.path.exists(entry['location']): entry.fail('file not found: %s' % entry['location']) continue if '$RECYCLE.BIN' in entry['location']: # ignore deleted files in Windows shares continue try: entry_languages = set(entry.get('subtitle_languages', [])) or languages if entry['location'].endswith(VIDEO_EXTENSIONS): video = scan_video(entry['location']) elif entry['location'].endswith(ARCHIVE_EXTENSIONS): video = scan_archive(entry['location']) else: entry.reject( 'File extension is not a supported video or archive extension' ) continue # use metadata refiner to get mkv metadata refiner = ('metadata',) refine(video, episode_refiners=refiner, movie_refiners=refiner) existing_subtitles = set(search_external_subtitles(entry['location']).values()) video.subtitle_languages |= existing_subtitles if isinstance(video, subliminal.Episode): title = video.series hash_scores = episode_scores['hash'] else: title = video.title hash_scores = movie_scores['hash'] logger.info('Name computed for {} was {}', entry['location'], title) msc = hash_scores if config['exact_match'] else 0 if entry_languages.issubset(video.subtitle_languages): logger.debug( 'All preferred languages already exist for "{}"', entry['title'] ) entry['subtitles_missing'] = set() continue # subs for preferred lang(s) already exists else: # Gather the subtitles for the alternative languages too, to avoid needing to search the sites # again. They'll just be ignored if the main languages are found. all_subtitles = provider_pool.list_subtitles( video, entry_languages | alternative_languages ) try: subtitles = provider_pool.download_best_subtitles( all_subtitles, video, entry_languages, min_score=msc, hearing_impaired=hearing_impaired, ) except TypeError as e: logger.error( 'Downloading subtitles failed due to a bug in subliminal. Please seehttps://github.com/Diaoul/subliminal/issues/921. Error: {}', e, ) subtitles = [] if subtitles: downloaded_subtitles[video].extend(subtitles) logger.info('Subtitles found for {}', entry['location']) else: # only try to download for alternatives that aren't already downloaded subtitles = provider_pool.download_best_subtitles( all_subtitles, video, alternative_languages, min_score=msc, hearing_impaired=hearing_impaired, ) if subtitles: downloaded_subtitles[video].extend(subtitles) entry.reject('subtitles found for a second-choice language.') else: entry.reject('cannot find any subtitles for now.') downloaded_languages = set( [Language.fromietf(str(l.language)) for l in subtitles] ) if entry_languages: entry['subtitles_missing'] = entry_languages - downloaded_languages if len(entry['subtitles_missing']) > 0: entry.reject('Subtitles for all primary languages not found') except ValueError as e: logger.error('subliminal error: {}', e) entry.fail() if downloaded_subtitles: if task.options.test: logger.verbose('Test mode. Found subtitles:') # save subtitles to disk for video, subtitle in downloaded_subtitles.items(): if subtitle: _directory = config.get('directory') if _directory: _directory = os.path.expanduser(_directory) if task.options.test: logger.verbose( ' FOUND LANGUAGES {} for {}', [str(l.language) for l in subtitle], video.name, ) continue save_subtitles(video, subtitle, single=single_mode, directory=_directory)
def test_scan_password_protected_archive(rar): with pytest.raises(ValueError) as excinfo: scan_archive(rar['pwd-protected']) assert excinfo.value.args == ('Rar requires a password', )
def test_scan_bad_archive(mkv): with pytest.raises(ValueError) as excinfo: scan_archive(mkv['test1']) assert excinfo.value.args == ("'.mkv' is not a valid archive", )
def test_scan_archive_with_no_video(rar): with pytest.raises(ValueError) as excinfo: scan_archive(rar['simple']) assert excinfo.value.args == ('No video in archive', )
def test_scan_archive_with_multiple_videos(rar, mkv): rar_file = rar['videos'] actual = scan_archive(rar_file) assert actual.name == os.path.join(os.path.split(rar_file)[0], mkv['test5'])
def test_scan_archive_with_one_video(rar, mkv): rar_file = rar['video'] actual = scan_archive(rar_file) assert actual.name == os.path.join(os.path.split(rar_file)[0], mkv['test1'])
def on_task_output(self, task, config): """ Configuration:: subliminal: languages: List of languages (as IETF codes) in order of preference. At least one is required. alternatives: List of second-choice languages; subs will be downloaded but entries rejected. exact_match: Use file hash only to search for subs, otherwise Subliminal will try to guess by filename. providers: List of providers from where to download subtitles. single: Download subtitles in single mode (no language code added to subtitle filename). directory: Path to directory where to save the subtitles, default is next to the video. hearing_impaired: Prefer subtitles for the hearing impaired when available authentication: > Dictionary of configuration options for different providers. Keys correspond to provider names, and values are dictionaries, usually specifying `username` and `password`. """ if not task.accepted: log.debug('nothing accepted, aborting') return from babelfish import Language from dogpile.cache.exception import RegionAlreadyConfigured import subliminal from subliminal import scan_video, save_subtitles from subliminal.cli import MutexLock from subliminal.core import ARCHIVE_EXTENSIONS, scan_archive, refine, search_external_subtitles from subliminal.score import episode_scores, movie_scores from subliminal.video import VIDEO_EXTENSIONS try: subliminal.region.configure('dogpile.cache.dbm', arguments={ 'filename': os.path.join(tempfile.gettempdir(), 'cachefile.dbm'), 'lock_factory': MutexLock, }) except RegionAlreadyConfigured: pass # Let subliminal be more verbose if our logger is set to DEBUG if log.isEnabledFor(logging.DEBUG): logging.getLogger("subliminal").setLevel(logging.INFO) else: logging.getLogger("subliminal").setLevel(logging.CRITICAL) logging.getLogger("dogpile").setLevel(logging.CRITICAL) logging.getLogger("enzyme").setLevel(logging.WARNING) try: languages = set([Language.fromietf(s) for s in config.get('languages', [])]) alternative_languages = set([Language.fromietf(s) for s in config.get('alternatives', [])]) except ValueError as e: raise plugin.PluginError(e) # keep all downloaded subtitles and save to disk when done (no need to write every time) downloaded_subtitles = collections.defaultdict(list) providers_list = config.get('providers', None) provider_configs = config.get('authentication', None) # test if only one language was provided, if so we will download in single mode # (aka no language code added to subtitle filename) # unless we are forced not to by configuration # if we pass 'yes' for single in configuration but choose more than one language # we ignore the configuration and add the language code to the # potentially downloaded files single_mode = config.get('single', '') and len(languages | alternative_languages) <= 1 hearing_impaired = config.get('hearing_impaired', False) with subliminal.core.ProviderPool(providers=providers_list, provider_configs=provider_configs) as provider_pool: for entry in task.accepted: if 'location' not in entry: log.warning('Cannot act on entries that do not represent a local file.') continue if not os.path.exists(entry['location']): entry.fail('file not found: %s' % entry['location']) continue if '$RECYCLE.BIN' in entry['location']: # ignore deleted files in Windows shares continue try: entry_languages = set(entry.get('subtitle_languages', [])) or languages if entry['location'].endswith(VIDEO_EXTENSIONS): video = scan_video(entry['location']) elif entry['location'].endswith(ARCHIVE_EXTENSIONS): video = scan_archive(entry['location']) else: entry.reject('File extension is not a supported video or archive extension') continue # use metadata refiner to get mkv metadata refiner = ('metadata',) refine(video, episode_refiners=refiner, movie_refiners=refiner) existing_subtitles = set(search_external_subtitles(entry['location']).values()) video.subtitle_languages |= existing_subtitles if isinstance(video, subliminal.Episode): title = video.series hash_scores = episode_scores['hash'] else: title = video.title hash_scores = movie_scores['hash'] log.info('Name computed for %s was %s', entry['location'], title) msc = hash_scores if config['exact_match'] else 0 if entry_languages.issubset(video.subtitle_languages): log.debug('All preferred languages already exist for "%s"', entry['title']) entry['subtitles_missing'] = set() continue # subs for preferred lang(s) already exists else: # Gather the subtitles for the alternative languages too, to avoid needing to search the sites # again. They'll just be ignored if the main languages are found. all_subtitles = provider_pool.list_subtitles(video, entry_languages | alternative_languages) subtitles = provider_pool.download_best_subtitles(all_subtitles, video, entry_languages, min_score=msc, hearing_impaired=hearing_impaired) if subtitles: downloaded_subtitles[video].extend(subtitles) log.info('Subtitles found for %s', entry['location']) else: # only try to download for alternatives that aren't alread downloaded subtitles = provider_pool.download_best_subtitles(all_subtitles, video, alternative_languages, min_score=msc, hearing_impaired=hearing_impaired) if subtitles: downloaded_subtitles[video].extend(subtitles) entry.reject('subtitles found for a second-choice language.') else: entry.reject('cannot find any subtitles for now.') downloaded_languages = set([Language.fromietf(str(l.language)) for l in subtitles]) if entry_languages: entry['subtitles_missing'] = entry_languages - downloaded_languages if len(entry['subtitles_missing']) > 0: entry.reject('Subtitles for all primary languages not found') except ValueError as e: log.error('subliminal error: %s', e) entry.fail() if downloaded_subtitles: if task.options.test: log.verbose('Test mode. Found subtitles:') # save subtitles to disk for video, subtitle in downloaded_subtitles.items(): if subtitle: _directory = config.get('directory') if _directory: _directory = os.path.expanduser(_directory) if task.options.test: log.verbose(' FOUND LANGUAGES %s for %s', [str(l.language) for l in subtitle], video.name) continue save_subtitles(video, subtitle, single=single_mode, directory=_directory)