Scenario4 inc

click-textual
Maveno 11 months ago
parent f007ada29f
commit 2abda01fe6

@ -440,10 +440,12 @@ def convert(ctx,
if context['use_tmdb']: if context['use_tmdb']:
click.echo(f"Querying TMDB for show_id={currentShowDescriptor.getId()} season={mediaFileProperties.getSeason()} episode{mediaFileProperties.getEpisode()}")
tmdbEpisodeResult = tc.queryEpisode(currentShowDescriptor.getId(), mediaFileProperties.getSeason(), mediaFileProperties.getEpisode()) tmdbEpisodeResult = tc.queryEpisode(currentShowDescriptor.getId(), mediaFileProperties.getSeason(), mediaFileProperties.getEpisode())
click.echo(f"tmdbEpisodeResult={tmdbEpisodeResult}")
if tmdbEpisodeResult: if tmdbEpisodeResult:
sourceFileBasename = tc.getEpisodeFileBasename(currentShowDescriptor.getFilenamePrefix(), sourceFileBasename = TmdbController.getEpisodeFileBasename(currentShowDescriptor.getFilenamePrefix(),
tmdbEpisodeResult['name'], tmdbEpisodeResult['name'],
mediaFileProperties.getSeason(), mediaFileProperties.getSeason(),
mediaFileProperties.getEpisode(), mediaFileProperties.getEpisode(),

@ -175,12 +175,12 @@ class FfxController():
# sourceTrackDescriptors = [] if self.__sourceMediaDescriptor is None else self.__sourceMediaDescriptor.getAllTrackDescriptors() # sourceTrackDescriptors = [] if self.__sourceMediaDescriptor is None else self.__sourceMediaDescriptor.getAllTrackDescriptors()
targetTrackDescriptors = self.__targetMediaDescriptor.getAllTrackDescriptors() targetTrackDescriptors = self.__targetMediaDescriptor.getAllTrackDescriptors()
dispositionTokens = [] dispositionTokens = []
for trackIndex in range(len(targetTrackDescriptors)): for trackIndex in range(len(targetTrackDescriptors)):
td = targetTrackDescriptors[trackIndex] td = targetTrackDescriptors[trackIndex]
#sd = sourceTrackDescriptors[trackIndex]
#HINT: No dispositions for pgs subtitle tracks that have no external file source #HINT: No dispositions for pgs subtitle tracks that have no external file source
if (td.getExternalSourceFilePath() if (td.getExternalSourceFilePath()

@ -43,24 +43,35 @@ class FileProperties():
self.__pc = PatternController(context) self.__pc = PatternController(context)
# db pattern boruto_[sS]([0-9]+)[eE]([0-9]+).mkv
# Checking if database contains matching pattern
matchResult = self.__pc.matchFilename(self.__sourceFilename) matchResult = self.__pc.matchFilename(self.__sourceFilename)
self.__logger.debug(f"FileProperties.__init__(): Match result {matchResult}")
self.__pattern: Pattern = matchResult['pattern'] if matchResult else None self.__pattern: Pattern = matchResult['pattern'] if matchResult else None
matchedGroups = matchResult['match'].groups() if matchResult else {} if matchResult:
seIndicator = matchedGroups[0] if matchedGroups else self.__sourceFilename databaseMatchedGroups = matchResult['match'].groups()
self.__season = databaseMatchedGroups[0]
se_match = re.search(FileProperties.SEASON_EPISODE_INDICATOR_MATCH, seIndicator) self.__episode = databaseMatchedGroups[1]
e_match = re.search(FileProperties.EPISODE_INDICATOR_MATCH, seIndicator)
else:
self.__season = -1 self.__logger.debug(f"FileProperties.__init__(): Checking file name for indicator {self.__sourceFilename}")
self.__episode = -1
se_match = re.search(FileProperties.SEASON_EPISODE_INDICATOR_MATCH, self.__sourceFilename)
if se_match is not None: e_match = re.search(FileProperties.EPISODE_INDICATOR_MATCH, self.__sourceFilename)
self.__season = int(se_match.group(1))
self.__episode = int(se_match.group(2)) if se_match is not None:
elif e_match is not None: self.__season = int(se_match.group(1))
self.__episode = int(e_match.group(1)) self.__episode = int(se_match.group(2))
elif e_match is not None:
self.__season = -1
self.__episode = int(e_match.group(1))
else:
self.__season = -1
self.__episode = -1
def getFormatData(self): def getFormatData(self):

@ -0,0 +1,47 @@
import click, re
from ffx.model.pattern import Pattern
from ffx.media_descriptor import MediaDescriptor
from ffx.tag_controller import TagController
from ffx.track_controller import TrackController
class MediaController():
def __init__(self, context):
self.context = context
self.Session = self.context['database']['session'] # convenience
self.__logger = context['logger']
self.__tc = TrackController(context = context)
self.__tac = TagController(context = context)
def setPatternMediaDescriptor(self, mediaDescriptor: MediaDescriptor, patternId: int):
try:
pid = int(patternId)
s = self.Session()
q = s.query(Pattern).filter(Pattern.id == pid)
if q.count():
pattern = q.first
for mediaTagKey, mediaTagValue in mediaDescriptor.getTags():
self.__tac.updateMediaTag(pid, mediaTagKey, mediaTagValue)
for trackDescriptor in mediaDescriptor.getAllTrackDescriptors():
self.__tc.addTrack(trackDescriptor, patternId = pid)
s.commit()
return True
else:
return False
except Exception as ex:
self.__logger.error(f"MediaController.setPatternMediaDescriptor(): {repr(ex)}")
raise click.ClickException(f"MediaController.setPatternMediaDescriptor(): {repr(ex)}")
finally:
s.close()

@ -501,3 +501,11 @@ class MediaDescriptor:
# click.echo(f"Found matching subtitle file {msfd["path"]}\n") # click.echo(f"Found matching subtitle file {msfd["path"]}\n")
self.__logger.debug(f"importSubtitles(): Found matching subtitle file {msfd["path"]}") self.__logger.debug(f"importSubtitles(): Found matching subtitle file {msfd["path"]}")
matchingSubtitleTrackDescriptor[0].setExternalSourceFilePath(msfd["path"]) matchingSubtitleTrackDescriptor[0].setExternalSourceFilePath(msfd["path"])
def getConfiguration(self, label: str = ''):
yield f"--- {label if label else 'MediaDescriptor '+str(id(self))} {' '.join([str(k)+'='+str(v) for k,v in self.__mediaTags.items()])}"
for td in self.getAllTrackDescriptors():
yield (f"{td.getIndex()}:{td.getType().indicator()}:{td.getSubIndex()} "
+ '|'.join([d.indicator() for d in td.getDispositionSet()])
+ ' ' + ' '.join([str(k)+'='+str(v) for k,v in td.getTags().items()]))

@ -504,14 +504,14 @@ class MediaDetailsScreen(Screen):
if patternDescriptor: if patternDescriptor:
patternId = self.__pc.addPattern(patternDescriptor) patternId = self.__pc.addPattern(patternDescriptor)
if patternId:
self.highlightPattern(False)
self.highlightPattern(False) for tagKey, tagValue in self.__currentMediaDescriptor.getTags().items():
self.__tac.updateMediaTag(patternId, tagKey, tagValue)
for tagKey, tagValue in self.__currentMediaDescriptor.getTags().items(): for trackDescriptor in self.__currentMediaDescriptor.getAllTrackDescriptors():
self.__tac.updateMediaTag(patternId, tagKey, tagValue) self.__tc.addTrack(trackDescriptor, patternId = patternId)
for trackDescriptor in self.__currentMediaDescriptor.getAllTrackDescriptors():
self.__tc.addTrack(trackDescriptor, patternId = patternId)
def action_new_pattern(self): def action_new_pattern(self):

@ -25,7 +25,7 @@ class PatternController():
s.commit() s.commit()
return pattern.getId() return pattern.getId()
else: else:
return None return 0
except Exception as ex: except Exception as ex:
raise click.ClickException(f"PatternController.addPattern(): {repr(ex)}") raise click.ClickException(f"PatternController.addPattern(): {repr(ex)}")
@ -116,7 +116,8 @@ class PatternController():
s.close() s.close()
def matchFilename(self, filename : str) -> re.Match: def matchFilename(self, filename : str) -> dict:
"""Returns dict {'match': <a regex match obj>, 'pattern': <ffx pattern obj>} or empty dict of no pattern was found"""
try: try:
s = self.Session() s = self.Session()
@ -126,7 +127,7 @@ class PatternController():
for pattern in q.all(): for pattern in q.all():
patternMatch = re.search(str(pattern.pattern), str(filename)) patternMatch = re.search(str(pattern.pattern), str(filename))
if patternMatch: if patternMatch is not None:
matchResult['match'] = patternMatch matchResult['match'] = patternMatch
matchResult['pattern'] = pattern matchResult['pattern'] = pattern

@ -345,8 +345,7 @@ class PatternDetailsScreen(Screen):
else: else:
patternId = self.__pc.addPattern(patternDescriptor) patternId = self.__pc.addPattern(patternDescriptor)
if patternId is not None: if patternId:
self.dismiss(patternDescriptor) self.dismiss(patternDescriptor)
else: else:
#TODO: Meldung #TODO: Meldung

@ -55,33 +55,34 @@ class ShowDescriptor():
else: else:
self.__showYear = -1 self.__showYear = -1
if ShowDescriptor.INDEX_SEASON_DIGITS_KEY in kwargs.keys(): if ShowDescriptor.INDEX_SEASON_DIGITS_KEY in kwargs.keys():
if type(kwargs[ShowDescriptor.INDEX_SEASON_DIGITS_KEY]) is not int: if type(kwargs[ShowDescriptor.INDEX_SEASON_DIGITS_KEY]) is not int:
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDEX_SEASON_DIGITS_KEY} is required to be of type int") raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDEX_SEASON_DIGITS_KEY} is required to be of type int")
self.__indexSeasonDigits = kwargs[ShowDescriptor.INDEX_SEASON_DIGITS_KEY] self.__indexSeasonDigits = kwargs[ShowDescriptor.INDEX_SEASON_DIGITS_KEY]
else: else:
self.__indexSeasonDigits = -1 self.__indexSeasonDigits = ShowDescriptor.DEFAULT_INDEX_SEASON_DIGITS
if ShowDescriptor.INDEX_EPISODE_DIGITS_KEY in kwargs.keys(): if ShowDescriptor.INDEX_EPISODE_DIGITS_KEY in kwargs.keys():
if type(kwargs[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY]) is not int: if type(kwargs[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY]) is not int:
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDEX_EPISODE_DIGITS_KEY} is required to be of type int") raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDEX_EPISODE_DIGITS_KEY} is required to be of type int")
self.__indexEpisodeDigits = kwargs[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY] self.__indexEpisodeDigits = kwargs[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY]
else: else:
self.__indexEpisodeDigits = -1 self.__indexEpisodeDigits = ShowDescriptor.DEFAULT_INDEX_EPISODE_DIGITS
if ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY in kwargs.keys(): if ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY in kwargs.keys():
if type(kwargs[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]) is not int: if type(kwargs[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]) is not int:
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY} is required to be of type int") raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY} is required to be of type int")
self.__indicatorSeasonDigits = kwargs[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY] self.__indicatorSeasonDigits = kwargs[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]
else: else:
self.__indicatorSeasonDigits = -1 self.__indicatorSeasonDigits = ShowDescriptor.DEFAULT_INDICATOR_SEASON_DIGITS
if ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY in kwargs.keys(): if ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY in kwargs.keys():
if type(kwargs[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]) is not int: if type(kwargs[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]) is not int:
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY} is required to be of type int") raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY} is required to be of type int")
self.__indicatorEpisodeDigits = kwargs[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY] self.__indicatorEpisodeDigits = kwargs[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]
else: else:
self.__indicatorEpisodeDigits = -1 self.__indicatorEpisodeDigits = ShowDescriptor.DEFAULT_INDICATOR_EPISODE_DIGITS
def getId(self): def getId(self):

@ -17,12 +17,16 @@ class IndicatorCombinator():
if season == -1 and episode == -1: if season == -1 and episode == -1:
return { return {
'variant': 'S00E00', 'variant': 'S00E00',
'indicator': '' 'indicator': '',
'season': season,
'episode': episode
} }
else: else:
return { return {
'variant': f"S{season+1:02d}E{episode+1:02d}", 'variant': f"S{season:02d}E{episode:02d}",
'indicator': f"S{season+1:02d}E{episode+1:02d}" 'indicator': f"S{season:02d}E{episode:02d}",
'season': season,
'episode': episode
} }
def assertFunc(self, testObj = {}): def assertFunc(self, testObj = {}):
@ -36,4 +40,4 @@ class IndicatorCombinator():
yield self.getPayload() yield self.getPayload()
for season in range(IndicatorCombinator.MAX_SEASON): for season in range(IndicatorCombinator.MAX_SEASON):
for episode in range(IndicatorCombinator.MAX_EPISODE): for episode in range(IndicatorCombinator.MAX_EPISODE):
yield self.getPayload(season, episode) yield self.getPayload(season+1, episode+1)

@ -2,7 +2,6 @@ import os, glob, sys, importlib, glob, inspect
from ffx.test.helper import createEmptyDirectory from ffx.test.helper import createEmptyDirectory
class Scenario(): class Scenario():
"""Scenarios """Scenarios

@ -91,8 +91,13 @@ class Scenario2(Scenario):
if rc: if rc:
self._logger.debug(f"{variantLabel}: Process returned ERROR {rc} ({err})") self._logger.debug(f"{variantLabel}: Process returned ERROR {rc} ({err})")
# Phase 4: Evaluate results # Phase 4: Evaluate results
resultFilenames = [rf for rf in self.getFilenamesInTestDirectory() if rf != 'ffmpeg2pass-0.log' and rf != variantFilename]
self._logger.debug(f"{variantLabel}: Result filenames: {resultFilenames}")
try: try:
jobFailed = bool(rc) jobFailed = bool(rc)

@ -4,6 +4,8 @@ from .scenario import Scenario
from ffx.test.helper import createMediaTestFile from ffx.test.helper import createMediaTestFile
from ffx.process import executeProcess from ffx.process import executeProcess
from ffx.database import databaseContext
from ffx.test.helper import createEmptyDirectory
from ffx.file_properties import FileProperties from ffx.file_properties import FileProperties
@ -14,18 +16,98 @@ from ffx.track_type import TrackType
from ffx.track_disposition import TrackDisposition from ffx.track_disposition import TrackDisposition
from ffx.test.media_combinator import MediaCombinator from ffx.test.media_combinator import MediaCombinator
from ffx.test.indicator_combinator import IndicatorCombinator
from ffx.show_descriptor import ShowDescriptor
from ffx.show_controller import ShowController
from ffx.pattern_controller import PatternController
from ffx.media_controller import MediaController
from ffx.tmdb_controller import TmdbController
from ffx.tmdb_controller import TMDB_API_KEY_NOT_PRESENT_EXCEPTION
class Scenario4(Scenario): class Scenario4(Scenario):
TEST_SHOW_IDENTIFIER = 83095
TEST_SHOW_NAME = 'The Rising of the Shield Hero'
TEST_SHOW_YEAR = 2019
TEST_FILE_LABEL = 'rotsh'
TEST_FILE_EXTENSION = 'mkv'
TEST_PATTERN = f"{TEST_FILE_LABEL}_{FileProperties.SEASON_EPISODE_INDICATOR_MATCH}.{TEST_FILE_EXTENSION}"
EXPECTED_FILE_EXTENSION = 'webm'
def __init__(self, context): def __init__(self, context):
super().__init__(context) super().__init__(context)
self.__tmdbApiKey = os.environ.get('TMDB_API_KEY', None)
if self.__tmdbApiKey is None:
raise TMDB_API_KEY_NOT_PRESENT_EXCEPTION
self.__testDbFilePath = os.path.join(self._testDirectory, 'test.db')
self.createEmptyTestDatabase()
self.__ic = IndicatorCombinator(context = context)
self.__sc = ShowController(context = context)
self.__pc = PatternController(context = context)
self.__mc = MediaController(context = context)
self.__tc = TmdbController()
def getScenario(self): def getScenario(self):
return self.__class__.__name__[8:] return self.__class__.__name__[8:]
def createEmptyTestDatabase(self):
if not self._context['database'] is None:
self._context['database']['engine'].dispose()
if os.path.isfile(self.__testDbFilePath):
os.unlink(self.__testDbFilePath)
self._context['database'] = None
self._logger.debug(f"Creating test db with path {self.__testDbFilePath}")
self._context['database'] = databaseContext(databasePath=self.__testDbFilePath)
def prepareTestDatabase(self, sourceMediaDescriptor: MediaDescriptor):
if not self._context['database'] is None:
self._context['database']['engine'].dispose()
if os.path.isfile(self.__testDbFilePath):
os.unlink(self.__testDbFilePath)
self._context['database'] = None
self._logger.debug(f"Creating test db with path {self.__testDbFilePath}")
self._context['database'] = databaseContext(databasePath=self.__testDbFilePath)
kwargs = {}
kwargs[ShowDescriptor.ID_KEY] = Scenario4.TEST_SHOW_IDENTIFIER
kwargs[ShowDescriptor.NAME_KEY] = Scenario4.TEST_SHOW_NAME
kwargs[ShowDescriptor.YEAR_KEY] = Scenario4.TEST_SHOW_YEAR
self.__testShowDescriptor = ShowDescriptor(**kwargs)
self._logger.debug(f"Adding test show '{self.__testShowDescriptor.getFilenamePrefix()}' to test db")
if not self.__sc.updateShow(self.__testShowDescriptor):
raise click.ClickException('Could not create test show in db')
testPatternDescriptor = {
'show_id': Scenario4.TEST_SHOW_IDENTIFIER,
'pattern': Scenario4.TEST_PATTERN
}
patternId = self.__pc.addPattern(testPatternDescriptor)
if patternId:
self.__mc.setPatternMediaDescriptor(sourceMediaDescriptor, patternId)
def job(self, yieldObj: dict): def job(self, yieldObj: dict):
@ -44,11 +126,11 @@ class Scenario4(Scenario):
variantLabel = f"{self.__class__.__name__} Variant {variantIdentifier}" variantLabel = f"{self.__class__.__name__} Variant {variantIdentifier}"
sourceMediaDescriptor: MediaDescriptor = targetYieldObj['payload'] sourceMediaDescriptor: MediaDescriptor = targetYieldObj['payload']
#presetMediaDescriptor: MediaDescriptor = targetYieldObj['payload']['preset'] presetMediaDescriptor: MediaDescriptor = presetYieldObj['payload']
assertSelectorList: list = targetYieldObj['assertSelectors'] assertSelectorList: list = presetYieldObj['assertSelectors']
assertFuncList = targetYieldObj['assertFuncs'] assertFuncList = presetYieldObj['assertFuncs']
shouldFail = targetYieldObj['shouldFail'] shouldFail = presetYieldObj['shouldFail']
try: try:
jellyfinSelectorIndex = assertSelectorList.index('J') jellyfinSelectorIndex = assertSelectorList.index('J')
@ -61,63 +143,113 @@ class Scenario4(Scenario):
if self._context['test_variant'] and variantIdentifier != self._context['test_variant']: if self._context['test_variant'] and variantIdentifier != self._context['test_variant']:
return return
for l in sourceMediaDescriptor.getConfiguration(label = 'sourceMediaDescriptor'):
self._logger.debug(l)
for l in presetMediaDescriptor.getConfiguration(label = 'presetMediaDescriptor'):
self._logger.debug(l)
self._logger.debug(f"Running Job: {variantLabel}") self._logger.debug(f"Running Job: {variantLabel}")
# Phase 1: Setup source files # Phase 1: Setup source files
self.clearTestDirectory() self.clearTestDirectory()
mediaFilePath = createMediaTestFile(mediaDescriptor=sourceMediaDescriptor, directory=self._testDirectory, logger=self._logger, length = 2) self.createEmptyTestDatabase()
self.prepareTestDatabase(sourceMediaDescriptor)
testFileList = []
for indicatorObj in [y for y in self.__ic.getYield() if y['indicator']]:
indicator = indicatorObj['indicator']
testFileObj = {}
testFileObj['season'] = indicatorObj['season']
testFileObj['episode'] = indicatorObj['episode']
testFileObj['basename'] = f"{Scenario4.TEST_FILE_LABEL}_{indicator}"
testFileObj['path'] = createMediaTestFile(mediaDescriptor = presetMediaDescriptor,
directory = self._testDirectory,
baseName = testFileObj['basename'],
logger=self._logger,
length = 2)
testFileObj['filename'] = f"{testFileObj['basename']}.{Scenario4.TEST_FILE_EXTENSION}"
testFileList.append(testFileObj)
# Phase 2: Prepare database
# # Phase 2: Prepare database
#
# Phase 3: Run ffx # Phase 3: Run ffx
commandSequence = [sys.executable, commandSequence = [sys.executable,
self._ffxExecutablePath, self._ffxExecutablePath,
'convert', '--database-file',
mediaFilePath, self.__testDbFilePath,
'--no-prompt'] 'convert']
commandSequence += [tfo['filename'] for tfo in testFileList]
commandSequence += ['--no-prompt']
if not testContext['use_jellyfin']: # if not testContext['use_jellyfin']:
commandSequence += ['--no-jellyfin'] # commandSequence += ['--no-jellyfin']
self._logger.debug(f"{variantLabel}: Test sequence: {commandSequence}") self._logger.debug(f"{variantLabel}: Test sequence: {commandSequence}")
out, err, rc = executeProcess(commandSequence, directory = self._testDirectory) out, err, rc = executeProcess(commandSequence, directory = self._testDirectory)
if out: # if out:
self._logger.debug(f"{variantLabel}: Process output: {out}") # self._logger.debug(f"{variantLabel}: Process output: {out}")
if rc: if rc:
self._logger.debug(f"{variantLabel}: Process returned ERROR {rc} ({err})") self._logger.debug(f"{variantLabel}: Process returned ERROR {rc} ({err})")
# Phase 4: Evaluate results # Phase 4: Evaluate results
resultFilenames = [rf for rf in self.getFilenamesInTestDirectory() if rf.endswith(f".{Scenario4.EXPECTED_FILE_EXTENSION}")]
self._logger.debug(f"{variantLabel}: Result filenames: {resultFilenames}")
try: try:
jobFailed = bool(rc) assert not (bool(rc)
), f"Process failed"
for tfo in testFileList:
self._logger.debug(f"{variantLabel}: Should fail: {shouldFail} / actually failed: {jobFailed}") tmdbEpisodeResult = self.__tc.queryEpisode(Scenario4.TEST_SHOW_IDENTIFIER,
tfo['season'], tfo['episode'])
assert (jobFailed == shouldFail expectedFileBasename = TmdbController.getEpisodeFileBasename(self.__testShowDescriptor.getFilenamePrefix(),
), f"Process {'failed' if jobFailed else 'did not fail'}" tmdbEpisodeResult['name'],
tfo['season'], tfo['episode'])
if not jobFailed: expectedFilename = f"{expectedFileBasename}.{Scenario4.EXPECTED_FILE_EXTENSION}"
expectedFilePath = os.path.join(self._testDirectory, expectedFilename)
resultFile = os.path.join(self._testDirectory, 'media.webm') assert (os.path.isfile(expectedFilePath)
), f"Result file '{expectedFilename}' in path '{self._testDirectory}' wasn't created"
assert (os.path.isfile(resultFile) ###
), f"Result file 'media.webm' in path '{self._testDirectory}' wasn't created" #
resultFileProperties = FileProperties(testContext, resultFile) rfp = FileProperties(testContext, expectedFilePath)
resultMediaDescriptor = resultFileProperties.getMediaDescriptor() self._logger.debug(f"{variantLabel}: Result file properties: {rfp.getFilename()} season={rfp.getSeason()} episode={rfp.getEpisode()}")
rmd = rfp.getMediaDescriptor()
rmt = rmd.getAllTrackDescriptors()
for l in rmd.getConfiguration(label = 'resultMediaDescriptor'):
self._logger.debug(l)
if testContext['use_jellyfin']: if testContext['use_jellyfin']:
sourceMediaDescriptor.applyJellyfinOrder() sourceMediaDescriptor.applyJellyfinOrder()
resultMediaDescriptor.applySourceIndices(sourceMediaDescriptor)
resultMediaTracks = resultMediaDescriptor.getAllTrackDescriptors() # num tracks differ
rmd.applySourceIndices(sourceMediaDescriptor)
for assertIndex in range(len(assertSelectorList)): for assertIndex in range(len(assertSelectorList)):
@ -125,17 +257,17 @@ class Scenario4(Scenario):
assertFunc = assertFuncList[assertIndex] assertFunc = assertFuncList[assertIndex]
assertVariant = variantList[assertIndex] assertVariant = variantList[assertIndex]
if assertSelector == 'M': # if assertSelector == 'M':
assertFunc() # assertFunc()
for variantIndex in range(len(assertVariant)): # for variantIndex in range(len(assertVariant)):
assert (assertVariant[variantIndex].lower() == resultMediaTracks[variantIndex].getType().indicator() # assert (assertVariant[variantIndex].lower() == rmd.getType().indicator()
), f"Stream #{variantIndex} is not of type {resultMediaTracks[variantIndex].getType().label()}" # ), f"Stream #{variantIndex} is not of type {rmd.getType().label()}"
#
elif assertSelector == 'AD' or assertSelector == 'AT': if assertSelector == 'AD' or assertSelector == 'AT':
assertFunc({'tracks': resultMediaDescriptor.getAudioTracks()}) assertFunc({'tracks': rmd.getAudioTracks()})
elif assertSelector == 'SD' or assertSelector == 'ST': elif assertSelector == 'SD' or assertSelector == 'ST':
assertFunc({'tracks': resultMediaDescriptor.getSubtitleTracks()}) assertFunc({'tracks': rmd.getSubtitleTracks()})
elif type(assertSelector) is str: elif type(assertSelector) is str:
if assertSelector == 'J': if assertSelector == 'J':
@ -148,9 +280,13 @@ class Scenario4(Scenario):
self._reportLogger.error(f"{variantLabel}: Test FAILED ({ae})") self._reportLogger.error(f"{variantLabel}: Test FAILED ({ae})")
exit()
def run(self): def run(self):
MC_list = MediaCombinator.getAllClassReferences()
MC_list = [MediaCombinator.getClassReference(6)]
for MC in MC_list: for MC in MC_list:
self._logger.debug(f"MC={MC.__name__}") self._logger.debug(f"MC={MC.__name__}")
mc = MC(context = self._context, createPresets = True) mc = MC(context = self._context, createPresets = True)

@ -1,19 +1,56 @@
import os, click, requests, json import os, click, requests, json, time, logging
class TMDB_REQUEST_EXCEPTION(Exception):
def __init__(self, statusCode, statusMessage):
errorMessage = f"TMDB query failed with status code {statusCode}: {statusMessage}"
super().__init__(errorMessage)
class TMDB_API_KEY_NOT_PRESENT_EXCEPTION(Exception):
def __str__(self):
return 'TMDB api key is not available, please set environment variable TMDB_API_KEY'
class TMDB_EXCESSIVE_USAGE_EXCEPTION(Exception):
def __str__(self):
return 'Rate limit was triggered too often'
class TmdbController(): class TmdbController():
DEFAULT_LANGUAGE = 'de-DE' DEFAULT_LANGUAGE = 'de-DE'
def __init__(self): RATE_LIMIT_WAIT_SECONDS = 10
RATE_LIMIT_RETRIES = 3
try:
self.__tmdbApiKey = os.environ['TMDB_API_KEY']
except KeyError:
raise click.ClickException('TMDB api key is not available, please set environment variable TMDB_API_KEY')
def __init__(self, context = None):
self.__context = context
self.__logger = (context['logger'] if context is not None and 'logger' in context.keys()
else logging.getLogger('FFX').addHandler(logging.NullHandler()))
self.__tmdbApiKey = os.environ.get('TMDB_API_KEY', None)
if self.__tmdbApiKey is None:
raise TMDB_API_KEY_NOT_PRESENT_EXCEPTION
self.tmdbLanguage = TmdbController.DEFAULT_LANGUAGE self.tmdbLanguage = TmdbController.DEFAULT_LANGUAGE
def getTmdbRequest(self, tmdbUrl):
retries = TmdbController.RATE_LIMIT_RETRIES
while True:
response = requests.get(tmdbUrl)
if response.status_code == 429:
if not retries:
raise TMDB_EXCESSIVE_USAGE_EXCEPTION()
self.__logger.warning('TMDB Rate limit (status_code 429)')
time.sleep(TmdbController.RATE_LIMIT_WAIT_SECONDS)
retries -= 1
else:
jsonResult = response.json()
if ('success' in jsonResult.keys()
and not jsonResult['success']):
raise TMDB_REQUEST_EXCEPTION(jsonResult['status_code'], jsonResult['status_message'])
return jsonResult
def queryShow(self, showId): def queryShow(self, showId):
""" """
First level keys in the response object: First level keys in the response object:
@ -55,22 +92,8 @@ class TmdbController():
tmdbUrl = f"https://api.themoviedb.org/3/tv/{showId}{urlParams}" tmdbUrl = f"https://api.themoviedb.org/3/tv/{showId}{urlParams}"
#TODO Check for result return self.getTmdbRequest(tmdbUrl)
try:
#TODO: Content Type aware processing
# response = requests.get(tmdbUrl)
# response.encoding = 'utf-8'
# return response.json()
# response = requests.get(tmdbUrl)
# contentType = response.headers.get('Content-Type')
# print(content_type) # Example: 'application/json; charset=UTF-8'
# decoded_content = response.content.decode('utf-8')
# return json.loads(decoded_content)
return requests.get(tmdbUrl).json()
except:
return {}
def queryEpisode(self, showId, season, episode): def queryEpisode(self, showId, season, episode):
""" """
@ -94,21 +117,18 @@ class TmdbController():
tmdbUrl = f"https://api.themoviedb.org/3/tv/{showId}/season/{season}/episode/{episode}{urlParams}" tmdbUrl = f"https://api.themoviedb.org/3/tv/{showId}/season/{season}/episode/{episode}{urlParams}"
#TODO Check for result return self.getTmdbRequest(tmdbUrl)
try:
return requests.get(tmdbUrl).json()
except: @staticmethod
return {} def getEpisodeFileBasename(showName,
episodeName,
def getEpisodeFileBasename(self, season,
showName, episode,
episodeName, indexSeasonDigits = 2,
season, indexEpisodeDigits = 2,
episode, indicatorSeasonDigits = 2,
indexSeasonDigits = 2, indicatorEpisodeDigits = 2):
indexEpisodeDigits = 2,
indicatorSeasonDigits = 2,
indicatorEpisodeDigits = 2):
""" """
One Piece: One Piece:
indexSeasonDigits = 0, indexSeasonDigits = 0,

@ -5,25 +5,25 @@ from enum import Enum
class TrackDisposition(Enum): class TrackDisposition(Enum):
DEFAULT = {"name": "default", "index": 0} DEFAULT = {"name": "default", "index": 0, "indicator": "DE"}
FORCED = {"name": "forced", "index": 1} FORCED = {"name": "forced", "index": 1, "indicator": "FO"}
DUB = {"name": "dub", "index": 2} DUB = {"name": "dub", "index": 2, "indicator": "DB"}
ORIGINAL = {"name": "original", "index": 3} ORIGINAL = {"name": "original", "index": 3, "indicator": "OG"}
COMMENT = {"name": "comment", "index": 4} COMMENT = {"name": "comment", "index": 4, "indicator": "CM"}
LYRICS = {"name": "lyrics", "index": 5} LYRICS = {"name": "lyrics", "index": 5, "indicator": "LY"}
KARAOKE = {"name": "karaoke", "index": 6} KARAOKE = {"name": "karaoke", "index": 6, "indicator": "KA"}
HEARING_IMPAIRED = {"name": "hearing_impaired", "index": 7} HEARING_IMPAIRED = {"name": "hearing_impaired", "index": 7, "indicator": "HI"}
VISUAL_IMPAIRED = {"name": "visual_impaired", "index": 8} VISUAL_IMPAIRED = {"name": "visual_impaired", "index": 8, "indicator": "VI"}
CLEAN_EFFECTS = {"name": "clean_effects", "index": 9} CLEAN_EFFECTS = {"name": "clean_effects", "index": 9, "indicator": "CE"}
ATTACHED_PIC = {"name": "attached_pic", "index": 10} ATTACHED_PIC = {"name": "attached_pic", "index": 10, "indicator": "AP"}
TIMED_THUMBNAILS = {"name": "timed_thumbnails", "index": 11} TIMED_THUMBNAILS = {"name": "timed_thumbnails", "index": 11, "indicator": "TT"}
NON_DIEGETICS = {"name": "non_diegetic", "index": 12} NON_DIEGETICS = {"name": "non_diegetic", "index": 12, "indicator": "ND"}
CAPTIONS = {"name": "captions", "index": 13} CAPTIONS = {"name": "captions", "index": 13, "indicator": "CA"}
DESCRIPTIONS = {"name": "descriptions", "index": 14} DESCRIPTIONS = {"name": "descriptions", "index": 14, "indicator": "DS"}
METADATA = {"name": "metadata", "index": 15} METADATA = {"name": "metadata", "index": 15, "indicator": "MD"}
DEPENDENT = {"name": "dependent", "index": 16} DEPENDENT = {"name": "dependent", "index": 16, "indicator": "DP"}
STILL_IMAGE = {"name": "still_image", "index": 17} STILL_IMAGE = {"name": "still_image", "index": 17, "indicator": "SI"}
def label(self): def label(self):
@ -32,6 +32,9 @@ class TrackDisposition(Enum):
def index(self): def index(self):
return int(self.value['index']) return int(self.value['index'])
def indicator(self):
return str(self.value['indicator'])
@staticmethod @staticmethod
def toFlags(dispositionSet): def toFlags(dispositionSet):

@ -10,6 +10,7 @@ from ffx.database import databaseContext
from ffx.test.helper import createMediaTestFile from ffx.test.helper import createMediaTestFile
from ffx.test.scenario import Scenario from ffx.test.scenario import Scenario
from ffx.tmdb_controller import TMDB_API_KEY_NOT_PRESENT_EXCEPTION
@click.group() @click.group()
@ -20,7 +21,7 @@ def ffx(ctx, verbose, dry_run):
"""FFX""" """FFX"""
ctx.obj = {} ctx.obj = {}
ctx.obj['database'] = databaseContext(databasePath=None) ctx.obj['database'] = None
ctx.obj['dry_run'] = dry_run ctx.obj['dry_run'] = dry_run
ctx.obj['verbosity'] = verbose ctx.obj['verbosity'] = verbose
@ -85,14 +86,19 @@ def run(ctx, scenario, variant):
for si in Scenario.list(): for si in Scenario.list():
scen = Scenario.getClassReference(si)(ctx.obj) try:
SCEN = Scenario.getClassReference(si)
scen = SCEN(ctx.obj)
if scenario and scenario != scen.getScenario(): if scenario and scenario != scen.getScenario():
continue continue
ctx.obj['logger'].debug(f"Running scenario {si}") ctx.obj['logger'].debug(f"Running scenario {si}")
scen.run() scen.run()
except TMDB_API_KEY_NOT_PRESENT_EXCEPTION:
ctx.obj['logger'].info(f"TMDB_API_KEY not set: Skipping {SCEN.__class__.__name__}")
@ffx.command() @ffx.command()

Loading…
Cancel
Save