diff --git a/bin/ffx.py b/bin/ffx.py index 6ed92b6..596eba4 100755 --- a/bin/ffx.py +++ b/bin/ffx.py @@ -11,55 +11,6 @@ from ffx.database import databaseContext VERSION='0.1.0' -STREAM_TYPE_VIDEO = 'video' -STREAM_TYPE_AUDIO = 'audio' -STREAM_TYPE_SUBTITLE = 'subtitle' - -SEASON_EPISODE_INDICATOR_MATCH = '[sS]([0-9]+)[eE]([0-9]+)' -EPISODE_INDICATOR_MATCH = '[eE]([0-9]+)' -SEASON_EPISODE_STREAM_LANGUAGE_MATCH = '[sS]([0-9]+)[eE]([0-9]+)_([0-9]+)_([a-z]{3})' - -SUBTITLE_FILE_EXTENSION = 'vtt' - - -def getModifiedStreamOrder(length, last): - """This is jellyfin specific as the last stream in the order is set as default""" - seq = list(range(length)) - if last < 0 or last > length -1: - return seq - seq.pop(last) - seq.append(last) - return seq - - -# def countStreamDispositions(subStreamDescriptor): -# return len([l for (k,v) in subStreamDescriptor['disposition'].items()]) - -def searchSubtitleFiles(dir, prefix): - - sesl_match = re.compile(SEASON_EPISODE_STREAM_LANGUAGE_MATCH) - - availableFileSubtitleDescriptors = [] - for subtitleFilename in os.listdir(dir): - if subtitleFilename.startswith(prefix) and subtitleFilename.endswith('.' + SUBTITLE_FILE_EXTENSION): - sesl_result = sesl_match.search(subtitleFilename) - if sesl_result is not None: - subtitleFilePath = os.path.join(dir, subtitleFilename) - if os.path.isfile(subtitleFilePath): - - subtitleFileDescriptor = {} - subtitleFileDescriptor['path'] = subtitleFilePath - subtitleFileDescriptor['season'] = int(sesl_result.group(1)) - subtitleFileDescriptor['episode'] = int(sesl_result.group(2)) - subtitleFileDescriptor['stream'] = int(sesl_result.group(3)) - subtitleFileDescriptor['language'] = sesl_result.group(4) - - availableFileSubtitleDescriptors.append(subtitleFileDescriptor) - - click.echo(f"Found {len(availableFileSubtitleDescriptors)} subtitles in files\n") - - return availableFileSubtitleDescriptors - @click.group() @click.pass_context @@ -252,19 +203,16 @@ def convert(ctx, # click.echo(f"Qualities: {q_list}") # -# context['bitrates'] = {} -# context['bitrates']['stereo'] = str(stereo_bitrate) if str(stereo_bitrate).endswith('k') else f"{stereo_bitrate}k" -# context['bitrates']['ac3'] = str(ac3_bitrate) if str(ac3_bitrate).endswith('k') else f"{ac3_bitrate}k" -# context['bitrates']['dts'] = str(dts_bitrate) if str(dts_bitrate).endswith('k') else f"{dts_bitrate}k" + context['bitrates'] = {} + context['bitrates']['stereo'] = str(stereo_bitrate) if str(stereo_bitrate).endswith('k') else f"{stereo_bitrate}k" + context['bitrates']['ac3'] = str(ac3_bitrate) if str(ac3_bitrate).endswith('k') else f"{ac3_bitrate}k" + context['bitrates']['dts'] = str(dts_bitrate) if str(dts_bitrate).endswith('k') else f"{dts_bitrate}k" # # click.echo(f"Stereo bitrate: {context['bitrates']['stereo']}") # click.echo(f"AC3 bitrate: {context['bitrates']['ac3']}") # click.echo(f"DTS bitrate: {context['bitrates']['dts']}") # # -# se_match = re.compile(SEASON_EPISODE_INDICATOR_MATCH) -# e_match = re.compile(EPISODE_INDICATOR_MATCH) -# # # ## Conversion parameters # @@ -347,6 +295,10 @@ def convert(ctx, dispositionTokens = fc.generateDispositionTokens() click.echo(f"Disposition Tokens: {dispositionTokens}") + audioTokens = fc.generateAudioEncodingTokens() + click.echo(f"Audio Tokens: {audioTokens}") + + click.echo(f"Season={mediaFileProperties.getSeason()} Episode={mediaFileProperties.getEpisode()}") # # Determine season and episode if present in current filename # season_digits = 2 diff --git a/bin/ffx/ffx_controller.py b/bin/ffx/ffx_controller.py index 362c3c3..a86424c 100644 --- a/bin/ffx/ffx_controller.py +++ b/bin/ffx/ffx_controller.py @@ -5,7 +5,7 @@ from ffx.helper import DIFF_ADDED_KEY, DIFF_REMOVED_KEY, DIFF_CHANGED_KEY from ffx.track_descriptor import TrackDescriptor from ffx.model.track import Track from ffx.audio_layout import AudioLayout - +from ffx.track_type import TrackType class FfxController(): @@ -104,41 +104,59 @@ class FfxController(): return ['-f', format, f"{filepath}.{ext}"] - def generateAudioEncodingTokens(self, subIndex : int, layout : AudioLayout): - """Generates ffmpeg options for one output audio stream including channel remapping, codec and bitrate""" - pass - - if layout == AudioLayout.LAYOUT_6_1: - return [f"-c:a:{subIndex}", - 'libopus', - f"-filter:a:{subIndex}", - 'channelmap=channel_layout=6.1', - f"-b:a:{subIndex}", - self.__context['bitrates']['dts']] - - elif layout == AudioLayout.LAYOUT_5_1: - return [f"-c:a:{subIndex}", - 'libopus', - f"-filter:a:{subIndex}", - "channelmap=FL-FL|FR-FR|FC-FC|LFE-LFE|SL-BL|SR-BR:5.1", - f"-b:a:{subIndex}", - self.__context['bitrates']['ac3']] - - elif layout == AudioLayout.LAYOUT_STEREO: - return [f"-c:a:{subIndex}", - 'libopus', - f"-b:a:{subIndex}", - self.__context['bitrates']['stereo']] - - elif layout == AudioLayout.LAYOUT_6CH: - return [f"-c:a:{subIndex}", - 'libopus', - f"-filter:a:{subIndex}", - "channelmap=FL-FL|FR-FR|FC-FC|LFE-LFE|SL-BL|SR-BR:5.1", - f"-b:a:{subIndex}", - self.__context['bitrates']['ac3']] - else: - return [] + def generateAudioEncodingTokens(self): + """Generates ffmpeg options audio streams including channel remapping, codec and bitrate""" + + audioTokens = [] + + #sourceAudioTrackDescriptors = [smd for smd in self.__sourceMediaDescriptor.getAllTrackDescriptors() if smd.getType() == TrackType.AUDIO] + targetAudioTrackDescriptors = [rtd for rtd in self.__targetMediaDescriptor.getReorderedTrackDescriptors() if rtd.getType() == TrackType.AUDIO] + + trackSubIndex = 0 + for trackDescriptor in targetAudioTrackDescriptors: + + # Calculate source sub index + #changedTargetTrackDescriptor : TrackDescriptor = targetAudioTrackDescriptors[trackDescriptor.getIndex()] + #changedTargetTrackSourceIndex = changedTargetTrackDescriptor.getSourceIndex() + #sourceSubIndex = sourceAudioTrackDescriptors[changedTargetTrackSourceIndex].getSubIndex() + + trackAudioLayout = trackDescriptor.getAudioLayout() + + #TODO: Sollte nicht die sub index unverändert bleiben wenn jellyfin reordering angewendet wurde? + # siehe auch: MediaDescriptor.getInputMappingTokens() + #trackSubIndex = trackDescriptor.getSubIndex() + + if trackAudioLayout == AudioLayout.LAYOUT_6_1: + audioTokens += [f"-c:a:{trackSubIndex}", + 'libopus', + f"-filter:a:{trackSubIndex}", + 'channelmap=channel_layout=6.1', + f"-b:a:{trackSubIndex}", + self.__context['bitrates']['dts']] + + if trackAudioLayout == AudioLayout.LAYOUT_5_1: + audioTokens += [f"-c:a:{trackSubIndex}", + 'libopus', + f"-filter:a:{trackSubIndex}", + "channelmap=FL-FL|FR-FR|FC-FC|LFE-LFE|SL-BL|SR-BR:5.1", + f"-b:a:{trackSubIndex}", + self.__context['bitrates']['ac3']] + + if trackAudioLayout == AudioLayout.LAYOUT_STEREO: + audioTokens += [f"-c:a:{trackSubIndex}", + 'libopus', + f"-b:a:{trackSubIndex}", + self.__context['bitrates']['stereo']] + + if trackAudioLayout == AudioLayout.LAYOUT_6CH: + audioTokens += [f"-c:a:{trackSubIndex}", + 'libopus', + f"-filter:a:{trackSubIndex}", + "channelmap=FL-FL|FR-FR|FC-FC|LFE-LFE|SL-BL|SR-BR:5.1", + f"-b:a:{trackSubIndex}", + self.__context['bitrates']['ac3']] + trackSubIndex += 1 + return audioTokens # def generateClearTokens(self, streams): diff --git a/bin/ffx/file_properties.py b/bin/ffx/file_properties.py index 35bb811..07e9bda 100644 --- a/bin/ffx/file_properties.py +++ b/bin/ffx/file_properties.py @@ -16,6 +16,10 @@ class FileProperties(): EPISODE_INDICATOR_MATCH = '[eE]([0-9]+)' + SEASON_EPISODE_STREAM_LANGUAGE_MATCH = '[sS]([0-9]+)[eE]([0-9]+)_([0-9]+)_([a-z]{3})' + SUBTITLE_FILE_EXTENSION = 'vtt' + + def __init__(self, context, sourcePath): self.context = context @@ -36,28 +40,26 @@ class FileProperties(): self.__sourceFilenameExtension = '' - se_match = re.compile(FileProperties.SEASON_EPISODE_INDICATOR_MATCH) - e_match = re.compile(FileProperties.EPISODE_INDICATOR_MATCH) - - se_result = se_match.search(self.__sourceFilename) - e_result = e_match.search(self.__sourceFilename) + self.__pc = PatternController(context) - self.__season = -1 - self.__episode = -1 - file_index = 0 + matchResult = self.__pc.matchFilename(self.__sourceFilename) + + self.__pattern = matchResult['pattern'] if matchResult else None - if se_result is not None: - self.__season = int(se_result.group(1)) - self.__episode = int(se_result.group(2)) - elif e_result is not None: - self.__episode = int(e_result.group(1)) - else: - file_index += 1 + matchedGroups = matchResult['match'].groups() if matchResult else {} + seIndicator = matchedGroups[0] if matchedGroups else self.__sourceFilename + se_match = re.search(FileProperties.SEASON_EPISODE_INDICATOR_MATCH, seIndicator) + e_match = re.search(FileProperties.EPISODE_INDICATOR_MATCH, seIndicator) - self.__pc = PatternController(context) + self.__season = -1 + self.__episode = -1 - self.__pattern = self.__pc.matchFilename(self.__sourceFilename) + if se_match is not None: + self.__season = int(se_match.group(1)) + self.__episode = int(se_match.group(2)) + elif e_match is not None: + self.__episode = int(e_match.group(1)) # click.echo(pattern) @@ -85,6 +87,33 @@ class FileProperties(): # + def searchSubtitleFiles(dir, prefix): + + sesl_match = re.compile(FileProperties.SEASON_EPISODE_STREAM_LANGUAGE_MATCH) + + availableFileSubtitleDescriptors = [] + for subtitleFilename in os.listdir(dir): + if subtitleFilename.startswith(prefix) and subtitleFilename.endswith('.' + FileProperties.SUBTITLE_FILE_EXTENSION): + sesl_result = sesl_match.search(subtitleFilename) + if sesl_result is not None: + subtitleFilePath = os.path.join(dir, subtitleFilename) + if os.path.isfile(subtitleFilePath): + + subtitleFileDescriptor = {} + subtitleFileDescriptor['path'] = subtitleFilePath + subtitleFileDescriptor['season'] = int(sesl_result.group(1)) + subtitleFileDescriptor['episode'] = int(sesl_result.group(2)) + subtitleFileDescriptor['stream'] = int(sesl_result.group(3)) + subtitleFileDescriptor['language'] = sesl_result.group(4) + + availableFileSubtitleDescriptors.append(subtitleFileDescriptor) + + click.echo(f"Found {len(availableFileSubtitleDescriptors)} subtitles in files\n") + + return availableFileSubtitleDescriptors + + + def getFormatData(self): """ "format": { @@ -201,4 +230,12 @@ class FileProperties(): def getPattern(self) -> Pattern: """Result is None if the filename did not match anything in database""" return self.__pattern - \ No newline at end of file + + + def getSeason(self): + return int(self.__season) + + def getEpisode(self): + return int(self.__episode) + + diff --git a/bin/ffx/pattern_controller.py b/bin/ffx/pattern_controller.py index f6f567d..8bb68e1 100644 --- a/bin/ffx/pattern_controller.py +++ b/bin/ffx/pattern_controller.py @@ -116,21 +116,24 @@ class PatternController(): s.close() - def matchFilename(self, filename) -> Pattern: + def matchFilename(self, filename : str) -> re.Match: try: s = self.Session() q = s.query(Pattern) - matchedPatterns = [p for p in q.all() if re.search(p.pattern, filename)] - - if matchedPatterns: - return matchedPatterns[0] - else: - return None + matchResult = {} + + for pattern in q.all(): + patternMatch = re.search(str(pattern.pattern), str(filename)) + if patternMatch: + matchResult['match'] = patternMatch + matchResult['pattern'] = pattern + return matchResult + except Exception as ex: - raise click.ClickException(f"PatternController.findPattern(): {repr(ex)}") + raise click.ClickException(f"PatternController.matchFilename(): {repr(ex)}") finally: s.close()