alpha 0.0.1
This commit is contained in:
75
bin/ffx.py
75
bin/ffx.py
@@ -118,9 +118,6 @@ def shows(ctx):
|
|||||||
|
|
||||||
@click.argument('paths', nargs=-1)
|
@click.argument('paths', nargs=-1)
|
||||||
|
|
||||||
@click.option("-t", "--tmdb", is_flag=True, default=False)
|
|
||||||
@click.option("-j", "--jellyfin", is_flag=True, default=False)
|
|
||||||
|
|
||||||
@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=FfxController.DEFAULT_VIDEO_ENCODER, help=f"Target video encoder (vp9 or av1) default: {FfxController.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}")
|
||||||
@@ -135,11 +132,6 @@ def shows(ctx):
|
|||||||
@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')
|
||||||
|
|
||||||
@click.option('-ss', '--subtitle-language', type=str, multiple=True, help='Subtitle stream language(s)')
|
|
||||||
@click.option('-st', '--subtitle-title', type=str, multiple=True, help='Subtitle stream title(s)')
|
|
||||||
|
|
||||||
@click.option('-ds', '--default-subtitle', type=int, default=-1, help='Index of default subtitle stream')
|
|
||||||
@click.option('-fs', '--forced-subtitle', type=int, default=-1, help='Index of forced subtitle stream') # (including default audio stream tag)
|
|
||||||
|
|
||||||
@click.option('-as', '--audio-language', type=str, multiple=True, help='Audio stream language(s)')
|
@click.option('-as', '--audio-language', type=str, multiple=True, help='Audio stream language(s)')
|
||||||
@click.option('-at', '--audio-title', type=str, multiple=True, help='Audio stream title(s)')
|
@click.option('-at', '--audio-title', type=str, multiple=True, help='Audio stream title(s)')
|
||||||
@@ -148,23 +140,27 @@ def shows(ctx):
|
|||||||
@click.option('-da', '--forced-audio', type=int, default=-1, help='Index of forced audio stream')
|
@click.option('-da', '--forced-audio', type=int, default=-1, help='Index of forced audio stream')
|
||||||
|
|
||||||
|
|
||||||
|
@click.option('-ss', '--subtitle-language', type=str, multiple=True, help='Subtitle stream language(s)')
|
||||||
|
@click.option('-st', '--subtitle-title', type=str, multiple=True, help='Subtitle stream title(s)')
|
||||||
|
|
||||||
|
@click.option('-ds', '--default-subtitle', type=int, default=-1, help='Index of default subtitle stream')
|
||||||
|
@click.option('-fs', '--forced-subtitle', type=int, default=-1, help='Index of forced subtitle stream') # (including default audio stream tag)
|
||||||
|
|
||||||
|
|
||||||
@click.option("--crop", is_flag=False, flag_value="default", default="none")
|
@click.option("--crop", is_flag=False, flag_value="default", default="none")
|
||||||
|
|
||||||
@click.option("-o", "--output-directory", type=str, default='')
|
@click.option("-o", "--output-directory", type=str, default='')
|
||||||
|
|
||||||
|
|
||||||
@click.option("-c", "--clear-metadata", is_flag=True, default=False)
|
|
||||||
@click.option("-d", "--denoise", is_flag=True, default=False)
|
@click.option("-d", "--denoise", 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("--dry-run", is_flag=True, default=False)
|
@click.option("--dry-run", is_flag=True, default=False)
|
||||||
|
|
||||||
|
|
||||||
def convert(ctx,
|
def convert(ctx,
|
||||||
paths,
|
paths,
|
||||||
tmdb,
|
|
||||||
jellyfin,
|
|
||||||
label,
|
label,
|
||||||
video_encoder,
|
video_encoder,
|
||||||
quality,
|
quality,
|
||||||
@@ -174,18 +170,22 @@ def convert(ctx,
|
|||||||
dts_bitrate,
|
dts_bitrate,
|
||||||
subtitle_directory,
|
subtitle_directory,
|
||||||
subtitle_prefix,
|
subtitle_prefix,
|
||||||
subtitle_language,
|
|
||||||
subtitle_title,
|
|
||||||
default_subtitle,
|
|
||||||
forced_subtitle,
|
|
||||||
audio_language,
|
audio_language,
|
||||||
audio_title,
|
audio_title,
|
||||||
default_audio,
|
default_audio,
|
||||||
forced_audio,
|
forced_audio,
|
||||||
|
|
||||||
|
subtitle_language,
|
||||||
|
subtitle_title,
|
||||||
|
default_subtitle,
|
||||||
|
forced_subtitle,
|
||||||
|
|
||||||
crop,
|
crop,
|
||||||
output_directory,
|
output_directory,
|
||||||
clear_metadata,
|
|
||||||
denoise,
|
denoise,
|
||||||
|
no_tmdb,
|
||||||
|
no_jellyfin,
|
||||||
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
|
||||||
|
|
||||||
@@ -202,8 +202,8 @@ def convert(ctx,
|
|||||||
|
|
||||||
context['video_encoder'] = VideoEncoder.fromLabel(video_encoder)
|
context['video_encoder'] = VideoEncoder.fromLabel(video_encoder)
|
||||||
|
|
||||||
context['jellyfin'] = jellyfin
|
context['jellyfin'] = not no_jellyfin
|
||||||
context['tmdb'] = tmdb
|
context['tmdb'] = not no_tmdb
|
||||||
|
|
||||||
context['import_subtitles'] = (subtitle_directory and subtitle_prefix)
|
context['import_subtitles'] = (subtitle_directory and subtitle_prefix)
|
||||||
if context['import_subtitles']:
|
if context['import_subtitles']:
|
||||||
@@ -237,12 +237,6 @@ 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']}")
|
||||||
|
|
||||||
|
|
||||||
# ## Conversion parameters
|
|
||||||
#
|
|
||||||
# # Parse subtitle files
|
|
||||||
#
|
|
||||||
# availableFileSubtitleDescriptors = searchSubtitleFiles(subtitle_directory, subtitle_prefix) if context['import_subtitles'] else []
|
|
||||||
# sc = ShowController(context)
|
|
||||||
tc = TmdbController()
|
tc = TmdbController()
|
||||||
|
|
||||||
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]
|
||||||
@@ -270,6 +264,7 @@ def convert(ctx,
|
|||||||
|
|
||||||
click.echo(f"Pattern matching: {'No' if currentPattern is None else 'Yes'}")
|
click.echo(f"Pattern matching: {'No' if currentPattern is None else 'Yes'}")
|
||||||
|
|
||||||
|
fileBasename = ''
|
||||||
|
|
||||||
if currentPattern is None:
|
if currentPattern is None:
|
||||||
|
|
||||||
@@ -324,8 +319,6 @@ def convert(ctx,
|
|||||||
audioTokens = fc.generateAudioEncodingTokens()
|
audioTokens = fc.generateAudioEncodingTokens()
|
||||||
click.echo(f"Audio Tokens: {audioTokens}")
|
click.echo(f"Audio Tokens: {audioTokens}")
|
||||||
|
|
||||||
tmdbFileBasename = ''
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
# Case pattern matching
|
# Case pattern matching
|
||||||
@@ -333,13 +326,15 @@ def convert(ctx,
|
|||||||
targetMediaDescriptor = currentPattern.getMediaDescriptor()
|
targetMediaDescriptor = currentPattern.getMediaDescriptor()
|
||||||
currentShowDescriptor = currentPattern.getShowDescriptor()
|
currentShowDescriptor = currentPattern.getShowDescriptor()
|
||||||
|
|
||||||
label = currentShowDescriptor.getFilenamePrefix()
|
|
||||||
|
if context['tmdb']:
|
||||||
|
|
||||||
tmdbResult = tc.queryTmdb(currentShowDescriptor.getId(), mediaFileProperties.getSeason(), mediaFileProperties.getEpisode())
|
tmdbResult = tc.queryTmdb(currentShowDescriptor.getId(), mediaFileProperties.getSeason(), mediaFileProperties.getEpisode())
|
||||||
|
|
||||||
# click.echo(f"{tmdbResult}")
|
# click.echo(f"{tmdbResult}")
|
||||||
|
|
||||||
tmdbFileBasename = tc.getEpisodeFileBasename(currentShowDescriptor.getFilenamePrefix(),
|
if tmdbResult:
|
||||||
|
fileBasename = tc.getEpisodeFileBasename(currentShowDescriptor.getFilenamePrefix(),
|
||||||
tmdbResult['name'],
|
tmdbResult['name'],
|
||||||
mediaFileProperties.getSeason(),
|
mediaFileProperties.getSeason(),
|
||||||
mediaFileProperties.getEpisode(),
|
mediaFileProperties.getEpisode(),
|
||||||
@@ -348,7 +343,11 @@ def convert(ctx,
|
|||||||
currentShowDescriptor.getIndicatorSeasonDigits(),
|
currentShowDescriptor.getIndicatorSeasonDigits(),
|
||||||
currentShowDescriptor.getIndicatorEpisodeDigits())
|
currentShowDescriptor.getIndicatorEpisodeDigits())
|
||||||
|
|
||||||
click.echo(f"tmdbFileBasename={tmdbFileBasename}")
|
|
||||||
|
else:
|
||||||
|
fileBasename = currentShowDescriptor.getFilenamePrefix()
|
||||||
|
|
||||||
|
click.echo(f"fileBasename={fileBasename}")
|
||||||
|
|
||||||
if context['import_subtitles']:
|
if context['import_subtitles']:
|
||||||
targetMediaDescriptor.importSubtitles(context['subtitle_directory'], context['subtitle_prefix'])
|
targetMediaDescriptor.importSubtitles(context['subtitle_directory'], context['subtitle_prefix'])
|
||||||
@@ -376,14 +375,20 @@ def convert(ctx,
|
|||||||
click.echo(f"\nRunning job {jobIndex} file={sourcePath} q={q}")
|
click.echo(f"\nRunning job {jobIndex} file={sourcePath} q={q}")
|
||||||
jobIndex += 1
|
jobIndex += 1
|
||||||
|
|
||||||
targetFilename = tmdbFileBasename if tmdbFileBasename else mediaFileProperties.assembleTargetFilename(label, q if len(q_list) > 1 else -1)
|
extra = ['ffx'] if sourceFilenameExtension == FfxController.DEFAULT_FILE_EXTENSION else []
|
||||||
targetPath = os.path.join(sourceDirectory, targetFilename)
|
|
||||||
|
targetFilename = fileBasename if context['tmdb'] else mediaFileProperties.assembleTargetFileBasename(label if label else fileBasename,
|
||||||
|
q if len(q_list) > 1 else -1,
|
||||||
|
extraTokens = extra)
|
||||||
|
|
||||||
|
targetPath = os.path.join(output_directory if output_directory else sourceDirectory, targetFilename)
|
||||||
|
|
||||||
fc.runJob(sourcePath,
|
fc.runJob(sourcePath,
|
||||||
targetPath,
|
targetPath,
|
||||||
context['video_encoder'],
|
context['video_encoder'],
|
||||||
q)
|
q,
|
||||||
|
preset,
|
||||||
|
denoise)
|
||||||
|
|
||||||
# #click.confirm('Warning! This file is not compliant to the defined source schema! Do you want to continue?', abort=True)
|
# #click.confirm('Warning! This file is not compliant to the defined source schema! Do you want to continue?', abort=True)
|
||||||
|
|
||||||
|
|||||||
@@ -367,8 +367,11 @@ class FfxController():
|
|||||||
if not self.__sourceMediaDescriptor is None:
|
if not self.__sourceMediaDescriptor is None:
|
||||||
commandSequence += self.generateMetadataTokens()
|
commandSequence += self.generateMetadataTokens()
|
||||||
|
|
||||||
|
if denoise:
|
||||||
|
commandSequence += self.generateDenoiseTokens()
|
||||||
|
|
||||||
commandSequence += (self.generateAudioEncodingTokens()
|
commandSequence += (self.generateAudioEncodingTokens()
|
||||||
+ self.generateAV1Tokens(quality, preset)
|
+ self.generateAV1Tokens(int(quality), int(preset))
|
||||||
+ self.generateAudioEncodingTokens())
|
+ self.generateAudioEncodingTokens())
|
||||||
|
|
||||||
if self.__context['perform_crop']:
|
if self.__context['perform_crop']:
|
||||||
@@ -388,7 +391,7 @@ class FfxController():
|
|||||||
|
|
||||||
commandSequence1 = (commandTokens
|
commandSequence1 = (commandTokens
|
||||||
+ self.__targetMediaDescriptor.getInputMappingTokens()
|
+ self.__targetMediaDescriptor.getInputMappingTokens()
|
||||||
+ self.generateVP9Pass1Tokens(quality))
|
+ self.generateVP9Pass1Tokens(int(quality)))
|
||||||
|
|
||||||
if self.__context['perform_crop']:
|
if self.__context['perform_crop']:
|
||||||
commandSequence1 += FfxController.generateCropTokens()
|
commandSequence1 += FfxController.generateCropTokens()
|
||||||
@@ -414,7 +417,7 @@ class FfxController():
|
|||||||
if denoise:
|
if denoise:
|
||||||
commandSequence2 += self.generateDenoiseTokens()
|
commandSequence2 += self.generateDenoiseTokens()
|
||||||
|
|
||||||
commandSequence2 += self.generateVP9Pass2Tokens(quality) + self.generateAudioEncodingTokens()
|
commandSequence2 += self.generateVP9Pass2Tokens(int(quality)) + self.generateAudioEncodingTokens()
|
||||||
|
|
||||||
if self.__context['perform_crop']:
|
if self.__context['perform_crop']:
|
||||||
commandSequence2 += FfxController.generateCropTokens()
|
commandSequence2 += FfxController.generateCropTokens()
|
||||||
|
|||||||
@@ -186,12 +186,12 @@ class FileProperties():
|
|||||||
return int(self.__episode)
|
return int(self.__episode)
|
||||||
|
|
||||||
|
|
||||||
def assembleTargetFilename(self,
|
def assembleTargetFileBasename(self,
|
||||||
label: str = "",
|
label: str = "",
|
||||||
quality: int = -1,
|
quality: int = -1,
|
||||||
fileIndex: int = -1,
|
fileIndex: int = -1,
|
||||||
indexDigits: int = DEFAULT_INDEX_DIGITS,
|
indexDigits: int = DEFAULT_INDEX_DIGITS,
|
||||||
extension: str = None):
|
extraTokens: list = []):
|
||||||
|
|
||||||
if 'show_descriptor' in self.context.keys():
|
if 'show_descriptor' in self.context.keys():
|
||||||
season_digits = self.context['show_descriptor'][ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]
|
season_digits = self.context['show_descriptor'][ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]
|
||||||
@@ -202,7 +202,7 @@ class FileProperties():
|
|||||||
|
|
||||||
targetFilenameTokens = []
|
targetFilenameTokens = []
|
||||||
|
|
||||||
targetFilenameExtension = FfxController.DEFAULT_FILE_EXTENSION if extension is None else str(extension)
|
# targetFilenameExtension = FfxController.DEFAULT_FILE_EXTENSION if extension is None else str(extension)
|
||||||
|
|
||||||
if not label:
|
if not label:
|
||||||
targetFilenameTokens = [self.__sourceFileBasename]
|
targetFilenameTokens = [self.__sourceFileBasename]
|
||||||
@@ -220,8 +220,9 @@ class FileProperties():
|
|||||||
targetFilenameTokens += [f"q{quality}"]
|
targetFilenameTokens += [f"q{quality}"]
|
||||||
|
|
||||||
# In case source and target filenames are the same add an extension to distinct output from input
|
# In case source and target filenames are the same add an extension to distinct output from input
|
||||||
if not label and self.__sourceFilenameExtension == targetFilenameExtension:
|
#if not label and self.__sourceFilenameExtension == targetFilenameExtension:
|
||||||
targetFilenameTokens += ['ffx']
|
# targetFilenameTokens += ['ffx']
|
||||||
|
targetFilenameTokens += extraTokens
|
||||||
|
|
||||||
targetFilename = '_'.join(targetFilenameTokens)
|
targetFilename = '_'.join(targetFilenameTokens)
|
||||||
|
|
||||||
|
|||||||
@@ -37,8 +37,11 @@ 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
|
||||||
|
try:
|
||||||
return requests.get(tmdbUrl).json()
|
return requests.get(tmdbUrl).json()
|
||||||
|
except:
|
||||||
|
return {}
|
||||||
|
|
||||||
def getEpisodeFileBasename(self,
|
def getEpisodeFileBasename(self,
|
||||||
showName,
|
showName,
|
||||||
|
|||||||
Reference in New Issue
Block a user