From 82b257d80968f9f7e359f76a2e3abf3045d3ef7c Mon Sep 17 00:00:00 2001 From: Javanaut Date: Tue, 1 Oct 2024 12:27:23 +0000 Subject: [PATCH] inc --- bin/.ipynb_checkpoints/check-checkpoint.py | 32 +++++ bin/.ipynb_checkpoints/ffx-checkpoint.py | 125 ++++++++++++------ .../file_properties-checkpoint.py} | 7 +- bin/ffx/file_properties.py | 66 +++++++++ 4 files changed, 182 insertions(+), 48 deletions(-) create mode 100644 bin/.ipynb_checkpoints/check-checkpoint.py rename bin/ffx/{filename_controller.py => .ipynb_checkpoints/file_properties-checkpoint.py} (93%) create mode 100644 bin/ffx/file_properties.py diff --git a/bin/.ipynb_checkpoints/check-checkpoint.py b/bin/.ipynb_checkpoints/check-checkpoint.py new file mode 100644 index 0000000..9759724 --- /dev/null +++ b/bin/.ipynb_checkpoints/check-checkpoint.py @@ -0,0 +1,32 @@ +import os + +from ffx.pattern_controller import PatternController + +from ffx.model.show import Base +from sqlalchemy import create_engine, Column, Integer, String, ForeignKey +from sqlalchemy.orm import relationship, sessionmaker, Mapped, backref + +filename = 'Boruto.Naruto.Next.Generations.S01E256.GerEngSub.AAC.1080p.WebDL.x264-Tanuki.mkv' + + + +# Data 'input' variable +context = {} + +# Initialize DB +homeDir = os.path.expanduser("~") +ffxVarDir = os.path.join(homeDir, '.local', 'var', 'ffx') +if not os.path.exists(ffxVarDir): + os.makedirs(ffxVarDir) + +context['database_url'] = f"sqlite:///{os.path.join(ffxVarDir, 'ffx.db')}" +context['database_engine'] = create_engine(context['database_url']) +context['database_session'] = sessionmaker(bind=context['database_engine']) + +Base.metadata.create_all(context['database_engine']) + + +pc = PatternController(context) + + +print(pc.matchFilename(filename)) diff --git a/bin/.ipynb_checkpoints/ffx-checkpoint.py b/bin/.ipynb_checkpoints/ffx-checkpoint.py index b1849f1..88fc807 100755 --- a/bin/.ipynb_checkpoints/ffx-checkpoint.py +++ b/bin/.ipynb_checkpoints/ffx-checkpoint.py @@ -1,10 +1,10 @@ #! /usr/bin/python3 -import os, sys, subprocess, json, click, time +import os, sys, subprocess, json, click, time, re from textual.app import App, ComposeResult from textual.screen import Screen -from textual.widgets import Header, Footer, Placeholder +from textual.widgets import Header, Footer, Placeholder, Label VERSION='0.1.0' @@ -35,7 +35,7 @@ MKVMERGE_METADATA_KEYS = ['BPS', '_STATISTICS_WRITING_DATE_UTC', '_STATISTICS_TAGS'] -FILE_EXTENSION = ['mkv', 'mp4', 'avi', 'flv', 'webm'] +FILE_EXTENSIONS = ['mkv', 'mp4', 'avi', 'flv', 'webm'] COMMAND_TOKENS = ['ffmpeg', '-y', '-i'] @@ -50,6 +50,9 @@ STREAM_LAYOUT_5_1 = '5.1(side)' STREAM_LAYOUT_STEREO = 'stereo' STREAM_LAYOUT_6CH = '6ch' +SEASON_EPISODE_INDICATOR_MATCH = '([sS][0-9]+)([eE][0-9]+)' +SEASON_INDICATOR_MATCH = '([sS][0-9]+)' +EPISODE_INDICATOR_MATCH = '([eE][0-9]+)' class DashboardScreen(Screen): @@ -65,6 +68,14 @@ class DashboardScreen(Screen): yield Placeholder("Dashboard Screen") yield Footer() +class WarningScreen(Screen): + def __init__(self): + super().__init__() + context = self.app.getContext() + def compose(self) -> ComposeResult: + yield Label("Warning! This file is not compliant to the defined source schema!") + yield Footer() + class SettingsScreen(Screen): def __init__(self): @@ -87,29 +98,31 @@ class HelpScreen(Screen): class ModesApp(App): BINDINGS = [ - ("d", "switch_mode('dashboard')", "Dashboard"), - ("s", "switch_mode('settings')", "Settings"), - ("h", "switch_mode('help')", "Help"), + ("q", "quit()", "Quit"), + # ("d", "switch_mode('dashboard')", "Dashboard"), + # ("s", "switch_mode('settings')", "Settings"), + # ("h", "switch_mode('help')", "Help"), ] MODES = { + "warning": WarningScreen, "dashboard": DashboardScreen, "settings": SettingsScreen, "help": HelpScreen, } + def __init__(self, context = {}): super().__init__() self.context = context def on_mount(self) -> None: - self.switch_mode("dashboard") + self.switch_mode("warning") def getContext(self): return self.context - def executeProcess(commandSequence): process = subprocess.Popen(commandSequence, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -354,8 +367,10 @@ def streams(filename): @click.option("-c", "--clear-metadata", is_flag=True, default=False) @click.option("-d", "--denoise", is_flag=True, default=False) +@click.option("-o", "--output-directory", type=str, default='') -def convert(ctx, paths, label, video_encoder, quality, preset, stereo_bitrate, ac3_bitrate, dts_bitrate, crop, clear_metadata, default_subtitle, forced_audio, default_audio, denoise): + +def convert(ctx, paths, label, video_encoder, quality, preset, stereo_bitrate, ac3_bitrate, dts_bitrate, crop, clear_metadata, default_subtitle, forced_audio, default_audio, denoise, output_directory): """Batch conversion of audiovideo files in format suitable for web playback, e.g. jellyfin Files found under PATHS will be converted according to parameters. @@ -363,56 +378,79 @@ def convert(ctx, paths, label, video_encoder, quality, preset, stereo_bitrate, a Suffices will we appended to filename in case of multiple created files or if the filename has not changed.""" - #startTime = time.perf_counter() + startTime = time.perf_counter() + + context = ctx.obj - #sourcePath = paths[0] - #targetFilename = paths[1] - #if not os.path.isfile(sourcePath): - # raise click.ClickException(f"There is no file with path {sourcePath}") + click.echo(f"\nVideo encoder: {video_encoder}") - #click.echo(f"src: {sourcePath} tgt: {targetFilename}") + qualityTokens = quality.split(',') + q_list = [q for q in qualityTokens if q.isnumeric()] + click.echo(f"Qualities: {q_list}") - #click.echo(f"ve={video_encoder}") + + ctx.obj['bitrates'] = {} + ctx.obj['bitrates']['stereo'] = str(stereo_bitrate) if str(stereo_bitrate).endswith('k') else f"{stereo_bitrate}k" + ctx.obj['bitrates']['ac3'] = str(ac3_bitrate) if str(ac3_bitrate).endswith('k') else f"{ac3_bitrate}k" + ctx.obj['bitrates']['dts'] = str(dts_bitrate) if str(dts_bitrate).endswith('k') else f"{dts_bitrate}k" + click.echo(f"Stereo bitrate: {ctx.obj['bitrates']['stereo']}") + click.echo(f"AC3 bitrate: {ctx.obj['bitrates']['ac3']}") + click.echo(f"DTS bitrate: {ctx.obj['bitrates']['dts']}") - #qualityTokens = quality.split(',') + ctx.obj['perform_crop'] = (crop != 'none') - #q_list = [q for q in qualityTokens if q.isnumeric()] + if ctx.obj['perform_crop']: - #click.echo(q_list) + cropTokens = crop.split(',') - #ctx.obj['bitrates'] = {} - #ctx.obj['bitrates']['stereo'] = str(stereo_bitrate) if str(stereo_bitrate).endswith('k') else f"{stereo_bitrate}k" - #ctx.obj['bitrates']['ac3'] = str(ac3_bitrate) if str(ac3_bitrate).endswith('k') else f"{ac3_bitrate}k" - #ctx.obj['bitrates']['dts'] = str(dts_bitrate) if str(dts_bitrate).endswith('k') else f"{dts_bitrate}k" + if cropTokens and len(cropTokens) == 2: + ctx.obj['crop_start'], ctx.obj['crop_length'] = crop.split(',') + else: + ctx.obj['crop_start'] = DEFAULT_CROP_START + ctx.obj['crop_length'] = DEFAULT_CROP_LENGTH - #click.echo(f"a={ctx.obj['bitrates']['stereo']}") - #click.echo(f"ac3={ctx.obj['bitrates']['ac3']}") - #click.echo(f"dts={ctx.obj['bitrates']['dts']}") + click.echo(f"crop start={ctx.obj['crop_start']} length={ctx.obj['crop_length']}") - #performCrop = (crop != 'none') + click.echo(f"\nRunning {len(paths) * len(q_list)} jobs") + + se_match = re.compile(SEASON_EPISODE_INDICATOR_MATCH) + s_match = re.compile(SEASON_INDICATOR_MATCH) + e_match = re.compile(EPISODE_INDICATOR_MATCH) - #if performCrop: - #cropTokens = crop.split(',') + for sourcePath in paths: - #if cropTokens and len(cropTokens) == 2: - #cropStart, cropLength = crop.split(',') - #else: - #cropStart = DEFAULT_CROP_START - #cropLength = DEFAULT_CROP_LENGTH + if not os.path.isfile(sourcePath): + click.echo(f"There is no file with path {sourcePath}, skipping ...") + continue - #click.echo(f"crop start={cropStart} length={cropLength}") + sourceDirectory = os.path.dirname(sourcePath) + sourceFilename = os.path.basename(sourcePath) + sourcePathTokens = sourceFilename.split('.') + + if sourcePathTokens[-1] in FILE_EXTENSIONS: + sourceFileBasename = '.'.join(sourcePathTokens[:-1]) + sourceFilenameExtension = sourcePathTokens[-1] + else: + sourceFileBasename = sourceFilename + sourceFilenameExtension = '' + #click.echo(f"dir={sourceDirectory} base={sourceFileBasename} ext={sourceFilenameExtension}") + click.echo(f"\nProcessing file {sourcePath}") + + + se_result = se_match.search(sourceFilename) + s_result = s_match.search(sourceFilename) + e_result = e_match.search(sourceFilename) - #click.echo(f"\nRunning {len(q_list)} jobs") #streamDescriptor = getStreamDescriptor(sourcePath) @@ -497,15 +535,18 @@ def convert(ctx, paths, label, video_encoder, quality, preset, stereo_bitrate, a #executeProcess(commandSequence2) - #click.echo('\nDONE\n') + #app = ModesApp(ctx.obj) + #app.run() + + #click.confirm('Warning! This file is not compliant to the defined source schema! Do you want to continue?', abort=True) + + click.echo('\nDONE\n') - #endTime = time.perf_counter() - #click.echo(f"Time elapsed {endTime - startTime}") + endTime = time.perf_counter() + click.echo(f"Time elapsed {endTime - startTime}") - app = ModesApp(ctx.obj) - app.run() - click.echo(f"app result: {app.getContext()}") + # click.echo(f"app result: {app.getContext()}") diff --git a/bin/ffx/filename_controller.py b/bin/ffx/.ipynb_checkpoints/file_properties-checkpoint.py similarity index 93% rename from bin/ffx/filename_controller.py rename to bin/ffx/.ipynb_checkpoints/file_properties-checkpoint.py index f70c221..4e2521a 100644 --- a/bin/ffx/filename_controller.py +++ b/bin/ffx/.ipynb_checkpoints/file_properties-checkpoint.py @@ -1,6 +1,6 @@ import os, re, click -class FilenameController(): +class FileProperties(): FILE_EXTENSIONS = ['mkv', 'mp4', 'avi', 'flv', 'webm'] @@ -23,11 +23,6 @@ class FilenameController(): self.__sourceFilenameExtension = '' - # Determine season and episode if present in current filename - season_digits = 2 - episode_digits = 2 - index_digits = 3 - se_match = re.compile(FilenameController.SEASON_EPISODE_INDICATOR_MATCH) e_match = re.compile(FilenameController.EPISODE_INDICATOR_MATCH) diff --git a/bin/ffx/file_properties.py b/bin/ffx/file_properties.py new file mode 100644 index 0000000..4e2521a --- /dev/null +++ b/bin/ffx/file_properties.py @@ -0,0 +1,66 @@ +import os, re, click + +class FileProperties(): + + FILE_EXTENSIONS = ['mkv', 'mp4', 'avi', 'flv', 'webm'] + + SEASON_EPISODE_INDICATOR_MATCH = '[sS]([0-9]+)[eE]([0-9]+)' + EPISODE_INDICATOR_MATCH = '[eE]([0-9]+)' + + def ___init__(self, sourcePath, ): + + + # Separate basedir, basename and extension for current source file + self.__sourceDirectory = os.path.dirname(sourcePath) + self.__sourceFilename = os.path.basename(sourcePath) + sourcePathTokens = self.__sourceFilename.split('.') + + if sourcePathTokens[-1] in FilenameController.FILE_EXTENSIONS: + self.__sourceFileBasename = '.'.join(sourcePathTokens[:-1]) + self.__sourceFilenameExtension = sourcePathTokens[-1] + else: + self.__sourceFileBasename = self.__sourceFilename + self.__sourceFilenameExtension = '' + + + se_match = re.compile(FilenameController.SEASON_EPISODE_INDICATOR_MATCH) + e_match = re.compile(FilenameController.EPISODE_INDICATOR_MATCH) + + se_result = se_match.search(self.__sourceFilename) + e_result = e_match.search(self.__sourceFilename) + + self.__season = -1 + self.__episode = -1 + file_index = 0 + + 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 + + + + 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}") + + + # Assemble target filename tokens + targetFilenameTokens = [] + targetFilenameExtension = DEFAULT_FILE_EXTENSION + + if label: + targetFilenameTokens = [label] + + if season > -1 and episode > -1: + targetFilenameTokens += [f"S{season:0{season_digits}d}E{episode:0{episode_digits}d}"] + elif episode > -1: + targetFilenameTokens += [f"E{episode:0{episode_digits}d}"] + else: + targetFilenameTokens += [f"{file_index:0{index_digits}d}"] + + else: + targetFilenameTokens = [sourceFileBasename]