reworking target media decriptor track handling
This commit is contained in:
78
bin/ffx.py
78
bin/ffx.py
@@ -13,6 +13,7 @@ from ffx.database import databaseContext
|
|||||||
|
|
||||||
from ffx.track_type import TrackType
|
from ffx.track_type import TrackType
|
||||||
from ffx.video_encoder import VideoEncoder
|
from ffx.video_encoder import VideoEncoder
|
||||||
|
from ffx.track_disposition import TrackDisposition
|
||||||
|
|
||||||
|
|
||||||
VERSION='0.1.2'
|
VERSION='0.1.2'
|
||||||
@@ -160,6 +161,7 @@ def shows(ctx):
|
|||||||
|
|
||||||
@click.option("-t", "--no-tmdb", is_flag=True, default=False)
|
@click.option("-t", "--no-tmdb", is_flag=True, default=False)
|
||||||
@click.option("-j", "--no-jellyfin", is_flag=True, default=False)
|
@click.option("-j", "--no-jellyfin", is_flag=True, default=False)
|
||||||
|
@click.option("-np", "--no-pattern", is_flag=True, default=False)
|
||||||
|
|
||||||
@click.option("--dry-run", is_flag=True, default=False)
|
@click.option("--dry-run", is_flag=True, default=False)
|
||||||
|
|
||||||
@@ -191,6 +193,7 @@ def convert(ctx,
|
|||||||
denoise,
|
denoise,
|
||||||
no_tmdb,
|
no_tmdb,
|
||||||
no_jellyfin,
|
no_jellyfin,
|
||||||
|
no_pattern,
|
||||||
dry_run):
|
dry_run):
|
||||||
"""Batch conversion of audiovideo files in format suitable for web playback, e.g. jellyfin
|
"""Batch conversion of audiovideo files in format suitable for web playback, e.g. jellyfin
|
||||||
|
|
||||||
@@ -207,8 +210,9 @@ def convert(ctx,
|
|||||||
|
|
||||||
context['video_encoder'] = VideoEncoder.fromLabel(video_encoder)
|
context['video_encoder'] = VideoEncoder.fromLabel(video_encoder)
|
||||||
|
|
||||||
context['jellyfin'] = not no_jellyfin
|
context['use_jellyfin'] = not no_jellyfin
|
||||||
context['tmdb'] = not no_tmdb
|
context['use_tmdb'] = not no_tmdb
|
||||||
|
context['use_pattern'] = not no_pattern
|
||||||
|
|
||||||
context['import_subtitles'] = (subtitle_directory and subtitle_prefix)
|
context['import_subtitles'] = (subtitle_directory and subtitle_prefix)
|
||||||
if context['import_subtitles']:
|
if context['import_subtitles']:
|
||||||
@@ -242,7 +246,7 @@ def convert(ctx,
|
|||||||
click.echo(f"Crop start={context['crop_start']} length={context['crop_length']}")
|
click.echo(f"Crop start={context['crop_start']} length={context['crop_length']}")
|
||||||
|
|
||||||
|
|
||||||
tc = TmdbController()
|
tc = TmdbController() if context['use_tmdb'] else None
|
||||||
|
|
||||||
existingSourcePaths = [p for p in paths if os.path.isfile(p) and p.split('.')[-1] in FfxController.INPUT_FILE_EXTENSIONS]
|
existingSourcePaths = [p for p in paths if os.path.isfile(p) and p.split('.')[-1] in FfxController.INPUT_FILE_EXTENSIONS]
|
||||||
click.echo(f"\nRunning {len(existingSourcePaths) * len(q_list)} jobs")
|
click.echo(f"\nRunning {len(existingSourcePaths) * len(q_list)} jobs")
|
||||||
@@ -265,7 +269,7 @@ def convert(ctx,
|
|||||||
sourceMediaDescriptor = mediaFileProperties.getMediaDescriptor()
|
sourceMediaDescriptor = mediaFileProperties.getMediaDescriptor()
|
||||||
|
|
||||||
#HINT: This is None if the filename did not match anything in database
|
#HINT: This is None if the filename did not match anything in database
|
||||||
currentPattern = mediaFileProperties.getPattern()
|
currentPattern = mediaFileProperties.getPattern() if context['use_pattern'] else None
|
||||||
|
|
||||||
click.echo(f"Pattern matching: {'No' if currentPattern is None else 'Yes'}")
|
click.echo(f"Pattern matching: {'No' if currentPattern is None else 'Yes'}")
|
||||||
|
|
||||||
@@ -279,45 +283,36 @@ def convert(ctx,
|
|||||||
#
|
#
|
||||||
# Query user for the correct sub indices, then configure flags in track descriptors associated with media descriptor accordingly.
|
# Query user for the correct sub indices, then configure flags in track descriptors associated with media descriptor accordingly.
|
||||||
# The correct tokens should then be created by
|
# The correct tokens should then be created by
|
||||||
try:
|
if len([v for v in sourceMediaDescriptor.getVideoTracks() if v.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
|
||||||
sourceMediaDescriptor.getDefaultVideoTrack()
|
|
||||||
except ValueError:
|
|
||||||
defaultVideoTrackSubIndex = click.prompt("More than one default video stream detected! Please select stream", type=int)
|
defaultVideoTrackSubIndex = click.prompt("More than one default video stream detected! Please select stream", type=int)
|
||||||
sourceMediaDescriptor.setDefaultSubTrack(TrackType.VIDEO, defaultVideoTrackSubIndex)
|
sourceMediaDescriptor.setDefaultSubTrack(TrackType.VIDEO, defaultVideoTrackSubIndex)
|
||||||
try:
|
|
||||||
sourceMediaDescriptor.getForcedVideoTrack()
|
if len([v for v in sourceMediaDescriptor.getVideoTracks() if v.getDispositionFlag(TrackDisposition.FORCED)]) > 1:
|
||||||
except ValueError:
|
|
||||||
forcedVideoTrackSubIndex = click.prompt("More than one forced video stream detected! Please select stream", type=int)
|
forcedVideoTrackSubIndex = click.prompt("More than one forced video stream detected! Please select stream", type=int)
|
||||||
sourceMediaDescriptor.setForcedSubTrack(TrackType.VIDEO, forcedVideoTrackSubIndex)
|
sourceMediaDescriptor.setForcedSubTrack(TrackType.VIDEO, forcedVideoTrackSubIndex)
|
||||||
try:
|
|
||||||
sourceMediaDescriptor.getDefaultAudioTrack()
|
if len([a for a in sourceMediaDescriptor.getAudioTracks() if a.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
|
||||||
except ValueError:
|
|
||||||
defaultAudioTrackSubIndex = click.prompt("More than one default audio stream detected! Please select stream", type=int)
|
defaultAudioTrackSubIndex = click.prompt("More than one default audio stream detected! Please select stream", type=int)
|
||||||
sourceMediaDescriptor.setDefaultSubTrack(TrackType.AUDIO, defaultAudioTrackSubIndex)
|
sourceMediaDescriptor.setDefaultSubTrack(TrackType.AUDIO, defaultAudioTrackSubIndex)
|
||||||
try:
|
|
||||||
sourceMediaDescriptor.getForcedAudioTrack()
|
if len([a for a in sourceMediaDescriptor.getAudioTracks() if a.getDispositionFlag(TrackDisposition.FORCED)]) > 1:
|
||||||
except ValueError:
|
|
||||||
forcedAudioTrackSubIndex = click.prompt("More than one forced audio stream detected! Please select stream", type=int)
|
forcedAudioTrackSubIndex = click.prompt("More than one forced audio stream detected! Please select stream", type=int)
|
||||||
sourceMediaDescriptor.setForcedSubTrack(TrackType.AUDIO, forcedAudioTrackSubIndex)
|
sourceMediaDescriptor.setForcedSubTrack(TrackType.AUDIO, forcedAudioTrackSubIndex)
|
||||||
try:
|
|
||||||
sourceMediaDescriptor.getDefaultSubtitleTrack()
|
if len([s for s in sourceMediaDescriptor.getSubtitleTracks() if s.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
|
||||||
except ValueError:
|
|
||||||
defaultSubtitleTrackSubIndex = click.prompt("More than one default subtitle stream detected! Please select stream", type=int)
|
defaultSubtitleTrackSubIndex = click.prompt("More than one default subtitle stream detected! Please select stream", type=int)
|
||||||
sourceMediaDescriptor.setDefaultSubTrack(TrackType.SUBTITLE, defaultSubtitleTrackSubIndex)
|
sourceMediaDescriptor.setDefaultSubTrack(TrackType.SUBTITLE, defaultSubtitleTrackSubIndex)
|
||||||
try:
|
|
||||||
sourceMediaDescriptor.getForcedSubtitleTrack()
|
if len([s for s in sourceMediaDescriptor.getSubtitleTracks() if s.getDispositionFlag(TrackDisposition.FORCED)]) > 1:
|
||||||
except ValueError:
|
|
||||||
forcedSubtitleTrackSubIndex = click.prompt("More than one forced subtitle stream detected! Please select stream", type=int)
|
forcedSubtitleTrackSubIndex = click.prompt("More than one forced subtitle stream detected! Please select stream", type=int)
|
||||||
sourceMediaDescriptor.setForcedSubTrack(TrackType.SUBTITLE, forcedSubtitleTrackSubIndex)
|
sourceMediaDescriptor.setForcedSubTrack(TrackType.SUBTITLE, forcedSubtitleTrackSubIndex)
|
||||||
|
|
||||||
|
|
||||||
if context['import_subtitles']:
|
if context['import_subtitles']:
|
||||||
sourceMediaDescriptor.importSubtitles(context['subtitle_directory'], context['subtitle_prefix'])
|
sourceMediaDescriptor.importSubtitles(context['subtitle_directory'], context['subtitle_prefix'])
|
||||||
|
|
||||||
fc = FfxController(context, sourceMediaDescriptor)
|
fc = FfxController(context, sourceMediaDescriptor)
|
||||||
|
|
||||||
# mappingTokens = fc.generateMetadataTokens()
|
|
||||||
# click.echo(f"Metadata Tokens: {mappingTokens}")
|
|
||||||
|
|
||||||
dispositionTokens = fc.generateDispositionTokens()
|
dispositionTokens = fc.generateDispositionTokens()
|
||||||
click.echo(f"Disposition Tokens: {dispositionTokens}")
|
click.echo(f"Disposition Tokens: {dispositionTokens}")
|
||||||
|
|
||||||
@@ -329,18 +324,14 @@ def convert(ctx,
|
|||||||
# Case pattern matching
|
# Case pattern matching
|
||||||
|
|
||||||
targetMediaDescriptor = currentPattern.getMediaDescriptor()
|
targetMediaDescriptor = currentPattern.getMediaDescriptor()
|
||||||
|
|
||||||
currentShowDescriptor = currentPattern.getShowDescriptor()
|
currentShowDescriptor = currentPattern.getShowDescriptor()
|
||||||
|
|
||||||
|
|
||||||
if context['tmdb']:
|
if context['use_tmdb']:
|
||||||
|
|
||||||
tmdbEpisodeResult = tc.queryEpisode(currentShowDescriptor.getId(), mediaFileProperties.getSeason(), mediaFileProperties.getEpisode())
|
tmdbEpisodeResult = tc.queryEpisode(currentShowDescriptor.getId(), mediaFileProperties.getSeason(), mediaFileProperties.getEpisode())
|
||||||
|
|
||||||
# click.echo(f"{tmdbEpisodeResult}")
|
|
||||||
|
|
||||||
#print(type(tmdbEpisodeResult['name']))
|
|
||||||
#quit()
|
|
||||||
|
|
||||||
if tmdbEpisodeResult:
|
if tmdbEpisodeResult:
|
||||||
fileBasename = tc.getEpisodeFileBasename(currentShowDescriptor.getFilenamePrefix(),
|
fileBasename = tc.getEpisodeFileBasename(currentShowDescriptor.getFilenamePrefix(),
|
||||||
tmdbEpisodeResult['name'],
|
tmdbEpisodeResult['name'],
|
||||||
@@ -349,9 +340,7 @@ def convert(ctx,
|
|||||||
currentShowDescriptor.getIndexSeasonDigits(),
|
currentShowDescriptor.getIndexSeasonDigits(),
|
||||||
currentShowDescriptor.getIndexEpisodeDigits(),
|
currentShowDescriptor.getIndexEpisodeDigits(),
|
||||||
currentShowDescriptor.getIndicatorSeasonDigits(),
|
currentShowDescriptor.getIndicatorSeasonDigits(),
|
||||||
currentShowDescriptor.getIndicatorEpisodeDigits())
|
currentShowDescriptor.getIndicatorEpisodeDigits())
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
fileBasename = currentShowDescriptor.getFilenamePrefix()
|
fileBasename = currentShowDescriptor.getFilenamePrefix()
|
||||||
|
|
||||||
@@ -359,8 +348,16 @@ def convert(ctx,
|
|||||||
|
|
||||||
if context['import_subtitles']:
|
if context['import_subtitles']:
|
||||||
targetMediaDescriptor.importSubtitles(context['subtitle_directory'], context['subtitle_prefix'])
|
targetMediaDescriptor.importSubtitles(context['subtitle_directory'], context['subtitle_prefix'])
|
||||||
|
|
||||||
|
# raise click.ClickException(f"tmd subindices: {[t.getSubIndex() for t in targetMediaDescriptor.getAllTrackDescriptors()]}")
|
||||||
|
# click.echo(f"tmd subindices: {[t.getIndex() for t in targetMediaDescriptor.getAllTrackDescriptors()]} {[t.getSubIndex() for t in targetMediaDescriptor.getAllTrackDescriptors()]} {[t.getDispositionFlag(TrackDisposition.DEFAULT) for t in targetMediaDescriptor.getAllTrackDescriptors()]}")
|
||||||
|
|
||||||
targetMediaDescriptor.setJellyfinOrder(context['jellyfin'])
|
if context['use_jellyfin']:
|
||||||
|
# Reorder subtracks in types with default the last, then make subindices flat again
|
||||||
|
targetMediaDescriptor.applyJellyfinOrder()
|
||||||
|
|
||||||
|
# click.echo(f"tmd subindices: {[t.getIndex() for t in targetMediaDescriptor.getAllTrackDescriptors()]} {[t.getSubIndex() for t in targetMediaDescriptor.getAllTrackDescriptors()]} {[t.getDispositionFlag(TrackDisposition.DEFAULT) for t in targetMediaDescriptor.getAllTrackDescriptors()]}")
|
||||||
|
# raise click.Abort
|
||||||
|
|
||||||
click.echo(f"Input mapping tokens (2nd pass): {targetMediaDescriptor.getInputMappingTokens()}")
|
click.echo(f"Input mapping tokens (2nd pass): {targetMediaDescriptor.getInputMappingTokens()}")
|
||||||
|
|
||||||
@@ -385,9 +382,10 @@ def convert(ctx,
|
|||||||
|
|
||||||
extra = ['ffx'] if sourceFilenameExtension == FfxController.DEFAULT_FILE_EXTENSION else []
|
extra = ['ffx'] if sourceFilenameExtension == FfxController.DEFAULT_FILE_EXTENSION else []
|
||||||
|
|
||||||
targetFilename = fileBasename if context['tmdb'] else mediaFileProperties.assembleTargetFileBasename(label if label else fileBasename,
|
targetFilename = (fileBasename if context['use_tmdb']
|
||||||
q if len(q_list) > 1 else -1,
|
else mediaFileProperties.assembleTargetFileBasename(label if label else fileBasename,
|
||||||
extraTokens = extra)
|
q if len(q_list) > 1 else -1,
|
||||||
|
extraTokens = extra))
|
||||||
|
|
||||||
targetPath = os.path.join(output_directory if output_directory else sourceDirectory, targetFilename)
|
targetPath = os.path.join(output_directory if output_directory else sourceDirectory, targetFilename)
|
||||||
|
|
||||||
@@ -398,7 +396,7 @@ def convert(ctx,
|
|||||||
preset,
|
preset,
|
||||||
denoise)
|
denoise)
|
||||||
|
|
||||||
# #click.confirm('Warning! This file is not compliant to the defined source schema! Do you want to continue?', abort=True)
|
#TODO: click.confirm('Warning! This file is not compliant to the defined source schema! Do you want to continue?', abort=True)
|
||||||
|
|
||||||
endTime = time.perf_counter()
|
endTime = time.perf_counter()
|
||||||
click.echo(f"\nDONE\nTime elapsed {endTime - startTime}")
|
click.echo(f"\nDONE\nTime elapsed {endTime - startTime}")
|
||||||
|
|||||||
@@ -117,7 +117,8 @@ class FfxController():
|
|||||||
audioTokens = []
|
audioTokens = []
|
||||||
|
|
||||||
#sourceAudioTrackDescriptors = [smd for smd in self.__sourceMediaDescriptor.getAllTrackDescriptors() if smd.getType() == TrackType.AUDIO]
|
#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]
|
# targetAudioTrackDescriptors = [rtd for rtd in self.__targetMediaDescriptor.getReorderedTrackDescriptors() if rtd.getType() == TrackType.AUDIO]
|
||||||
|
targetAudioTrackDescriptors = [td for td in self.__targetMediaDescriptor.getAllTrackDescriptors() if td.getType() == TrackType.AUDIO]
|
||||||
|
|
||||||
trackSubIndex = 0
|
trackSubIndex = 0
|
||||||
for trackDescriptor in targetAudioTrackDescriptors:
|
for trackDescriptor in targetAudioTrackDescriptors:
|
||||||
@@ -166,29 +167,25 @@ class FfxController():
|
|||||||
return audioTokens
|
return audioTokens
|
||||||
|
|
||||||
|
|
||||||
|
# -disposition:s:0 default -disposition:s:1 0
|
||||||
def generateDispositionTokens(self):
|
def generateDispositionTokens(self):
|
||||||
"""Source media descriptor is optional"""
|
|
||||||
|
# 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.getReorderedTrackDescriptors()
|
|
||||||
|
|
||||||
dispositionTokens = []
|
dispositionTokens = []
|
||||||
|
|
||||||
|
# raise click.ClickException(f"ttd subindices: {[t.getSubIndex() for t in targetTrackDescriptors]}")
|
||||||
|
|
||||||
# for subStreamIndex in range(len(subDescriptor)):
|
#TODO: Sorting here is for the sole purpose to let the tokens appear with ascending subindices. Why necessary? Jellyfin order?
|
||||||
|
# for trackDescriptor in sorted(targetTrackDescriptors.copy(), key=lambda d: d.getSubIndex()):
|
||||||
for trackDescriptor in targetTrackDescriptors:
|
for trackDescriptor in targetTrackDescriptors:
|
||||||
|
|
||||||
#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 (trackDescriptor.getExternalSourceFilePath()
|
if (trackDescriptor.getExternalSourceFilePath()
|
||||||
or trackDescriptor.getCodec() != TrackDescriptor.CODEC_PGS):
|
or trackDescriptor.getCodec() != TrackDescriptor.CODEC_PGS):
|
||||||
|
|
||||||
# Calculate source sub index. This applies only if a source media descriptor is defined.
|
subIndex = trackDescriptor.getSubIndex()
|
||||||
if sourceTrackDescriptors:
|
|
||||||
changedTargetTrackDescriptor : TrackDescriptor = targetTrackDescriptors[trackDescriptor.getIndex()]
|
|
||||||
changedTargetTrackSourceIndex = changedTargetTrackDescriptor.getSourceIndex()
|
|
||||||
subIndex = sourceTrackDescriptors[changedTargetTrackSourceIndex].getSubIndex()
|
|
||||||
else:
|
|
||||||
subIndex = trackDescriptor.getSubIndex()
|
|
||||||
|
|
||||||
streamIndicator = trackDescriptor.getType().indicator()
|
streamIndicator = trackDescriptor.getType().indicator()
|
||||||
dispositionSet = trackDescriptor.getDispositionSet()
|
dispositionSet = trackDescriptor.getDispositionSet()
|
||||||
|
|
||||||
@@ -358,37 +355,20 @@ class FfxController():
|
|||||||
metadataTokens = []
|
metadataTokens = []
|
||||||
|
|
||||||
for tagKey, tagValue in self.__targetMediaDescriptor.getTags().items():
|
for tagKey, tagValue in self.__targetMediaDescriptor.getTags().items():
|
||||||
|
|
||||||
metadataTokens += [f"-metadata:g",
|
metadataTokens += [f"-metadata:g",
|
||||||
f"{tagKey}={tagValue}"]
|
f"{tagKey}={tagValue}"]
|
||||||
|
|
||||||
|
#HINT: With current ffmpeg version track metadata tags are not passed to the outfile
|
||||||
|
for td in self.__targetMediaDescriptor.getAllTrackDescriptors():
|
||||||
|
|
||||||
#NOTE: With current version track metadata tags are not passed to the outfile
|
for tagKey, tagValue in td.getTags().items():
|
||||||
targetTrackDescriptors = self.__targetMediaDescriptor.getReorderedTrackDescriptors()
|
|
||||||
|
|
||||||
|
|
||||||
for d in targetTrackDescriptors:
|
|
||||||
|
|
||||||
# changedTagValue = targetTrackDescriptors[changedTargetTrackSourceIndex].getTags()[changedTagKey]
|
|
||||||
# sourceSubIndex = sourceTrackDescriptors[changedTargetTrackSourceIndex].getSubIndex()
|
|
||||||
# addedTagValue = changedTrackDiff[MediaDescriptor.TAGS_KEY][DIFF_ADDED_KEY][addedTagKey]
|
|
||||||
|
|
||||||
# click.echo(f"addedTagValue={addedTagValue}")
|
|
||||||
# click.echo(f"sourceTrackDescriptors: subindex={[s.getSubIndex() for s in sourceTrackDescriptors]} sourceindex={[s.getSourceIndex() for s in sourceTrackDescriptors]} tags={[s.getTags() for s in sourceTrackDescriptors]}")
|
|
||||||
# click.echo(f"targetTrackDescriptors: subindex={[t.getSubIndex() for t in targetTrackDescriptors]} sourceindex={[t.getSourceIndex() for t in targetTrackDescriptors]} tags={[t.getTags() for t in targetTrackDescriptors]}")
|
|
||||||
# click.echo(f"changed track_index={changedTrackIndex} indicator={changedTargetTrackDescriptor.getType().indicator()} key={addedTagKey} value={addedTagValue} source_index={changedSourceTrackIndex}")
|
|
||||||
for tagKey, tagValue in d.getTags().items():
|
|
||||||
|
|
||||||
metadataTokens += [f"-metadata:s:{d.getType().indicator()}:{d.getSubIndex()}",
|
metadataTokens += [f"-metadata:s:{td.getType().indicator()}:{td.getSubIndex()}",
|
||||||
f"{tagKey}={tagValue}"]
|
f"{tagKey}={tagValue}"]
|
||||||
|
|
||||||
return metadataTokens
|
return metadataTokens
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def runJob(self,
|
def runJob(self,
|
||||||
sourcePath,
|
sourcePath,
|
||||||
targetPath,
|
targetPath,
|
||||||
|
|||||||
@@ -61,15 +61,6 @@ class MediaDescriptor:
|
|||||||
else:
|
else:
|
||||||
self.__trackDescriptors = []
|
self.__trackDescriptors = []
|
||||||
|
|
||||||
if MediaDescriptor.CLEAR_TAGS_FLAG_KEY in kwargs.keys():
|
|
||||||
if type(kwargs[MediaDescriptor.CLEAR_TAGS_FLAG_KEY]) is not 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_FLAG_KEY]
|
|
||||||
else:
|
|
||||||
self.__clearTags = False
|
|
||||||
|
|
||||||
if MediaDescriptor.JELLYFIN_ORDER_FLAG_KEY in kwargs.keys():
|
if MediaDescriptor.JELLYFIN_ORDER_FLAG_KEY in kwargs.keys():
|
||||||
if type(kwargs[MediaDescriptor.JELLYFIN_ORDER_FLAG_KEY]) is not bool:
|
if type(kwargs[MediaDescriptor.JELLYFIN_ORDER_FLAG_KEY]) is not bool:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
@@ -79,77 +70,6 @@ class MediaDescriptor:
|
|||||||
else:
|
else:
|
||||||
self.__jellyfinOrder = False
|
self.__jellyfinOrder = False
|
||||||
|
|
||||||
def getDefaultVideoTrack(self):
|
|
||||||
videoDefaultTracks = [
|
|
||||||
v
|
|
||||||
for v in self.getVideoTracks()
|
|
||||||
if TrackDisposition.DEFAULT in v.getDispositionSet()
|
|
||||||
]
|
|
||||||
if len(videoDefaultTracks) > 1:
|
|
||||||
raise ValueError(
|
|
||||||
"MediaDescriptor.getDefaultVideoTrack(): More than one default video track is not supported"
|
|
||||||
)
|
|
||||||
return videoDefaultTracks[0] if videoDefaultTracks else None
|
|
||||||
|
|
||||||
def getForcedVideoTrack(self):
|
|
||||||
videoForcedTracks = [
|
|
||||||
v
|
|
||||||
for v in self.getVideoTracks()
|
|
||||||
if TrackDisposition.FORCED in v.getDispositionSet()
|
|
||||||
]
|
|
||||||
if len(videoForcedTracks) > 1:
|
|
||||||
raise ValueError(
|
|
||||||
"MediaDescriptor.getForcedVideoTrack(): More than one forced video track is not supported"
|
|
||||||
)
|
|
||||||
return videoForcedTracks[0] if videoForcedTracks else None
|
|
||||||
|
|
||||||
def getDefaultAudioTrack(self):
|
|
||||||
audioDefaultTracks = [
|
|
||||||
a
|
|
||||||
for a in self.getAudioTracks()
|
|
||||||
if TrackDisposition.DEFAULT in a.getDispositionSet()
|
|
||||||
]
|
|
||||||
if len(audioDefaultTracks) > 1:
|
|
||||||
raise ValueError(
|
|
||||||
"MediaDescriptor.getDefaultAudioTrack(): More than one default audio track is not supported"
|
|
||||||
)
|
|
||||||
return audioDefaultTracks[0] if audioDefaultTracks else None
|
|
||||||
|
|
||||||
def getForcedAudioTrack(self):
|
|
||||||
audioForcedTracks = [
|
|
||||||
a
|
|
||||||
for a in self.getAudioTracks()
|
|
||||||
if TrackDisposition.FORCED in a.getDispositionSet()
|
|
||||||
]
|
|
||||||
if len(audioForcedTracks) > 1:
|
|
||||||
raise ValueError(
|
|
||||||
"MediaDescriptor.getForcedAudioTrack(): More than one forced audio track is not supported"
|
|
||||||
)
|
|
||||||
return audioForcedTracks[0] if audioForcedTracks else None
|
|
||||||
|
|
||||||
def getDefaultSubtitleTrack(self):
|
|
||||||
subtitleDefaultTracks = [
|
|
||||||
s
|
|
||||||
for s in self.getSubtitleTracks()
|
|
||||||
if TrackDisposition.DEFAULT in s.getDispositionSet()
|
|
||||||
]
|
|
||||||
if len(subtitleDefaultTracks) > 1:
|
|
||||||
raise ValueError(
|
|
||||||
"MediaDescriptor.getDefaultSubtitleTrack(): More than one default subtitle track is not supported"
|
|
||||||
)
|
|
||||||
return subtitleDefaultTracks[0] if subtitleDefaultTracks else None
|
|
||||||
|
|
||||||
def getForcedSubtitleTrack(self):
|
|
||||||
subtitleForcedTracks = [
|
|
||||||
s
|
|
||||||
for s in self.getSubtitleTracks()
|
|
||||||
if TrackDisposition.FORCED in s.getDispositionSet()
|
|
||||||
]
|
|
||||||
if len(subtitleForcedTracks) > 1:
|
|
||||||
raise ValueError(
|
|
||||||
"MediaDescriptor.getForcedSubtitleTrack(): More than one forced subtitle track is not supported"
|
|
||||||
)
|
|
||||||
return subtitleForcedTracks[0] if subtitleForcedTracks else None
|
|
||||||
|
|
||||||
def setDefaultSubTrack(self, trackType: TrackType, subIndex: int):
|
def setDefaultSubTrack(self, trackType: TrackType, subIndex: int):
|
||||||
for t in self.getAllTrackDescriptors():
|
for t in self.getAllTrackDescriptors():
|
||||||
@@ -166,7 +86,7 @@ class MediaDescriptor:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def checkDefaultAndForcedDispositions(self):
|
def checkConfiguration(self):
|
||||||
|
|
||||||
videoTracks = self.getVideoTracks()
|
videoTracks = self.getVideoTracks()
|
||||||
audioTracks = self.getAudioTracks()
|
audioTracks = self.getAudioTracks()
|
||||||
@@ -193,42 +113,37 @@ class MediaDescriptor:
|
|||||||
if len(set(sourceIndices)) < len(trackDescriptors):
|
if len(set(sourceIndices)) < len(trackDescriptors):
|
||||||
raise ValueError('Multiple streams originating from the same source stream')
|
raise ValueError('Multiple streams originating from the same source stream')
|
||||||
|
|
||||||
def getReorderedTrackDescriptors(self):
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def applyJellyfinOrder(self):
|
def applyJellyfinOrder(self):
|
||||||
|
"""Reorder subtracks in types with default the last, then make subindices flat again"""
|
||||||
|
|
||||||
# videoTracks = self.sortSubIndices(self.getVideoTracks())
|
# videoTracks = self.sortSubIndices(self.getVideoTracks())
|
||||||
# audioTracks = self.sortSubIndices(self.getAudioTracks())
|
# audioTracks = self.sortSubIndices(self.getAudioTracks())
|
||||||
# subtitleTracks = self.sortSubIndices(self.getSubtitleTracks())
|
# subtitleTracks = self.sortSubIndices(self.getSubtitleTracks())
|
||||||
|
|
||||||
|
self.checkConfiguration()
|
||||||
|
|
||||||
|
# from self.__trackDescriptors
|
||||||
videoTracks = self.getVideoTracks()
|
videoTracks = self.getVideoTracks()
|
||||||
audioTracks = self.getAudioTracks()
|
audioTracks = self.getAudioTracks()
|
||||||
subtitleTracks = self.getSubtitleTracks()
|
subtitleTracks = self.getSubtitleTracks()
|
||||||
|
|
||||||
if len([v for v in videoTracks if v.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
|
|
||||||
pass
|
|
||||||
if len([a for a in audioTracks if a.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
|
|
||||||
pass
|
|
||||||
if len([s for s in subtitleTracks if s.getDispositionFlag(TrackDisposition.DEFAULT)]):
|
|
||||||
pass
|
|
||||||
|
|
||||||
videoTracks.append(videoTracks.pop(videoTracks.index(videoTracks)))
|
defaultVideoTracks = [v for v in videoTracks if v.getDispositionFlag(TrackDisposition.DEFAULT)]
|
||||||
audioTracks.append(audioTracks.pop(audioTracks.index(audioTracks)))
|
defaultAudioTracks = [a for a in audioTracks if a.getDispositionFlag(TrackDisposition.DEFAULT)]
|
||||||
subtitleTracks.append(subtitleTracks.pop(subtitleTracks.index(subtitleTracks)))
|
defaultSubtitleTracks = [s for s in subtitleTracks if s.getDispositionFlag(TrackDisposition.DEFAULT)]
|
||||||
|
|
||||||
|
if defaultVideoTracks:
|
||||||
|
videoTracks.append(videoTracks.pop(videoTracks.index(defaultVideoTracks[0])))
|
||||||
|
self.sortSubIndices(videoTracks)
|
||||||
|
if defaultAudioTracks:
|
||||||
|
audioTracks.append(audioTracks.pop(audioTracks.index(defaultAudioTracks[0])))
|
||||||
|
self.sortSubIndices(audioTracks)
|
||||||
|
if defaultSubtitleTracks:
|
||||||
|
subtitleTracks.append(subtitleTracks.pop(subtitleTracks.index(defaultSubtitleTracks[0])))
|
||||||
|
self.sortSubIndices(subtitleTracks)
|
||||||
|
|
||||||
|
self.__trackDescriptors = videoTracks + audioTracks + subtitleTracks
|
||||||
|
self.sortIndices(self.__trackDescriptors)
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -268,43 +183,46 @@ class MediaDescriptor:
|
|||||||
def getTags(self):
|
def getTags(self):
|
||||||
return self.__mediaTags
|
return self.__mediaTags
|
||||||
|
|
||||||
|
|
||||||
def sortSubIndices(
|
def sortSubIndices(
|
||||||
self, descriptors: List[TrackDescriptor]
|
self, descriptors: List[TrackDescriptor]
|
||||||
) -> List[TrackDescriptor]:
|
) -> List[TrackDescriptor]:
|
||||||
subIndex = 0
|
subIndex = 0
|
||||||
for t in descriptors:
|
for d in descriptors:
|
||||||
t.setSubIndex(subIndex)
|
d.setSubIndex(subIndex)
|
||||||
subIndex += 1
|
subIndex += 1
|
||||||
return descriptors
|
return descriptors
|
||||||
|
|
||||||
|
def sortIndices(
|
||||||
|
self, descriptors: List[TrackDescriptor]
|
||||||
|
) -> List[TrackDescriptor]:
|
||||||
|
index = 0
|
||||||
|
for d in descriptors:
|
||||||
|
d.setIndex(index)
|
||||||
|
index += 1
|
||||||
|
return descriptors
|
||||||
|
|
||||||
|
|
||||||
def getAllTrackDescriptors(self) -> List[TrackDescriptor]:
|
def getAllTrackDescriptors(self) -> List[TrackDescriptor]:
|
||||||
return self.getVideoTracks() + self.getAudioTracks() + self.getSubtitleTracks()
|
return self.getVideoTracks() + self.getAudioTracks() + self.getSubtitleTracks()
|
||||||
|
|
||||||
def getVideoTracks(self) -> List[TrackDescriptor]:
|
def getVideoTracks(self) -> List[TrackDescriptor]:
|
||||||
return [
|
return [
|
||||||
v for v in self.__trackDescriptors.copy() if v.getType() == TrackType.VIDEO
|
v for v in self.__trackDescriptors if v.getType() == TrackType.VIDEO
|
||||||
]
|
]
|
||||||
|
|
||||||
def getAudioTracks(self) -> List[TrackDescriptor]:
|
def getAudioTracks(self) -> List[TrackDescriptor]:
|
||||||
return [
|
return [
|
||||||
a for a in self.__trackDescriptors.copy() if a.getType() == TrackType.AUDIO
|
a for a in self.__trackDescriptors if a.getType() == TrackType.AUDIO
|
||||||
]
|
]
|
||||||
|
|
||||||
def getSubtitleTracks(self) -> List[TrackDescriptor]:
|
def getSubtitleTracks(self) -> List[TrackDescriptor]:
|
||||||
return [
|
return [
|
||||||
s
|
s
|
||||||
for s in self.__trackDescriptors.copy()
|
for s in self.__trackDescriptors
|
||||||
if s.getType() == TrackType.SUBTITLE
|
if s.getType() == TrackType.SUBTITLE
|
||||||
]
|
]
|
||||||
|
|
||||||
# def getJellyfin(self):
|
|
||||||
# return self.__jellyfinOrder
|
|
||||||
#
|
|
||||||
# def setJellyfinOrder(self, state):
|
|
||||||
# self.__jellyfinOrder = state
|
|
||||||
|
|
||||||
def getClearTags(self):
|
|
||||||
return self.__clearTags
|
|
||||||
|
|
||||||
def compare(self, vsMediaDescriptor: Self):
|
def compare(self, vsMediaDescriptor: Self):
|
||||||
|
|
||||||
@@ -315,6 +233,10 @@ class MediaDescriptor:
|
|||||||
|
|
||||||
vsTags = vsMediaDescriptor.getTags()
|
vsTags = vsMediaDescriptor.getTags()
|
||||||
tags = self.getTags()
|
tags = self.getTags()
|
||||||
|
|
||||||
|
# tags ist leer
|
||||||
|
# click.echo(f"tags={tags} vsTags={vsTags}")
|
||||||
|
# raise click.Abort
|
||||||
|
|
||||||
# HINT: Some tags differ per file, for example creation_time, so these are removed before diff
|
# HINT: Some tags differ per file, for example creation_time, so these are removed before diff
|
||||||
for emt in MediaDescriptor.EXCLUDED_MEDIA_TAGS:
|
for emt in MediaDescriptor.EXCLUDED_MEDIA_TAGS:
|
||||||
@@ -332,7 +254,7 @@ class MediaDescriptor:
|
|||||||
|
|
||||||
# Target track configuration (from DB)
|
# Target track configuration (from DB)
|
||||||
# tracks = self.getAllTrackDescriptors()
|
# tracks = self.getAllTrackDescriptors()
|
||||||
tracks = self.getReorderedTrackDescriptors()
|
tracks = self.getAllTrackDescriptors() # filtern
|
||||||
numTracks = len(tracks)
|
numTracks = len(tracks)
|
||||||
|
|
||||||
# Current track configuration (of file)
|
# Current track configuration (of file)
|
||||||
@@ -386,12 +308,13 @@ class MediaDescriptor:
|
|||||||
|
|
||||||
def getImportFileTokens(self, use_sub_index: bool = True):
|
def getImportFileTokens(self, use_sub_index: bool = True):
|
||||||
|
|
||||||
reorderedTrackDescriptors = self.getReorderedTrackDescriptors()
|
# reorderedTrackDescriptors = self.getReorderedTrackDescriptors()
|
||||||
importFileTokens = []
|
importFileTokens = []
|
||||||
|
|
||||||
for rtd in reorderedTrackDescriptors:
|
#for rtd in reorderedTrackDescriptors:
|
||||||
|
for td in self.__trackDescriptors:
|
||||||
|
|
||||||
importedFilePath = rtd.getExternalSourceFilePath()
|
importedFilePath = td.getExternalSourceFilePath()
|
||||||
|
|
||||||
if importedFilePath:
|
if importedFilePath:
|
||||||
importFileTokens += [
|
importFileTokens += [
|
||||||
@@ -403,17 +326,18 @@ class MediaDescriptor:
|
|||||||
|
|
||||||
def getInputMappingTokens(self, use_sub_index: bool = True, only_video: bool = False):
|
def getInputMappingTokens(self, use_sub_index: bool = True, only_video: bool = False):
|
||||||
|
|
||||||
reorderedTrackDescriptors = self.getReorderedTrackDescriptors()
|
# reorderedTrackDescriptors = self.getReorderedTrackDescriptors()
|
||||||
inputMappingTokens = []
|
inputMappingTokens = []
|
||||||
|
|
||||||
filePointer = 1
|
filePointer = 1
|
||||||
for rtd in reorderedTrackDescriptors:
|
#for rtd in reorderedTrackDescriptors:
|
||||||
|
for td in self.__trackDescriptors:
|
||||||
|
|
||||||
trackType = rtd.getType()
|
trackType = td.getType()
|
||||||
|
|
||||||
if (trackType == TrackType.VIDEO or not only_video):
|
if (trackType == TrackType.VIDEO or not only_video):
|
||||||
|
|
||||||
importedFilePath = rtd.getExternalSourceFilePath()
|
importedFilePath = td.getExternalSourceFilePath()
|
||||||
|
|
||||||
if use_sub_index:
|
if use_sub_index:
|
||||||
|
|
||||||
@@ -427,15 +351,15 @@ class MediaDescriptor:
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
if rtd.getCodec() != TrackDescriptor.CODEC_PGS:
|
if td.getCodec() != TrackDescriptor.CODEC_PGS:
|
||||||
inputMappingTokens += [
|
inputMappingTokens += [
|
||||||
"-map",
|
"-map",
|
||||||
f"0:{trackType.indicator()}:{rtd.getSubIndex()}",
|
f"0:{trackType.indicator()}:{td.getSubIndex()}",
|
||||||
]
|
]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if rtd.getCodec() != TrackDescriptor.CODEC_PGS:
|
if td.getCodec() != TrackDescriptor.CODEC_PGS:
|
||||||
inputMappingTokens += ["-map", f"0:{rtd.getIndex()}"]
|
inputMappingTokens += ["-map", f"0:{td.getIndex()}"]
|
||||||
|
|
||||||
return inputMappingTokens
|
return inputMappingTokens
|
||||||
|
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ class MediaDetailsScreen(Screen):
|
|||||||
#HINT: This is None if the filename did not match anything in database
|
#HINT: This is None if the filename did not match anything in database
|
||||||
self.__currentPattern = self.__mediaFileProperties.getPattern()
|
self.__currentPattern = self.__mediaFileProperties.getPattern()
|
||||||
|
|
||||||
|
# keine tags vorhanden
|
||||||
self.__targetMediaDescriptor = self.__currentPattern.getMediaDescriptor() if self.__currentPattern is not None else None
|
self.__targetMediaDescriptor = self.__currentPattern.getMediaDescriptor() if self.__currentPattern is not None else None
|
||||||
|
|
||||||
# Enumerating differences between media descriptors
|
# Enumerating differences between media descriptors
|
||||||
@@ -506,13 +507,18 @@ class MediaDetailsScreen(Screen):
|
|||||||
|
|
||||||
self.highlightPattern(False)
|
self.highlightPattern(False)
|
||||||
|
|
||||||
|
for tagKey, tagValue in self.__currentMediaDescriptor.getTags().items():
|
||||||
|
self.__tac.updateMediaTag(patternId, tagKey, tagValue)
|
||||||
|
|
||||||
for trackDescriptor in self.__currentMediaDescriptor.getAllTrackDescriptors():
|
for trackDescriptor in self.__currentMediaDescriptor.getAllTrackDescriptors():
|
||||||
self.__tc.addTrack(trackDescriptor, patternId = patternId)
|
self.__tc.addTrack(trackDescriptor, patternId = patternId)
|
||||||
|
|
||||||
|
|
||||||
def action_new_pattern(self):
|
def action_new_pattern(self):
|
||||||
|
|
||||||
if not self.__currentMediaDescriptor.checkDefaultAndForcedDispositions():
|
try:
|
||||||
|
self.__currentMediaDescriptor.checkConfiguration()
|
||||||
|
except ValueError:
|
||||||
return
|
return
|
||||||
|
|
||||||
selectedShowDescriptor = self.getSelectedShowDescriptor()
|
selectedShowDescriptor = self.getSelectedShowDescriptor()
|
||||||
|
|||||||
@@ -62,7 +62,13 @@ class Pattern(Base):
|
|||||||
|
|
||||||
kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY] = []
|
kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY] = []
|
||||||
|
|
||||||
|
# Set ordered subindices
|
||||||
|
subIndexCounter = {}
|
||||||
for track in self.tracks:
|
for track in self.tracks:
|
||||||
kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY].append(track.getDescriptor())
|
trackType = track.getType()
|
||||||
|
if not trackType in subIndexCounter.keys():
|
||||||
|
subIndexCounter[trackType] = 0
|
||||||
|
kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY].append(track.getDescriptor(subIndex = subIndexCounter[trackType]))
|
||||||
|
subIndexCounter[trackType] += 1
|
||||||
|
|
||||||
return MediaDescriptor(**kwargs)
|
return MediaDescriptor(**kwargs)
|
||||||
|
|||||||
@@ -241,6 +241,9 @@ class TrackDescriptor:
|
|||||||
def getIndex(self):
|
def getIndex(self):
|
||||||
return self.__index
|
return self.__index
|
||||||
|
|
||||||
|
def setIndex(self, index):
|
||||||
|
self.__index = index
|
||||||
|
|
||||||
def getSourceIndex(self):
|
def getSourceIndex(self):
|
||||||
return self.__sourceIndex
|
return self.__sourceIndex
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user