Extd rename/unmux to pad with zeroes

This commit is contained in:
Javanaut
2026-04-12 11:44:32 +02:00
parent d05b01cfb2
commit ef0d6e9274
13 changed files with 730 additions and 73 deletions

View File

@@ -185,8 +185,32 @@ def resolveUnmuxOutputDirectory(context, outputDirectory, subtitlesOnly, label):
return os.path.join(configuredSubtitlesBaseDirectory, resolvedLabel), True
def buildRenameTargetFilename(sourcePath, prefix, seasonOverride=None, suffix=''):
def resolveIndicatorDigitLengths(context=None, showDescriptor=None):
from ffx.show_descriptor import ShowDescriptor
defaultDigitLengths = ShowDescriptor.getDefaultDigitLengths(context)
if showDescriptor is None:
return (
defaultDigitLengths[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY],
defaultDigitLengths[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY],
)
return (
int(showDescriptor.getIndicatorSeasonDigits()),
int(showDescriptor.getIndicatorEpisodeDigits()),
)
def buildRenameTargetFilename(
sourcePath,
prefix,
seasonOverride=None,
suffix='',
indicatorSeasonDigits=None,
indicatorEpisodeDigits=None,
):
from ffx.file_properties import FileProperties
from ffx.show_descriptor import ShowDescriptor
sourceFilename = os.path.basename(sourcePath)
seasonEpisodeValues = FileProperties.extractSeasonEpisodeValues(sourceFilename)
@@ -197,12 +221,22 @@ def buildRenameTargetFilename(sourcePath, prefix, seasonOverride=None, suffix=''
resolvedSeason = int(seasonOverride) if seasonOverride is not None else (
int(sourceSeason) if sourceSeason is not None else 1
)
resolvedIndicatorSeasonDigits = (
int(indicatorSeasonDigits)
if indicatorSeasonDigits is not None
else ShowDescriptor.DEFAULT_INDICATOR_SEASON_DIGITS
)
resolvedIndicatorEpisodeDigits = (
int(indicatorEpisodeDigits)
if indicatorEpisodeDigits is not None
else ShowDescriptor.DEFAULT_INDICATOR_EPISODE_DIGITS
)
_sourceBasename, sourceExtension = os.path.splitext(sourceFilename)
targetFilenameTokens = [
str(prefix).strip(),
f"s{resolvedSeason}e{int(sourceEpisode)}",
f"s{resolvedSeason:0{resolvedIndicatorSeasonDigits}d}e{int(sourceEpisode):0{resolvedIndicatorEpisodeDigits}d}",
]
resolvedSuffix = str(suffix).strip()
@@ -444,10 +478,15 @@ def inspect(ctx, filename):
@click.option('--dry-run', is_flag=True, default=False, help='Only print planned renames')
def rename(ctx, paths, prefix, season, suffix, dry_run):
"""Rename matching episode files in place."""
from ffx.configuration_controller import ConfigurationController
resolvedPrefix = str(prefix).strip()
resolvedSuffix = str(suffix).strip()
effectiveDryRun = bool(ctx.obj.get('dry_run', False) or dry_run)
renameContext = {
'config': ctx.obj.get('config') or ConfigurationController(),
}
indicatorSeasonDigits, indicatorEpisodeDigits = resolveIndicatorDigitLengths(renameContext)
if not resolvedPrefix:
raise click.ClickException("Rename prefix must not be empty.")
@@ -463,6 +502,8 @@ def rename(ctx, paths, prefix, season, suffix, dry_run):
resolvedPrefix,
seasonOverride=season,
suffix=resolvedSuffix,
indicatorSeasonDigits=indicatorSeasonDigits,
indicatorEpisodeDigits=indicatorEpisodeDigits,
)
if targetFilename is None:
continue
@@ -571,13 +612,25 @@ def unmux(ctx,
try:
sourceMediaDescriptor = fp.getMediaDescriptor()
currentPattern = fp.getPattern()
currentShowDescriptor = (
currentPattern.getShowDescriptor(ctx.obj) if currentPattern is not None else None
)
indicatorSeasonDigits, indicatorEpisodeDigits = resolveIndicatorDigitLengths(
ctx.obj,
currentShowDescriptor,
)
season = fp.getSeason()
episode = fp.getEpisode()
#TODO: Recognition für alle Formate anpassen
targetLabel = label if label else fp.getFileBasename()
targetIndicator = f"_S{season}E{episode}" if label and season != -1 and episode != -1 else ''
targetIndicator = (
f"_S{season:0{indicatorSeasonDigits}d}E{episode:0{indicatorEpisodeDigits}d}"
if label and season != -1 and episode != -1
else ''
)
if label and not targetIndicator:
ctx.obj['logger'].warning(f"Skipping file {fp.getFilename()}: Label set but no indicator recognized")
@@ -1226,10 +1279,11 @@ def convert(ctx,
fc = FfxController(context, targetMediaDescriptor, sourceMediaDescriptor)
indexSeasonDigits = currentShowDescriptor.getIndexSeasonDigits() if not currentPattern is None else ShowDescriptor.DEFAULT_INDEX_SEASON_DIGITS
indexEpisodeDigits = currentShowDescriptor.getIndexEpisodeDigits() if not currentPattern is None else ShowDescriptor.DEFAULT_INDEX_EPISODE_DIGITS
indicatorSeasonDigits = currentShowDescriptor.getIndicatorSeasonDigits() if not currentPattern is None else ShowDescriptor.DEFAULT_INDICATOR_SEASON_DIGITS
indicatorEpisodeDigits = currentShowDescriptor.getIndicatorEpisodeDigits() if not currentPattern is None else ShowDescriptor.DEFAULT_INDICATOR_EPISODE_DIGITS
defaultDigitLengths = ShowDescriptor.getDefaultDigitLengths(context)
indexSeasonDigits = currentShowDescriptor.getIndexSeasonDigits() if not currentPattern is None else defaultDigitLengths[ShowDescriptor.INDEX_SEASON_DIGITS_KEY]
indexEpisodeDigits = currentShowDescriptor.getIndexEpisodeDigits() if not currentPattern is None else defaultDigitLengths[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY]
indicatorSeasonDigits = currentShowDescriptor.getIndicatorSeasonDigits() if not currentPattern is None else defaultDigitLengths[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]
indicatorEpisodeDigits = currentShowDescriptor.getIndicatorEpisodeDigits() if not currentPattern is None else defaultDigitLengths[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]
# Shift season and episode if defined for this show

View File

@@ -1,5 +1,12 @@
import os, json
from .constants import (
DEFAULT_SHOW_INDEX_EPISODE_DIGITS,
DEFAULT_SHOW_INDEX_SEASON_DIGITS,
DEFAULT_SHOW_INDICATOR_EPISODE_DIGITS,
DEFAULT_SHOW_INDICATOR_SEASON_DIGITS,
)
class ConfigurationController():
CONFIG_FILENAME = 'ffx.json'
@@ -10,6 +17,10 @@ class ConfigurationController():
LOG_DIRECTORY_CONFIG_KEY = 'logDirectory'
SUBTITLES_DIRECTORY_CONFIG_KEY = 'subtitlesDirectory'
OUTPUT_FILENAME_TEMPLATE_KEY = 'outputFilenameTemplate'
DEFAULT_INDEX_SEASON_DIGITS_CONFIG_KEY = 'defaultIndexSeasonDigits'
DEFAULT_INDEX_EPISODE_DIGITS_CONFIG_KEY = 'defaultIndexEpisodeDigits'
DEFAULT_INDICATOR_SEASON_DIGITS_CONFIG_KEY = 'defaultIndicatorSeasonDigits'
DEFAULT_INDICATOR_EPISODE_DIGITS_CONFIG_KEY = 'defaultIndicatorEpisodeDigits'
def __init__(self):
@@ -57,6 +68,42 @@ class ConfigurationController():
)
return os.path.expanduser(str(subtitlesDirectory)) if subtitlesDirectory else ''
@classmethod
def getConfiguredIntegerValue(cls, configurationData: dict, configKey: str, defaultValue: int) -> int:
configuredValue = configurationData.get(configKey, defaultValue)
try:
return int(configuredValue)
except (TypeError, ValueError):
return int(defaultValue)
def getDefaultIndexSeasonDigits(self):
return ConfigurationController.getConfiguredIntegerValue(
self.__configurationData,
ConfigurationController.DEFAULT_INDEX_SEASON_DIGITS_CONFIG_KEY,
DEFAULT_SHOW_INDEX_SEASON_DIGITS,
)
def getDefaultIndexEpisodeDigits(self):
return ConfigurationController.getConfiguredIntegerValue(
self.__configurationData,
ConfigurationController.DEFAULT_INDEX_EPISODE_DIGITS_CONFIG_KEY,
DEFAULT_SHOW_INDEX_EPISODE_DIGITS,
)
def getDefaultIndicatorSeasonDigits(self):
return ConfigurationController.getConfiguredIntegerValue(
self.__configurationData,
ConfigurationController.DEFAULT_INDICATOR_SEASON_DIGITS_CONFIG_KEY,
DEFAULT_SHOW_INDICATOR_SEASON_DIGITS,
)
def getDefaultIndicatorEpisodeDigits(self):
return ConfigurationController.getConfiguredIntegerValue(
self.__configurationData,
ConfigurationController.DEFAULT_INDICATOR_EPISODE_DIGITS_CONFIG_KEY,
DEFAULT_SHOW_INDICATOR_EPISODE_DIGITS,
)
def getData(self):
return self.__configurationData

View File

@@ -22,4 +22,9 @@ DEFAULT_CROPDETECT_DURATION_SECONDS = 180
DEFAULT_cut_start = 60
DEFAULT_cut_length = 180
DEFAULT_SHOW_INDEX_SEASON_DIGITS = 2
DEFAULT_SHOW_INDEX_EPISODE_DIGITS = 2
DEFAULT_SHOW_INDICATOR_SEASON_DIGITS = 2
DEFAULT_SHOW_INDICATOR_EPISODE_DIGITS = 2
DEFAULT_OUTPUT_FILENAME_TEMPLATE = '{{ ffx_show_name }} - {{ ffx_index }}{{ ffx_index_separator }}{{ ffx_episode_name }}{{ ffx_indicator_separator }}{{ ffx_indicator }}'

View File

@@ -4,6 +4,7 @@ from jinja2 import Environment, Undefined
from .constants import DEFAULT_OUTPUT_FILENAME_TEMPLATE
from .configuration_controller import ConfigurationController
from .logging_utils import get_ffx_logger
from .show_descriptor import ShowDescriptor
class EmptyStringUndefined(Undefined):
@@ -164,10 +165,10 @@ def getEpisodeFileBasename(showName,
episodeName,
season,
episode,
indexSeasonDigits = 2,
indexEpisodeDigits = 2,
indicatorSeasonDigits = 2,
indicatorEpisodeDigits = 2,
indexSeasonDigits = None,
indexEpisodeDigits = None,
indicatorSeasonDigits = None,
indicatorEpisodeDigits = None,
context = None):
"""
One Piece:
@@ -199,6 +200,16 @@ def getEpisodeFileBasename(showName,
configData = cc.getData() if cc is not None else {}
outputFilenameTemplate = configData.get(ConfigurationController.OUTPUT_FILENAME_TEMPLATE_KEY,
DEFAULT_OUTPUT_FILENAME_TEMPLATE)
defaultDigitLengths = ShowDescriptor.getDefaultDigitLengths(context)
if indexSeasonDigits is None:
indexSeasonDigits = defaultDigitLengths[ShowDescriptor.INDEX_SEASON_DIGITS_KEY]
if indexEpisodeDigits is None:
indexEpisodeDigits = defaultDigitLengths[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY]
if indicatorSeasonDigits is None:
indicatorSeasonDigits = defaultDigitLengths[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]
if indicatorEpisodeDigits is None:
indicatorEpisodeDigits = defaultDigitLengths[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]
if context is not None and 'logger' in context.keys():
logger = context['logger']

View File

@@ -559,6 +559,7 @@ class MediaDetailsScreen(Screen):
try:
kwargs = {}
kwargs[ShowDescriptor.CONTEXT_KEY] = self.context
kwargs[ShowDescriptor.ID_KEY] = int(selected_row_data[0])
kwargs[ShowDescriptor.NAME_KEY] = str(selected_row_data[1])
kwargs[ShowDescriptor.YEAR_KEY] = int(selected_row_data[2])

View File

@@ -1,3 +1,10 @@
from .configuration_controller import ConfigurationController
from .constants import (
DEFAULT_SHOW_INDEX_EPISODE_DIGITS,
DEFAULT_SHOW_INDEX_SEASON_DIGITS,
DEFAULT_SHOW_INDICATOR_EPISODE_DIGITS,
DEFAULT_SHOW_INDICATOR_SEASON_DIGITS,
)
from .logging_utils import get_ffx_logger
@@ -15,10 +22,42 @@ class ShowDescriptor():
INDICATOR_SEASON_DIGITS_KEY = 'indicator_season_digits'
INDICATOR_EPISODE_DIGITS_KEY = 'indicator_episode_digits'
DEFAULT_INDEX_SEASON_DIGITS = 2
DEFAULT_INDEX_EPISODE_DIGITS = 2
DEFAULT_INDICATOR_SEASON_DIGITS = 2
DEFAULT_INDICATOR_EPISODE_DIGITS = 2
DEFAULT_INDEX_SEASON_DIGITS = DEFAULT_SHOW_INDEX_SEASON_DIGITS
DEFAULT_INDEX_EPISODE_DIGITS = DEFAULT_SHOW_INDEX_EPISODE_DIGITS
DEFAULT_INDICATOR_SEASON_DIGITS = DEFAULT_SHOW_INDICATOR_SEASON_DIGITS
DEFAULT_INDICATOR_EPISODE_DIGITS = DEFAULT_SHOW_INDICATOR_EPISODE_DIGITS
@classmethod
def getDefaultDigitLengths(cls, context: dict | None = None) -> dict[str, int]:
configurationData = {}
if context is not None:
configController = context.get('config')
if configController is not None and hasattr(configController, 'getData'):
configurationData = configController.getData()
return {
cls.INDEX_SEASON_DIGITS_KEY: ConfigurationController.getConfiguredIntegerValue(
configurationData,
ConfigurationController.DEFAULT_INDEX_SEASON_DIGITS_CONFIG_KEY,
cls.DEFAULT_INDEX_SEASON_DIGITS,
),
cls.INDEX_EPISODE_DIGITS_KEY: ConfigurationController.getConfiguredIntegerValue(
configurationData,
ConfigurationController.DEFAULT_INDEX_EPISODE_DIGITS_CONFIG_KEY,
cls.DEFAULT_INDEX_EPISODE_DIGITS,
),
cls.INDICATOR_SEASON_DIGITS_KEY: ConfigurationController.getConfiguredIntegerValue(
configurationData,
ConfigurationController.DEFAULT_INDICATOR_SEASON_DIGITS_CONFIG_KEY,
cls.DEFAULT_INDICATOR_SEASON_DIGITS,
),
cls.INDICATOR_EPISODE_DIGITS_KEY: ConfigurationController.getConfiguredIntegerValue(
configurationData,
ConfigurationController.DEFAULT_INDICATOR_EPISODE_DIGITS_CONFIG_KEY,
cls.DEFAULT_INDICATOR_EPISODE_DIGITS,
),
}
def __init__(self, **kwargs):
@@ -53,36 +92,37 @@ class ShowDescriptor():
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.YEAR_KEY} is required to be of type int")
self.__showYear = kwargs[ShowDescriptor.YEAR_KEY]
else:
self.__showYear = -1
self.__showYear = -1
defaultDigitLengths = self.getDefaultDigitLengths(self.__context)
if ShowDescriptor.INDEX_SEASON_DIGITS_KEY in kwargs.keys():
if type(kwargs[ShowDescriptor.INDEX_SEASON_DIGITS_KEY]) is not int:
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDEX_SEASON_DIGITS_KEY} is required to be of type int")
self.__indexSeasonDigits = kwargs[ShowDescriptor.INDEX_SEASON_DIGITS_KEY]
else:
self.__indexSeasonDigits = ShowDescriptor.DEFAULT_INDEX_SEASON_DIGITS
self.__indexSeasonDigits = defaultDigitLengths[ShowDescriptor.INDEX_SEASON_DIGITS_KEY]
if ShowDescriptor.INDEX_EPISODE_DIGITS_KEY in kwargs.keys():
if type(kwargs[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY]) is not int:
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDEX_EPISODE_DIGITS_KEY} is required to be of type int")
self.__indexEpisodeDigits = kwargs[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY]
else:
self.__indexEpisodeDigits = ShowDescriptor.DEFAULT_INDEX_EPISODE_DIGITS
self.__indexEpisodeDigits = defaultDigitLengths[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY]
if ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY in kwargs.keys():
if type(kwargs[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]) is not int:
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY} is required to be of type int")
self.__indicatorSeasonDigits = kwargs[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]
else:
self.__indicatorSeasonDigits = ShowDescriptor.DEFAULT_INDICATOR_SEASON_DIGITS
self.__indicatorSeasonDigits = defaultDigitLengths[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]
if ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY in kwargs.keys():
if type(kwargs[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]) is not int:
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY} is required to be of type int")
self.__indicatorEpisodeDigits = kwargs[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]
else:
self.__indicatorEpisodeDigits = ShowDescriptor.DEFAULT_INDICATOR_EPISODE_DIGITS
self.__indicatorEpisodeDigits = defaultDigitLengths[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]
def getId(self):

View File

@@ -160,11 +160,20 @@ class ShowDetailsScreen(Screen):
self.updateShiftedSeasons()
else:
self.query_one("#index_season_digits_input", Input).value = "2"
self.query_one("#index_episode_digits_input", Input).value = "2"
self.query_one("#indicator_season_digits_input", Input).value = "2"
self.query_one("#indicator_episode_digits_input", Input).value = "2"
defaultDigitLengths = ShowDescriptor.getDefaultDigitLengths(self.context)
self.query_one("#index_season_digits_input", Input).value = str(
defaultDigitLengths[ShowDescriptor.INDEX_SEASON_DIGITS_KEY]
)
self.query_one("#index_episode_digits_input", Input).value = str(
defaultDigitLengths[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY]
)
self.query_one("#indicator_season_digits_input", Input).value = str(
defaultDigitLengths[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]
)
self.query_one("#indicator_episode_digits_input", Input).value = str(
defaultDigitLengths[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]
)
def getSelectedPatternDescriptor(self):
@@ -387,7 +396,7 @@ class ShowDetailsScreen(Screen):
def getShowDescriptorFromInput(self) -> ShowDescriptor:
kwargs = {}
kwargs = {ShowDescriptor.CONTEXT_KEY: self.context}
try:
if self.__showDescriptor: