@ -299,7 +299,8 @@ def generateOutputTokens(filepath, format, ext):
return [ ' -f ' , format , f " { filepath } . { ext } " ]
def generateAudioTokens ( context , index , layout ) :
def generateAudioEncodingTokens ( context , index , layout ) :
""" Generates ffmpeg options for one output audio stream including channel remapping, codec and bitrate """
if layout == STREAM_LAYOUT_6_1 :
return [ f " -c:a: { index } " ,
@ -342,16 +343,25 @@ def generateClearTokens(streams):
return clearTokens
def generateDispositionTokens ( subDescriptor ) :
def getDispositionFlags ( subStreamDescriptor ) :
return { k for ( k , v ) in subStreamDescriptor [ ' disposition ' ] . items ( ) if v == 1 } if ' disposition ' in subStreamDescriptor . keys ( ) else set ( )
# def generateDispositionTokens(subDescriptor):
def generateDispositionTokens ( subDescriptor , modifyOrder = [ ] ) :
""" -disposition:s:X default+forced """
dispositionTokens = [ ]
for subStreamIndex in range ( len ( subDescriptor ) ) :
subStream = subDescriptor [ subStreamIndex ]
sourceSubStreamIndex = modifyOrder [ subStreamIndex ] if modifyOrder else subStreamIndex
subStream = subDescriptor [ sourceSubStreamIndex ]
streamType = subStream [ ' codec_type ' ] [ 0 ] # v|a|s
dispositionFlags = { k for ( k , v ) in subStream [ ' disposition ' ] . items ( ) if v == 1 } if ' disposition ' in subStream . keys ( ) else set ( )
dispositionFlags = getDispositionFlags ( subStream )
if dispositionFlags :
dispositionTokens + = [ f " -disposition: { streamType } : { subStreamIndex } " , ' + ' . join ( dispositionFlags ) ]
@ -360,6 +370,8 @@ def generateDispositionTokens(subDescriptor):
return dispositionTokens
# def countStreamDispositions(subStreamDescriptor):
# return len([l for (k,v) in subStreamDescriptor['disposition'].items()])
def searchSubtitleFiles ( dir , prefix ) :
@ -529,6 +541,7 @@ def convert(ctx,
context [ ' import_subtitles ' ] = ( subtitle_directory and subtitle_prefix )
availableFileSubtitleDescriptors = searchSubtitleFiles ( subtitle_directory , subtitle_prefix ) if context [ ' import_subtitles ' ] else [ ]
# Overwrite audio tags if set
audioLanguages = audio_language
audioTitles = audio_title
@ -546,13 +559,18 @@ def convert(ctx,
# Process crop parameters
context [ ' perform_crop ' ] = ( crop != ' none ' )
if context [ ' perform_crop ' ] :
c rop Tokens = crop . split ( ' , ' )
if c rop Tokens and len ( c rop Tokens) == 2 :
c ontext[ ' crop_start ' ] , context [ ' crop_length ' ] = crop . split ( ' , ' )
c Tokens = crop . split ( ' , ' )
if c Tokens and len ( c Tokens) == 2 :
c ropStart, cropLength = crop . split ( ' , ' )
else :
context [ ' crop_start ' ] = DEFAULT_CROP_START
context [ ' crop_length ' ] = DEFAULT_CROP_LENGTH
click . echo ( f " crop start= { context [ ' crop_start ' ] } length= { context [ ' crop_length ' ] } " )
cropStart = DEFAULT_CROP_START
cropLength = DEFAULT_CROP_LENGTH
click . echo ( f " crop start= { cropStart } length= { cropLength } " )
cropTokens = generateCropTokens ( int ( cropStart ) , int ( cropLength ) )
else :
cropTokens = [ ]
job_index = 0
@ -597,6 +615,8 @@ def convert(ctx,
else :
file_index + = 1
matchingFileSubtitleDescriptors = sorted ( [ d for d in availableFileSubtitleDescriptors if d [ ' season ' ] == season and d [ ' episode ' ] == episode ] , key = lambda d : d [ ' stream ' ] ) if availableFileSubtitleDescriptors else [ ]
print ( f " season= { season } episode= { episode } file= { file_index } " )
@ -628,9 +648,10 @@ def convert(ctx,
click . echo ( f " File with path { sourcePath } does not contain any audiovisual data, skipping ... " )
continue
###
## ## ##
targetStreamDescriptor = sourceStreamDescriptor . copy ( )
## #
## ## # #
click . echo ( ' \n Source streams: ' )
@ -658,7 +679,7 @@ def convert(ctx,
if forcedSubtitle == - 1 and numForcedSubtitleStreams > 1 :
forcedSubtitle = click . prompt ( " More than one forced subtitle stream detected! Please select stream " , type = int )
# Fix multipl e default/forced tags
# Defin e default/forced tags
if defaultAudio != - 1 :
for substreamIndex in range ( len ( targetStreamDescriptor [ STREAM_TYPE_AUDIO ] ) ) :
targetStreamDescriptor [ STREAM_TYPE_AUDIO ] [ substreamIndex ] [ ' disposition ' ] [ ' default ' ] = 1 if substreamIndex == defaultAudio else 0
@ -670,7 +691,7 @@ def convert(ctx,
targetStreamDescriptor [ STREAM_TYPE_SUBTITLE ] [ substreamIndex ] [ ' disposition ' ] [ ' default ' ] = 1 if substreamIndex == defaultSubtitle else 0
if forcedSubtitle != - 1 :
for substreamIndex in range ( len ( targetStreamDescriptor [ STREAM_TYPE_SUBTITLE ] ) ) :
targetStreamDescriptor [ STREAM_TYPE_SUBTITLE ] [ substreamIndex ] [ ' disposition ' ] [ ' for d ed' ] = 1 if substreamIndex == forcedSubtitle else 0
targetStreamDescriptor [ STREAM_TYPE_SUBTITLE ] [ substreamIndex ] [ ' disposition ' ] [ ' for c ed' ] = 1 if substreamIndex == forcedSubtitle else 0
# Set language and title in source stream descriptors if given per command line option
@ -687,7 +708,6 @@ def convert(ctx,
targetStreamDescriptor [ STREAM_TYPE_SUBTITLE ] [ streamIndex ] [ ' tags ' ] [ ' title ' ] = subtitleTitles [ streamIndex ]
click . echo ( ' \n Target streams: ' )
for aStream in targetStreamDescriptor [ STREAM_TYPE_AUDIO ] :
click . echo ( f " audio stream { aStream [ ' sub_index ' ] } lang= { aStream [ ' tags ' ] [ ' language ' ] } title= { aStream [ ' tags ' ] [ ' title ' ] } default= { aStream [ ' disposition ' ] [ ' default ' ] } forced= { aStream [ ' disposition ' ] [ ' forced ' ] } " )
@ -695,10 +715,15 @@ def convert(ctx,
click . echo ( f " subtitle stream { sStream [ ' sub_index ' ] } lang= { sStream [ ' tags ' ] [ ' language ' ] } title= { sStream [ ' tags ' ] [ ' title ' ] } default= { sStream [ ' disposition ' ] [ ' default ' ] } forced= { sStream [ ' disposition ' ] [ ' forced ' ] } " )
# Find source stream order
audioStreamSourceOrder = list ( range ( len ( sourceStreamDescriptor [ STREAM_TYPE_AUDIO ] ) ) )
subtitleStreamSourceOrder = list ( range ( len ( sourceStreamDescriptor [ STREAM_TYPE_SUBTITLE ] ) ) )
numSourceAudioSubStreams = len ( sourceStreamDescriptor [ STREAM_TYPE_AUDIO ] )
numSourceSubtitleSubStreams = len ( sourceStreamDescriptor [ STREAM_TYPE_SUBTITLE ] )
# Stream order is just a list of integer
audioStreamSourceOrder = list ( range ( numSourceAudioSubStreams ) )
subtitleStreamSourceOrder = list ( range ( numSourceSubtitleSubStreams ) )
# In order for the jellyfin media web UI to work properly the default/forced stream has to be the last in the sequence
if jellyfin :
defaultTargetAudioStreams = [ a for a in targetStreamDescriptor [ STREAM_TYPE_AUDIO ] if a [ ' disposition ' ] [ ' default ' ] == 1 ]
@ -710,33 +735,121 @@ def convert(ctx,
subtitleStreamSourceOrder = getModifiedStreamOrder ( len ( sourceStreamDescriptor [ STREAM_TYPE_SUBTITLE ] ) , defaultTargetSubtitleStreams [ 0 ] [ ' sub_index ' ] )
# audioDispositionTokens = generateDispositionTokens(targetStreamDescriptor[STREAM_TYPE_AUDIO])
# subtitleDispositionTokens = generateDispositionTokens(targetStreamDescriptor[STREAM_TYPE_SUBTITLE])
mappingVideoTokens = [ ' -map ' , ' 0:v:0 ' ]
audioDispositionTokens = generateDispositionTokens ( targetStreamDescriptor [ STREAM_TYPE_AUDIO ] , modifyOrder = audioStreamSourceOrder )
subtitleDispositionTokens = generateDispositionTokens ( targetStreamDescriptor [ STREAM_TYPE_SUBTITLE ] , modifyOrder = subtitleStreamSourceOrder )
mappingVideoTokens = [ ' -map ' , ' 0:v:0 ' ]
mappingTokens = mappingVideoTokens . copy ( )
dispositionTokens = [ ]
audio Tokens = [ ]
audio Encoding Tokens = [ ]
audioMetadataTokens = [ ]
for audioStreamIndex in range ( len ( source StreamDescriptor[ STREAM_TYPE_AUDIO ] ) ) :
for audioStreamIndex in range ( len ( target StreamDescriptor[ STREAM_TYPE_AUDIO ] ) ) :
# Modify selected source audio stream for jellyfin if required
sourceAudioStreamIndex = audioStreamSourceOrder [ audioStreamIndex ]
sourceAudioStream = sourceStreamDescriptor [ STREAM_TYPE_AUDIO ] [ sourceAudioStreamIndex ]
# Create mapping and ffmpeg options for audio streams
# mappingTokens += ['-map', f"0:a:{sourceAudioStreamIndex['src_sub_index']}"]
# Add audio mapping tokens to list of general mapping tokens
mappingTokens + = [ ' -map ' , f " 0:a: { sourceAudioStreamIndex } " ]
# audioTokens += generateAudioTokens(context, sourceAudioStream['src_sub_index'], sourceAudioStream['layout'])
audioTokens + = generateAudioTokens ( context , sourceAudioStreamIndex , sourceAudioStream [ ' audio_layout ' ] )
if sourceStreamDescriptor [ STREAM_TYPE_AUDIO ] [ audioStreamIndex ] [ ' tags ' ] [ ' language ' ] != targetStreamDescriptor [ STREAM_TYPE_AUDIO ] [ audioStreamIndex ] [ ' tags ' ] [ ' language ' ] :
audioMetadataTokens + = [ f " -metadata:s:a: { audioStreamIndex } " , f " language= { targetStreamDescriptor [ STREAM_TYPE_AUDIO ] [ audioStreamIndex ] [ ' tags ' ] [ ' language ' ] } " ]
targetAudioStream = targetStreamDescriptor [ STREAM_TYPE_AUDIO ] [ audioStreamIndex ]
# audioEncodingTokens += generateAudioEncodingTokens(context, sourceAudioStream['src_sub_index'], sourceAudioStream['layout'])
audioEncodingTokens + = generateAudioEncodingTokens ( context , audioStreamIndex , targetAudioStream [ ' audio_layout ' ] )
if sourceStreamDescriptor [ STREAM_TYPE_AUDIO ] [ sourceAudioStreamIndex ] [ ' tags ' ] [ ' language ' ] != targetStreamDescriptor [ STREAM_TYPE_AUDIO ] [ audioStreamIndex ] [ ' tags ' ] [ ' language ' ] :
audioMetadataTokens + = [ f " -metadata:s:a: { audioStreamIndex } " , f " language= { targetStreamDescriptor [ STREAM_TYPE_AUDIO ] [ sourceAudioStreamIndex ] [ ' tags ' ] [ ' language ' ] } " ]
if sourceStreamDescriptor [ STREAM_TYPE_AUDIO ] [ sourceAudioStreamIndex ] [ ' tags ' ] [ ' title ' ] != targetStreamDescriptor [ STREAM_TYPE_AUDIO ] [ audioStreamIndex ] [ ' tags ' ] [ ' title ' ] :
audioMetadataTokens + = [ f " -metadata:s:a: { audioStreamIndex } " , f " title= { targetStreamDescriptor [ STREAM_TYPE_AUDIO ] [ sourceAudioStreamIndex ] [ ' tags ' ] [ ' title ' ] } " ]
# targetStreamDescriptor[STREAM_TYPE_AUDIO][audioStreamIndex]['disposition']['default'] = 1 if streamIndex == defaultAudio else 0
subtitleImportFileTokens = [ ]
subtitleMetadataTokens = [ ]
if context [ ' import_subtitles ' ] and numSourceSubtitleSubStreams != len ( matchingFileSubtitleDescriptors ) :
click . echo ( f " The number of subtitle streams found in file with path { sourcePath } is different from the number of subtitle streams provided by matching imported files, skipping ... " )
continue
# 0: Quelle f1 = forced
# 1: QUelle f2 = full
for subtitleStreamIndex in range ( len ( targetStreamDescriptor [ STREAM_TYPE_SUBTITLE ] ) ) :
# Modify selected source subtitle stream for jellyfin if required
sourceSubtitleStreamIndex = subtitleStreamSourceOrder [ subtitleStreamIndex ]
if context [ ' import_subtitles ' ] :
fileSubtitleDescriptor = matchingFileSubtitleDescriptors [ subtitleStreamIndex ] # original order
subtitleImportFileTokens + = [ ' -i ' , fileSubtitleDescriptor [ ' path ' ] ] # original order
# Create mapping for subtitle streams when imported from files
mappingTokens + = [ ' -map ' , f " { sourceSubtitleStreamIndex + 1 } :s:0 " ] # modified order
if fileSubtitleDescriptor [ ' language ' ] != targetStreamDescriptor [ STREAM_TYPE_SUBTITLE ] [ subtitleStreamIndex ] [ ' tags ' ] [ ' language ' ] :
subtitleMetadataTokens + = [ f " -metadata:s:s: { sourceSubtitleStreamIndex } " , f " language= { targetStreamDescriptor [ STREAM_TYPE_SUBTITLE ] [ subtitleStreamIndex ] [ ' tags ' ] [ ' language ' ] } " ]
subtitleMetadataTokens + = [ f " -metadata:s:s: { sourceSubtitleStreamIndex } " , f " title= { targetStreamDescriptor [ STREAM_TYPE_SUBTITLE ] [ subtitleStreamIndex ] [ ' tags ' ] [ ' title ' ] } " ]
else :
# Add subtitle mapping tokens to list of general mapping tokens
mappingTokens + = [ ' -map ' , f " 0:s: { sourceSubtitleStreamIndex } " ]
if sourceStreamDescriptor [ STREAM_TYPE_SUBTITLE ] [ sourceSubtitleStreamIndex ] [ ' tags ' ] [ ' language ' ] != targetStreamDescriptor [ STREAM_TYPE_SUBTITLE ] [ subtitleStreamIndex ] [ ' tags ' ] [ ' language ' ] :
subtitleMetadataTokens + = [ f " -metadata:s:s: { subtitleStreamIndex } " , f " language= { targetStreamDescriptor [ STREAM_TYPE_SUBTITLE ] [ subtitleStreamIndex ] [ ' tags ' ] [ ' language ' ] } " ]
if sourceStreamDescriptor [ STREAM_TYPE_SUBTITLE ] [ sourceSubtitleStreamIndex ] [ ' tags ' ] [ ' title ' ] != targetStreamDescriptor [ STREAM_TYPE_SUBTITLE ] [ subtitleStreamIndex ] [ ' tags ' ] [ ' title ' ] :
subtitleMetadataTokens + = [ f " -metadata:s:s: { subtitleStreamIndex } " , f " title= { targetStreamDescriptor [ STREAM_TYPE_SUBTITLE ] [ subtitleStreamIndex ] [ ' tags ' ] [ ' title ' ] } " ]
# # Reorder audio stream descriptors and create disposition options if default is given per command line option
# if defaultAudio == -1:
# sourceAudioStreams = audioStreams
# else:
# for streamIndex in range(len(audioStreams)):
# audioStreams[streamIndex]['disposition']['default'] = 1 if streamIndex == defaultAudio else 0
#
# sourceAudioStreams = getReorderedSubstreams(audioStreams, defaultAudio) if jellyfin else audioStreams
#
# dispositionTokens += generateDispositionTokens(sourceAudioStreams)
#
# # Set forced tag in subtitle descriptor if given per command line option
# if forcedSubtitle != -1:
# for streamIndex in range(len(subtitleStreams)):
# subtitleStreams[streamIndex]['disposition']['forced'] = 1 if streamIndex == forcedSubtitle else 0
#
# # Reorder subtitle stream descriptors and create disposition options if default is given per command line option
# if defaultSubtitle == -1:
# sourceSubtitleStreams = subtitleStreams
# else:
# for streamIndex in range(len(subtitleStreams)):
# subtitleStreams[streamIndex]['disposition']['default'] = 1 if streamIndex == defaultSubtitle else 0
#
# sourceSubtitleStreams = getReorderedSubstreams(subtitleStreams, defaultSubtitle) if jellyfin else subtitleStreams
#
# dispositionTokens += generateDispositionTokens(sourceSubtitleStreams)
#
if sourceStreamDescriptor [ STREAM_TYPE_AUDIO ] [ audioStreamIndex ] [ ' tags ' ] [ ' title ' ] != targetStreamDescriptor [ STREAM_TYPE_AUDIO ] [ audioStreamIndex ] [ ' tags ' ] [ ' title ' ] :
audioMetadataTokens + = [ f " -metadata:s:a: { audioStreamIndex } " , f " title= { targetStreamDescriptor [ STREAM_TYPE_AUDIO ] [ audioStreamIndex ] [ ' tags ' ] [ ' title ' ] } " ]
@ -747,17 +860,12 @@ def convert(ctx,
commandTokens = COMMAND_TOKENS + [ ' -i ' , sourcePath ]
subtitleFileTokens = [ ]
subtitleMetadataTokens = [ ]
# matchingSubtitles = []
# if context['import_subtitles']:
#
# subtitles = [a for a in availableFileSubtitleDescriptors if a['season'] == season and a['episode'] == episode]
# mSubtitles = sorted(subtitles, key=lambda d: d['stream'])
#
# for sfd in mSubtitles:
# subtitleFileTokens += ['-i', sfd['path']]
#
# for streamIndex in range(len(mSubtitles)):
# mSubtitles[streamIndex]['forced'] = 1 if forcedSubtitle != -1 and streamIndex == forcedSubtitle else 0
@ -863,19 +971,19 @@ def convert(ctx,
if video_encoder == ' av1 ' :
commandSequence = ( commandTokens
+ subtitle FileTokens
+ subtitle Import FileTokens
+ mappingTokens
+ dispositionTokens
+ audioMetadataTokens
+ subtitleMetadataTokens
+ audioTokens
+ generateAV1Tokens ( q , preset ) + audioTokens )
+ audioDispositionTokens
+ subtitleDispositionTokens
+ audioEncodingTokens
+ generateAV1Tokens ( q , preset ) + audioEncodingTokens )
if clear_metadata :
commandSequence + = generateClearTokens ( sourceStreamDescriptor )
if context [ ' perform_crop ' ] :
commandSequence + = generateCropTokens ( context [ ' crop_start ' ] , context [ ' crop_length ' ] )
commandSequence + = cropTokens
commandSequence + = generateOutputTokens ( targetFilename , DEFAULT_FILE_FORMAT , DEFAULT_FILE_EXTENSION )
@ -889,8 +997,7 @@ def convert(ctx,
commandSequence1 = commandTokens + mappingVideoTokens + generateVP9Pass1Tokens ( q )
if context [ ' perform_crop ' ] :
commandSequence1 + = generateCropTokens ( context [ ' crop_start ' ] , context [ ' crop_length ' ] )
commandSequence1 + = cropTokens
commandSequence1 + = NULL_TOKENS
@ -904,22 +1011,23 @@ def convert(ctx,
commandSequence2 = ( commandTokens
+ subtitle FileTokens
+ subtitle Import FileTokens
+ mappingTokens
+ audioMetadataTokens
+ subtitleMetadataTokens
+ audioDispositionTokens
+ subtitleDispositionTokens
+ dispositionTokens )
if denoise :
commandSequence2 + = generateDenoiseTokens ( )
commandSequence2 + = generateVP9Pass2Tokens ( q ) + audio Tokens
commandSequence2 + = generateVP9Pass2Tokens ( q ) + audio Encoding Tokens
if clear_metadata :
commandSequence2 + = generateClearTokens ( sourceStreamDescriptor )
if context [ ' perform_crop ' ] :
commandSequence2 + = generateCropTokens ( context [ ' crop_start ' ] , context [ ' crop_length ' ] )
commandSequence2 + = cropTokens
commandSequence2 + = generateOutputTokens ( targetFilename , DEFAULT_FILE_FORMAT , DEFAULT_FILE_EXTENSION )