|
|
|
|
@@ -1,6 +1,9 @@
|
|
|
|
|
#! /usr/bin/python3
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import os, sys, click, time, shutil, subprocess
|
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
|
|
|
|
# Allow direct execution via `python src/ffx/cli.py` by preferring the package
|
|
|
|
|
# root on sys.path.
|
|
|
|
|
@@ -10,42 +13,24 @@ if __package__ in (None, ''):
|
|
|
|
|
sys.path = [p for p in sys.path if os.path.abspath(p) != os.path.abspath(script_dir)]
|
|
|
|
|
sys.path.insert(0, package_root)
|
|
|
|
|
|
|
|
|
|
from ffx.configuration_controller import ConfigurationController
|
|
|
|
|
from ffx.constants import (
|
|
|
|
|
DEFAULT_AC3_BANDWIDTH,
|
|
|
|
|
DEFAULT_CONTAINER_EXTENSION,
|
|
|
|
|
DEFAULT_CONTAINER_FORMAT,
|
|
|
|
|
DEFAULT_DTS_BANDWIDTH,
|
|
|
|
|
DEFAULT_STEREO_BANDWIDTH,
|
|
|
|
|
DEFAULT_VIDEO_ENCODER_LABEL,
|
|
|
|
|
FFMPEG_COMMAND_TOKENS,
|
|
|
|
|
SUPPORTED_INPUT_FILE_EXTENSIONS,
|
|
|
|
|
VERSION,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
from ffx.file_properties import FileProperties
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from ffx.media_descriptor import MediaDescriptor
|
|
|
|
|
from ffx.track_descriptor import TrackDescriptor
|
|
|
|
|
|
|
|
|
|
from ffx.ffx_app import FfxApp
|
|
|
|
|
from ffx.ffx_controller import FfxController
|
|
|
|
|
from ffx.tmdb_controller import TmdbController
|
|
|
|
|
LIGHTWEIGHT_COMMANDS = {None, 'version', 'help', 'configure_workstation', 'upgrade'}
|
|
|
|
|
|
|
|
|
|
from ffx.database import databaseContext
|
|
|
|
|
|
|
|
|
|
from ffx.media_descriptor import MediaDescriptor
|
|
|
|
|
from ffx.track_descriptor import TrackDescriptor
|
|
|
|
|
from ffx.show_descriptor import ShowDescriptor
|
|
|
|
|
|
|
|
|
|
from ffx.track_type import TrackType
|
|
|
|
|
from ffx.video_encoder import VideoEncoder
|
|
|
|
|
from ffx.track_disposition import TrackDisposition
|
|
|
|
|
from ffx.track_codec import TrackCodec
|
|
|
|
|
|
|
|
|
|
from ffx.process import executeProcess
|
|
|
|
|
from ffx.helper import filterFilename, substituteTmdbFilename
|
|
|
|
|
from ffx.helper import getEpisodeFileBasename
|
|
|
|
|
|
|
|
|
|
from ffx.constants import DEFAULT_STEREO_BANDWIDTH, DEFAULT_AC3_BANDWIDTH, DEFAULT_DTS_BANDWIDTH, DEFAULT_7_1_BANDWIDTH
|
|
|
|
|
|
|
|
|
|
from ffx.filter.quality_filter import QualityFilter
|
|
|
|
|
from ffx.filter.preset_filter import PresetFilter
|
|
|
|
|
|
|
|
|
|
from ffx.filter.crop_filter import CropFilter
|
|
|
|
|
from ffx.filter.nlmeans_filter import NlmeansFilter
|
|
|
|
|
from ffx.filter.deinterlace_filter import DeinterlaceFilter
|
|
|
|
|
|
|
|
|
|
from ffx.constants import VERSION
|
|
|
|
|
|
|
|
|
|
from ffx.shifted_season_controller import ShiftedSeasonController
|
|
|
|
|
from ffx.logging_utils import configure_ffx_logger
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@click.group()
|
|
|
|
|
@@ -58,11 +43,18 @@ def ffx(ctx, database_file, verbose, dry_run):
|
|
|
|
|
|
|
|
|
|
ctx.obj = {}
|
|
|
|
|
|
|
|
|
|
if ctx.invoked_subcommand in ('configure_workstation', 'upgrade'):
|
|
|
|
|
if ctx.resilient_parsing:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if ctx.invoked_subcommand in LIGHTWEIGHT_COMMANDS:
|
|
|
|
|
ctx.obj['dry_run'] = dry_run
|
|
|
|
|
ctx.obj['verbosity'] = verbose
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
from ffx.configuration_controller import ConfigurationController
|
|
|
|
|
from ffx.database import databaseContext
|
|
|
|
|
from ffx.logging_utils import configure_ffx_logger
|
|
|
|
|
|
|
|
|
|
ctx.obj['config'] = ConfigurationController()
|
|
|
|
|
|
|
|
|
|
ctx.obj['database'] = databaseContext(databasePath=database_file
|
|
|
|
|
@@ -184,6 +176,7 @@ def upgrade(ctx, branch):
|
|
|
|
|
@click.pass_context
|
|
|
|
|
@click.argument('filename', nargs=1)
|
|
|
|
|
def inspect(ctx, filename):
|
|
|
|
|
from ffx.ffx_app import FfxApp
|
|
|
|
|
|
|
|
|
|
ctx.obj['command'] = 'inspect'
|
|
|
|
|
ctx.obj['arguments'] = {}
|
|
|
|
|
@@ -196,7 +189,7 @@ def inspect(ctx, filename):
|
|
|
|
|
def getUnmuxSequence(trackDescriptor: TrackDescriptor, sourcePath, targetPrefix, targetDirectory = ''):
|
|
|
|
|
|
|
|
|
|
# executable and input file
|
|
|
|
|
commandTokens = FfxController.COMMAND_TOKENS + ['-i', sourcePath]
|
|
|
|
|
commandTokens = list(FFMPEG_COMMAND_TOKENS) + ['-i', sourcePath]
|
|
|
|
|
|
|
|
|
|
trackType = trackDescriptor.getType()
|
|
|
|
|
|
|
|
|
|
@@ -237,6 +230,10 @@ def unmux(ctx,
|
|
|
|
|
subtitles_only,
|
|
|
|
|
nice,
|
|
|
|
|
cpu):
|
|
|
|
|
from ffx.file_properties import FileProperties
|
|
|
|
|
from ffx.process import executeProcess
|
|
|
|
|
from ffx.track_disposition import TrackDisposition
|
|
|
|
|
from ffx.track_type import TrackType
|
|
|
|
|
|
|
|
|
|
existingSourcePaths = [p for p in paths if os.path.isfile(p)]
|
|
|
|
|
ctx.obj['logger'].debug(f"\nUnmuxing {len(existingSourcePaths)} files")
|
|
|
|
|
@@ -307,6 +304,7 @@ def cropdetect(ctx,
|
|
|
|
|
paths,
|
|
|
|
|
nice,
|
|
|
|
|
cpu):
|
|
|
|
|
from ffx.file_properties import FileProperties
|
|
|
|
|
|
|
|
|
|
existingSourcePaths = [p for p in paths if os.path.isfile(p)]
|
|
|
|
|
ctx.obj['logger'].debug(f"\nUnmuxing {len(existingSourcePaths)} files")
|
|
|
|
|
@@ -333,6 +331,7 @@ def cropdetect(ctx,
|
|
|
|
|
@click.pass_context
|
|
|
|
|
|
|
|
|
|
def shows(ctx):
|
|
|
|
|
from ffx.ffx_app import FfxApp
|
|
|
|
|
|
|
|
|
|
ctx.obj['command'] = 'shows'
|
|
|
|
|
|
|
|
|
|
@@ -341,6 +340,8 @@ def shows(ctx):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def checkUniqueDispositions(context, mediaDescriptor: MediaDescriptor):
|
|
|
|
|
from ffx.track_disposition import TrackDisposition
|
|
|
|
|
from ffx.track_type import TrackType
|
|
|
|
|
|
|
|
|
|
# Check for multiple default or forced dispositions if not set by user input or database requirements
|
|
|
|
|
#
|
|
|
|
|
@@ -390,7 +391,7 @@ def checkUniqueDispositions(context, mediaDescriptor: MediaDescriptor):
|
|
|
|
|
|
|
|
|
|
@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, av1, h264 or copy)", show_default=True)
|
|
|
|
|
@click.option('-v', '--video-encoder', type=str, default=DEFAULT_VIDEO_ENCODER_LABEL, help=f"Target video encoder (vp9, av1, h264 or copy)", show_default=True)
|
|
|
|
|
|
|
|
|
|
@click.option('-q', '--quality', type=str, default="", help=f"Quality settings to be used with VP9/H264 encoder")
|
|
|
|
|
@click.option('-p', '--preset', type=str, default="", help=f"Quality preset to be used with AV1 encoder")
|
|
|
|
|
@@ -507,6 +508,20 @@ def convert(ctx,
|
|
|
|
|
Filename extensions will be changed appropriately.
|
|
|
|
|
Suffices will we appended to filename in case of multiple created files
|
|
|
|
|
or if the filename has not changed."""
|
|
|
|
|
from ffx.ffx_controller import FfxController
|
|
|
|
|
from ffx.file_properties import FileProperties
|
|
|
|
|
from ffx.filter.crop_filter import CropFilter
|
|
|
|
|
from ffx.filter.deinterlace_filter import DeinterlaceFilter
|
|
|
|
|
from ffx.filter.nlmeans_filter import NlmeansFilter
|
|
|
|
|
from ffx.filter.preset_filter import PresetFilter
|
|
|
|
|
from ffx.filter.quality_filter import QualityFilter
|
|
|
|
|
from ffx.helper import filterFilename, getEpisodeFileBasename, substituteTmdbFilename
|
|
|
|
|
from ffx.shifted_season_controller import ShiftedSeasonController
|
|
|
|
|
from ffx.show_descriptor import ShowDescriptor
|
|
|
|
|
from ffx.tmdb_controller import TmdbController
|
|
|
|
|
from ffx.track_codec import TrackCodec
|
|
|
|
|
from ffx.track_disposition import TrackDisposition
|
|
|
|
|
from ffx.video_encoder import VideoEncoder
|
|
|
|
|
|
|
|
|
|
startTime = time.perf_counter()
|
|
|
|
|
|
|
|
|
|
@@ -519,8 +534,8 @@ def convert(ctx,
|
|
|
|
|
targetFormat = ''
|
|
|
|
|
targetExtension = 'mkv'
|
|
|
|
|
else:
|
|
|
|
|
targetFormat = FfxController.DEFAULT_FILE_FORMAT
|
|
|
|
|
targetExtension = FfxController.DEFAULT_FILE_EXTENSION
|
|
|
|
|
targetFormat = DEFAULT_CONTAINER_FORMAT
|
|
|
|
|
targetExtension = DEFAULT_CONTAINER_EXTENSION
|
|
|
|
|
|
|
|
|
|
context['use_tmdb'] = not no_tmdb
|
|
|
|
|
context['use_pattern'] = not no_pattern
|
|
|
|
|
@@ -540,7 +555,7 @@ def convert(ctx,
|
|
|
|
|
context['subtitle_prefix'] = subtitle_prefix
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 SUPPORTED_INPUT_FILE_EXTENSIONS]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# CLI Overrides
|
|
|
|
|
|