click-textual
Maveno 12 months ago
parent 93cc8a23c9
commit e1cff6c8db

@ -3,7 +3,7 @@
import os, sys, subprocess, json, click, time, re import os, sys, subprocess, json, click, time, re
from ffx.ffx_app import FfxApp from ffx.ffx_app import FfxApp
from ffx.ffx_controller import FfxController
from ffx.database import databaseContext from ffx.database import databaseContext
@ -169,14 +169,14 @@ def shows(ctx):
@click.argument('paths', nargs=-1) @click.argument('paths', nargs=-1)
@click.option('-l', '--label', type=str, default='', help='Label to be used as filename prefix') @click.option('-l', '--label', type=str, default='', help='Label to be used as filename prefix')
@click.option('-v', '--video-encoder', type=str, default=DEFAULT_VIDEO_ENCODER, help=f"Target video encoder (vp9 or av1) default: {DEFAULT_VIDEO_ENCODER}") @click.option('-v', '--video-encoder', type=str, default=FfxController.DEFAULT_VIDEO_ENCODER, help=f"Target video encoder (vp9 or av1) default: {FfxController.DEFAULT_VIDEO_ENCODER}")
@click.option('-q', '--quality', type=str, default=DEFAULT_QUALITY, help=f"Quality settings to be used with VP9 encoder (default: {DEFAULT_QUALITY})") @click.option('-q', '--quality', type=str, default=FfxController.DEFAULT_QUALITY, help=f"Quality settings to be used with VP9 encoder (default: {FfxController.DEFAULT_QUALITY})")
@click.option('-p', '--preset', type=str, default=DEFAULT_AV1_PRESET, help=f"Quality preset to be used with AV1 encoder (default: {DEFAULT_AV1_PRESET})") @click.option('-p', '--preset', type=str, default=FfxController.DEFAULT_AV1_PRESET, help=f"Quality preset to be used with AV1 encoder (default: {FfxController.DEFAULT_AV1_PRESET})")
@click.option('-a', '--stereo-bitrate', type=int, default=DEFAULT_STEREO_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode stereo audio streams (default: {DEFAULT_STEREO_BANDWIDTH})") @click.option('-a', '--stereo-bitrate', type=int, default=FfxController.DEFAULT_STEREO_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode stereo audio streams (default: {FfxController.DEFAULT_STEREO_BANDWIDTH})")
@click.option('-ac3', '--ac3-bitrate', type=int, default=DEFAULT_AC3_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode 5.1 audio streams (default: {DEFAULT_AC3_BANDWIDTH})") @click.option('-ac3', '--ac3-bitrate', type=int, default=FfxController.DEFAULT_AC3_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode 5.1 audio streams (default: {FfxController.DEFAULT_AC3_BANDWIDTH})")
@click.option('-dts', '--dts-bitrate', type=int, default=DEFAULT_DTS_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode 6.1 audio streams (default: {DEFAULT_DTS_BANDWIDTH})") @click.option('-dts', '--dts-bitrate', type=int, default=FfxController.DEFAULT_DTS_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode 6.1 audio streams (default: {FfxController.DEFAULT_DTS_BANDWIDTH})")
@click.option('-sd', '--subtitle-directory', type=str, default='', help='Load subtitles from here') @click.option('-sd', '--subtitle-directory', type=str, default='', help='Load subtitles from here')
@click.option('-sp', '--subtitle-prefix', type=str, default='', help='Subtitle filename prefix') @click.option('-sp', '--subtitle-prefix', type=str, default='', help='Subtitle filename prefix')
@ -297,8 +297,8 @@ def convert(ctx,
if cTokens and len(cTokens) == 2: if cTokens and len(cTokens) == 2:
cropStart, cropLength = crop.split(',') cropStart, cropLength = crop.split(',')
else: else:
cropStart = DEFAULT_CROP_START cropStart = FfxController.DEFAULT_CROP_START
cropLength = DEFAULT_CROP_LENGTH cropLength = FfxController.DEFAULT_CROP_LENGTH
click.echo(f"crop start={cropStart} length={cropLength}") click.echo(f"crop start={cropStart} length={cropLength}")
@ -359,7 +359,7 @@ def convert(ctx,
# Assemble target filename tokens # Assemble target filename tokens
targetFilenameTokens = [] targetFilenameTokens = []
targetFilenameExtension = DEFAULT_FILE_EXTENSION targetFilenameExtension = FfxController.DEFAULT_FILE_EXTENSION
if label: if label:
targetFilenameTokens = [label] targetFilenameTokens = [label]

@ -3,10 +3,13 @@ import click
from typing import List, Self from typing import List, Self
from ffx.track_type import TrackType from ffx.track_type import TrackType
from ffx.track_disposition import TrackDisposition
from ffx.track_descriptor import TrackDescriptor from ffx.track_descriptor import TrackDescriptor
from ffx.helper import dictDiff, DIFF_ADDED_KEY, DIFF_CHANGED_KEY, DIFF_REMOVED_KEY from ffx.helper import dictDiff, DIFF_ADDED_KEY, DIFF_CHANGED_KEY, DIFF_REMOVED_KEY
class MediaDescriptor(): class MediaDescriptor():
"""This class represents the structural content of a media file including streams and metadata""" """This class represents the structural content of a media file including streams and metadata"""
@ -14,11 +17,12 @@ class MediaDescriptor():
TRACKS_KEY = 'tracks' TRACKS_KEY = 'tracks'
TRACK_DESCRIPTOR_LIST_KEY = 'track_descriptors' TRACK_DESCRIPTOR_LIST_KEY = 'track_descriptors'
CLEAR_TAGS_KEY = 'clear_tags' CLEAR_TAGS_FLAG_KEY = 'clear_tags'
FFPROBE_DISPOSITION_KEY = 'disposition' FFPROBE_DISPOSITION_KEY = 'disposition'
FFPROBE_TAGS_KEY = 'tags' FFPROBE_TAGS_KEY = 'tags'
FFPROBE_CODEC_TYPE_KEY = 'codec_type' FFPROBE_CODEC_TYPE_KEY = 'codec_type'
JELLYFIN_ORDER_FLAG_KEY = 'jellyfin_order'
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -42,13 +46,63 @@ class MediaDescriptor():
else: else:
self.__trackDescriptors = [] self.__trackDescriptors = []
if MediaDescriptor.CLEAR_TAGS_KEY in kwargs.keys(): if MediaDescriptor.CLEAR_TAGS_FLAG_KEY in kwargs.keys():
if type(kwargs[MediaDescriptor.CLEAR_TAGS_KEY]) is not bool: if type(kwargs[MediaDescriptor.CLEAR_TAGS_FLAG_KEY]) is not bool:
raise TypeError(f"MediaDescriptor.__init__(): Argument {MediaDescriptor.CLEAR_TAGS_KEY} is required to be of type bool") raise TypeError(f"MediaDescriptor.__init__(): Argument {MediaDescriptor.CLEAR_TAGS_FLAG_KEY} is required to be of type bool")
self.__clearTags = kwargs[MediaDescriptor.CLEAR_TAGS_KEY] self.__clearTags = kwargs[MediaDescriptor.CLEAR_TAGS_FLAG_KEY]
else: else:
self.__clearTags = False self.__clearTags = False
if MediaDescriptor.JELLYFIN_ORDER_FLAG_KEY in kwargs.keys():
if type(kwargs[MediaDescriptor.JELLYFIN_ORDER_FLAG_KEY]) is not bool:
raise TypeError(f"MediaDescriptor.__init__(): Argument {MediaDescriptor.JELLYFIN_ORDER_FLAG_KEY} is required to be of type bool")
self.__jellyfinOrder = kwargs[MediaDescriptor.JELLYFIN_ORDER_FLAG_KEY]
else:
self.__jellyfinOrder = False
def __getReorderedTrackDescriptors(self):
videoTracks = [v for v in self.__trackDescriptors.copy() if v.getType() == TrackType.VIDEO]
audioTracks = [a for a in self.__trackDescriptors.copy() if a.getType() == TrackType.AUDIO]
subtitleTracks = [s for s in self.__trackDescriptors.copy() if s.getType() == TrackType.SUBTITLE]
videoDefaultTracks = [v for v in videoTracks if TrackDisposition.DEFAULT in v.getDispositionSet()]
videoForcedTracks = [v for v in videoTracks if TrackDisposition.FORCED in v.getDispositionSet()]
audioDefaultTracks = [a for a in audioTracks if TrackDisposition.DEFAULT in a.getDispositionSet()]
audioForcedTracks = [a for a in audioTracks if TrackDisposition.FORCED in a.getDispositionSet()]
subtitleDefaultTracks = [s for s in subtitleTracks if TrackDisposition.DEFAULT in s.getDispositionSet()]
subtitleForcedTracks = [s for s in subtitleTracks if TrackDisposition.FORCED in s.getDispositionSet()]
if len(videoDefaultTracks) > 1:
raise ValueError('MediaDescriptor.__getSourceIndexOrder(): More than one default video track is not supported')
if len(videoForcedTracks) > 1:
raise ValueError('MediaDescriptor.__getSourceIndexOrder(): More than one forced video track is not supported')
if len(audioDefaultTracks) > 1:
raise ValueError('MediaDescriptor.__getSourceIndexOrder(): More than one default audio track is not supported')
if len(audioForcedTracks) > 1:
raise ValueError('MediaDescriptor.__getSourceIndexOrder(): More than one forced audio track is not supported')
if len(subtitleDefaultTracks) > 1:
raise ValueError('MediaDescriptor.__getSourceIndexOrder(): More than one default subtitle track is not supported')
if len(subtitleForcedTracks) > 1:
raise ValueError('MediaDescriptor.__getSourceIndexOrder(): More than one forced subtitle track is not supported')
if self.__jellyfinOrder:
if videoDefaultTracks:
videoTracks.append(videoTracks.pop(videoTracks.index(videoDefaultTracks[0])))
if audioDefaultTracks:
audioTracks.append(audioTracks.pop(audioTracks.index(audioDefaultTracks[0])))
if subtitleDefaultTracks:
subtitleTracks.append(subtitleTracks.pop(subtitleTracks.index(subtitleDefaultTracks[0])))
reorderedTrackDescriptors = videoTracks + audioTracks + subtitleTracks
orderedSourceTrackSequence = [t.getSourceIndex() for t in reorderedTrackDescriptors]
if len(set(orderedSourceTrackSequence)) < len(orderedSourceTrackSequence):
raise ValueError(f"Multiple streams originating from the same source stream not supported")
return reorderedTrackDescriptors
@classmethod @classmethod
def fromFfprobe(cls, formatData, streamData): def fromFfprobe(cls, formatData, streamData):
@ -72,7 +126,8 @@ class MediaDescriptor():
if trackType not in subIndexCounters.keys(): if trackType not in subIndexCounters.keys():
subIndexCounters[trackType] = 0 subIndexCounters[trackType] = 0
kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY].append(TrackDescriptor.fromFfprobe(streamObj, subIndex=subIndexCounters[trackType])) kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY].append(TrackDescriptor.fromFfprobe(streamObj,
subIndex=subIndexCounters[trackType]))
subIndexCounters[trackType] += 1 subIndexCounters[trackType] += 1
@ -112,7 +167,8 @@ class MediaDescriptor():
# Target track configuration (from DB) # Target track configuration (from DB)
tracks = self.getAllTracks() #tracks = self.getAllTracks()
tracks = self.__getReorderedTrackDescriptors()
numTracks = len(tracks) numTracks = len(tracks)
# Current track configuration (of file) # Current track configuration (of file)
@ -134,6 +190,7 @@ class MediaDescriptor():
continue continue
# Will trigger if tracks are missing in DB definition # Will trigger if tracks are missing in DB definition
# New tracks will be added per update via this way
if tp > (numTracks - 1): if tp > (numTracks - 1):
if DIFF_REMOVED_KEY not in trackCompareResult.keys(): if DIFF_REMOVED_KEY not in trackCompareResult.keys():
trackCompareResult[DIFF_REMOVED_KEY] = {} trackCompareResult[DIFF_REMOVED_KEY] = {}

@ -158,6 +158,8 @@ class MediaDetailsScreen(Screen):
# from file (=current) vs from stored in database (=target) # from file (=current) vs from stored in database (=target)
self.__mediaDifferences = self.__targetMediaDescriptor.compare(self.__currentMediaDescriptor) if self.__currentPattern is not None else {} self.__mediaDifferences = self.__targetMediaDescriptor.compare(self.__currentMediaDescriptor) if self.__currentPattern is not None else {}
#rtd = self.__targetMediaDescriptor.getReorderedTrackDescriptors()
#raise click.ClickException(f"getReorderedTrackDescriptors={[r.getIndex() for r in rtd]}")
def updateDifferences(self): def updateDifferences(self):
@ -242,6 +244,7 @@ class MediaDetailsScreen(Screen):
row = (' ', '<New show>', ' ') # Convert each element to a string before adding row = (' ', '<New show>', ' ') # Convert each element to a string before adding
self.showsTable.add_row(*map(str, row)) self.showsTable.add_row(*map(str, row))
#TODO: Stürzt ab wenn keine Shows vorhanden sind. Onthefly Show add impl
for show in self.__sc.getAllShows(): for show in self.__sc.getAllShows():
row = (int(show.id), show.name, show.year) # Convert each element to a string before adding row = (int(show.id), show.name, show.year) # Convert each element to a string before adding
self.showsTable.add_row(*map(str, row)) self.showsTable.add_row(*map(str, row))

@ -32,7 +32,7 @@ class Track(Base):
track_type = Column(Integer) # TrackType track_type = Column(Integer) # TrackType
index = Column(Integer) index = Column(Integer)
# sub_index = Column(Integer) source_index = Column(Integer)
# v1.x # v1.x
pattern_id = Column(Integer, ForeignKey('patterns.id', ondelete="CASCADE")) pattern_id = Column(Integer, ForeignKey('patterns.id', ondelete="CASCADE"))
@ -149,6 +149,9 @@ class Track(Base):
def getIndex(self): def getIndex(self):
return int(self.index) if self.index is not None else -1 return int(self.index) if self.index is not None else -1
def getSourceIndex(self):
return int(self.source_index) if self.source_index is not None else -1
def getLanguage(self): def getLanguage(self):
tags = {t.key:t.value for t in self.track_tags} tags = {t.key:t.value for t in self.track_tags}
return IsoLanguage.findThreeLetter(tags['language']) if 'language' in tags.keys() else IsoLanguage.UNDEFINED return IsoLanguage.findThreeLetter(tags['language']) if 'language' in tags.keys() else IsoLanguage.UNDEFINED
@ -182,6 +185,7 @@ class Track(Base):
kwargs[TrackDescriptor.PATTERN_ID_KEY] = self.getPatternId() kwargs[TrackDescriptor.PATTERN_ID_KEY] = self.getPatternId()
kwargs[TrackDescriptor.INDEX_KEY] = self.getIndex() kwargs[TrackDescriptor.INDEX_KEY] = self.getIndex()
kwargs[TrackDescriptor.SOURCE_INDEX_KEY] = self.getSourceIndex()
if subIndex > -1: if subIndex > -1:
kwargs[TrackDescriptor.SUB_INDEX_KEY] = subIndex kwargs[TrackDescriptor.SUB_INDEX_KEY] = subIndex

@ -31,6 +31,7 @@ class TrackController():
track = Track(pattern_id = patId, track = Track(pattern_id = patId,
track_type = int(trackDescriptor.getType().index()), track_type = int(trackDescriptor.getType().index()),
index = int(trackDescriptor.getIndex()), index = int(trackDescriptor.getIndex()),
source_index = int(trackDescriptor.getSourceIndex()),
disposition_flags = int(TrackDisposition.toFlags(trackDescriptor.getDispositionSet()))) disposition_flags = int(TrackDisposition.toFlags(trackDescriptor.getDispositionSet())))
s.add(track) s.add(track)

@ -10,6 +10,7 @@ class TrackDescriptor():
ID_KEY = 'id' ID_KEY = 'id'
INDEX_KEY = 'index' INDEX_KEY = 'index'
SOURCE_INDEX_KEY = 'source_index'
SUB_INDEX_KEY = 'sub_index' SUB_INDEX_KEY = 'sub_index'
PATTERN_ID_KEY = 'pattern_id' PATTERN_ID_KEY = 'pattern_id'
@ -46,6 +47,11 @@ class TrackDescriptor():
else: else:
self.__index = -1 self.__index = -1
if TrackDescriptor.SOURCE_INDEX_KEY in kwargs.keys() and type(kwargs[TrackDescriptor.SOURCE_INDEX_KEY]) is int:
self.__sourceIndex = kwargs[TrackDescriptor.SOURCE_INDEX_KEY]
else:
self.__sourceIndex = self.__index
if TrackDescriptor.SUB_INDEX_KEY in kwargs.keys(): if TrackDescriptor.SUB_INDEX_KEY in kwargs.keys():
if type(kwargs[TrackDescriptor.SUB_INDEX_KEY]) is not int: if type(kwargs[TrackDescriptor.SUB_INDEX_KEY]) is not int:
raise TypeError(f"TrackDesciptor.__init__(): Argument {TrackDescriptor.SUB_INDEX_KEY} is required to be of type int") raise TypeError(f"TrackDesciptor.__init__(): Argument {TrackDescriptor.SUB_INDEX_KEY} is required to be of type int")
@ -136,6 +142,7 @@ class TrackDescriptor():
kwargs = {} kwargs = {}
kwargs[TrackDescriptor.INDEX_KEY] = int(streamObj[TrackDescriptor.FFPROBE_INDEX_KEY]) if TrackDescriptor.FFPROBE_INDEX_KEY in streamObj.keys() else -1 kwargs[TrackDescriptor.INDEX_KEY] = int(streamObj[TrackDescriptor.FFPROBE_INDEX_KEY]) if TrackDescriptor.FFPROBE_INDEX_KEY in streamObj.keys() else -1
kwargs[TrackDescriptor.SOURCE_INDEX_KEY] = kwargs[TrackDescriptor.INDEX_KEY]
kwargs[TrackDescriptor.SUB_INDEX_KEY] = subIndex kwargs[TrackDescriptor.SUB_INDEX_KEY] = subIndex
kwargs[TrackDescriptor.TRACK_TYPE_KEY] = trackType kwargs[TrackDescriptor.TRACK_TYPE_KEY] = trackType
@ -158,6 +165,10 @@ class TrackDescriptor():
def getIndex(self): def getIndex(self):
return self.__index return self.__index
def getSourceIndex(self):
return self.__sourceIndex
def getSubIndex(self): def getSubIndex(self):
return self.__subIndex return self.__subIndex

Loading…
Cancel
Save