Streamlines imports and app start

This commit is contained in:
Javanaut
2026-04-11 14:57:01 +02:00
parent d19e69990a
commit 71553aad32
6 changed files with 199 additions and 79 deletions

View File

@@ -9,6 +9,8 @@
- The biggest near-term wins are in startup cost, repeated subprocess work, repeated database query patterns, and general repo hygiene. - The biggest near-term wins are in startup cost, repeated subprocess work, repeated database query patterns, and general repo hygiene.
- This list is intentionally optimization-oriented rather than bug-oriented. Some items below also improve correctness or maintainability, but they were selected because they can reduce runtime cost, operator friction, or iteration overhead. - This list is intentionally optimization-oriented rather than bug-oriented. Some items below also improve correctness or maintainability, but they were selected because they can reduce runtime cost, operator friction, or iteration overhead.
- A first modern integration slice now exists under [`tests/integration/subtrack_mapping`](/home/osgw/.local/src/codex/ffx/tests/integration/subtrack_mapping). Remaining test-suite cleanup is now mostly about migrating and shrinking the legacy harness surface under [`tests/legacy`](/home/osgw/.local/src/codex/ffx/tests/legacy). - A first modern integration slice now exists under [`tests/integration/subtrack_mapping`](/home/osgw/.local/src/codex/ffx/tests/integration/subtrack_mapping). Remaining test-suite cleanup is now mostly about migrating and shrinking the legacy harness surface under [`tests/legacy`](/home/osgw/.local/src/codex/ffx/tests/legacy).
- The CLI root now lazy-loads heavy runtime dependencies so lightweight commands such as `version`, `help`, `configure_workstation`, and `upgrade` stay import-light.
- Shared CLI defaults for container/output tokens now live outside [`src/ffx/ffx_controller.py`](/home/osgw/.local/src/codex/ffx/src/ffx/ffx_controller.py), and a focused unit test locks in the lazy-import contract.
- FFX logger setup now reuses named handlers, and fallback logger access no longer mutates handlers in ordinary constructors and helpers. - FFX logger setup now reuses named handlers, and fallback logger access no longer mutates handlers in ordinary constructors and helpers.
- The process wrapper now uses `subprocess.run(...)` with centralized command formatting plus stable timeout and missing-command error mapping. - The process wrapper now uses `subprocess.run(...)` with centralized command formatting plus stable timeout and missing-command error mapping.
- Active ORM controllers now use single-query accessors instead of paired `count()` plus `first()` lookups. - Active ORM controllers now use single-query accessors instead of paired `count()` plus `first()` lookups.
@@ -17,8 +19,8 @@
## Focused Snapshot ## Focused Snapshot
- Highest-leverage application optimizations: - Highest-leverage application optimizations:
- Lazy-load CLI command dependencies so lightweight commands do not import most of the app.
- Collapse repeated `ffprobe` calls into a single probe result per source file. - Collapse repeated `ffprobe` calls into a single probe result per source file.
- Revisit crop detection cost after the probe path is consolidated.
- Highest-leverage repo and workflow optimizations: - Highest-leverage repo and workflow optimizations:
- Consolidate setup and upgrade tooling to reduce overlapping shell-script responsibilities. - Consolidate setup and upgrade tooling to reduce overlapping shell-script responsibilities.
@@ -26,16 +28,7 @@
## Optimization Candidates ## Optimization Candidates
1. CLI startup and import cost 1. Media probing does two separate `ffprobe` subprocesses per file
- [`src/ffx/cli.py`](/home/osgw/.local/src/codex/ffx/src/ffx/cli.py) imports a large portion of the application at module import time, even for cheap commands such as `version`, `help`, `configure_workstation`, and `upgrade`.
- Optimization:
- Move heavy imports into the commands that actually need them.
- Keep the CLI root importable with only core stdlib and Click dependencies.
- Expected value:
- Faster startup for scripting and tooling commands.
- Less coupling between maintenance commands and the runtime stack.
2. Media probing does two separate `ffprobe` subprocesses per file
- [`src/ffx/file_properties.py`](/home/osgw/.local/src/codex/ffx/src/ffx/file_properties.py) calls `ffprobe` once for format data and once for stream data. - [`src/ffx/file_properties.py`](/home/osgw/.local/src/codex/ffx/src/ffx/file_properties.py) calls `ffprobe` once for format data and once for stream data.
- Optimization: - Optimization:
- Use one probe call that requests both format and streams. - Use one probe call that requests both format and streams.
@@ -44,7 +37,7 @@
- Less subprocess overhead. - Less subprocess overhead.
- Faster inspect and convert flows. - Faster inspect and convert flows.
3. Crop detection is always a full extra ffmpeg scan 2. Crop detection is always a full extra ffmpeg scan
- [`src/ffx/file_properties.py`](/home/osgw/.local/src/codex/ffx/src/ffx/file_properties.py) runs a dedicated `ffmpeg -vf cropdetect` pass for each file when crop detection is requested. - [`src/ffx/file_properties.py`](/home/osgw/.local/src/codex/ffx/src/ffx/file_properties.py) runs a dedicated `ffmpeg -vf cropdetect` pass for each file when crop detection is requested.
- Optimization: - Optimization:
- Cache crop results for repeated runs on the same source. - Cache crop results for repeated runs on the same source.
@@ -52,7 +45,7 @@
- Expected value: - Expected value:
- Lower latency on repeated experimentation. - Lower latency on repeated experimentation.
4. Tooling overlap and naming drift 3. Tooling overlap and naming drift
- There are still overlapping workstation-setup entrypoints across [`tools/configure_workstation.sh`](/home/osgw/.local/src/codex/ffx/tools/configure_workstation.sh), [`tools/setup.sh`](/home/osgw/.local/src/codex/ffx/tools/setup.sh), and newer CLI maintenance commands. - There are still overlapping workstation-setup entrypoints across [`tools/configure_workstation.sh`](/home/osgw/.local/src/codex/ffx/tools/configure_workstation.sh), [`tools/setup.sh`](/home/osgw/.local/src/codex/ffx/tools/setup.sh), and newer CLI maintenance commands.
- Optimization: - Optimization:
- Decide which scripts remain canonical. - Decide which scripts remain canonical.
@@ -62,7 +55,7 @@
- Less operator confusion. - Less operator confusion.
- Fewer duplicated procedures to maintain. - Fewer duplicated procedures to maintain.
5. Placeholder UI surfaces should either ship or disappear 4. Placeholder UI surfaces should either ship or disappear
- [`src/ffx/help_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/help_screen.py) and [`src/ffx/settings_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/settings_screen.py) are placeholders. - [`src/ffx/help_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/help_screen.py) and [`src/ffx/settings_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/settings_screen.py) are placeholders.
- Optimization: - Optimization:
- Either remove them from the active UI surface or complete them. - Either remove them from the active UI surface or complete them.
@@ -71,7 +64,7 @@
- Leaner interface. - Leaner interface.
- Lower UX ambiguity. - Lower UX ambiguity.
6. Large Textual screens repeat configuration and controller loading 5. Large Textual screens repeat configuration and controller loading
- Screens such as [`src/ffx/media_details_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/media_details_screen.py), [`src/ffx/pattern_details_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/pattern_details_screen.py), and [`src/ffx/show_details_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/show_details_screen.py) repeat setup patterns and local metadata filtering extraction. - Screens such as [`src/ffx/media_details_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/media_details_screen.py), [`src/ffx/pattern_details_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/pattern_details_screen.py), and [`src/ffx/show_details_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/show_details_screen.py) repeat setup patterns and local metadata filtering extraction.
- Optimization: - Optimization:
- Extract a shared screen base or helper for common config/controller/bootstrap logic. - Extract a shared screen base or helper for common config/controller/bootstrap logic.
@@ -80,7 +73,7 @@
- Lower maintenance overhead. - Lower maintenance overhead.
- Easier UI iteration. - Easier UI iteration.
7. Several helper functions are unfinished or dead-weight 6. Several helper functions are unfinished or dead-weight
- [`src/ffx/helper.py`](/home/osgw/.local/src/codex/ffx/src/ffx/helper.py) contains `permutateList(...): pass`. - [`src/ffx/helper.py`](/home/osgw/.local/src/codex/ffx/src/ffx/helper.py) contains `permutateList(...): pass`.
- There are many combinator and conversion placeholders across tests and migrations. - There are many combinator and conversion placeholders across tests and migrations.
- Optimization: - Optimization:
@@ -90,7 +83,7 @@
- Smaller mental model. - Smaller mental model.
- Less time spent re-evaluating inactive paths. - Less time spent re-evaluating inactive paths.
8. Test suite shape is expensive to understand and likely expensive to run 7. Test suite shape is expensive to understand and likely expensive to run
- The project still carries a large legacy matrix of combinator files under [`tests/legacy`](/home/osgw/.local/src/codex/ffx/tests/legacy), several placeholder `pass` implementations, and at least one suspicious filename with an embedded space: [`tests/legacy/disposition_combinator_2_3 .py`](/home/osgw/.local/src/codex/ffx/tests/legacy/disposition_combinator_2_3 .py). - The project still carries a large legacy matrix of combinator files under [`tests/legacy`](/home/osgw/.local/src/codex/ffx/tests/legacy), several placeholder `pass` implementations, and at least one suspicious filename with an embedded space: [`tests/legacy/disposition_combinator_2_3 .py`](/home/osgw/.local/src/codex/ffx/tests/legacy/disposition_combinator_2_3 .py).
- A first focused replacement slice now exists in [`tests/integration/subtrack_mapping/test_cli_bundle.py`](/home/osgw/.local/src/codex/ffx/tests/integration/subtrack_mapping/test_cli_bundle.py), so the remaining work is migration and consolidation rather than creating the modern test shape from scratch. - A first focused replacement slice now exists in [`tests/integration/subtrack_mapping/test_cli_bundle.py`](/home/osgw/.local/src/codex/ffx/tests/integration/subtrack_mapping/test_cli_bundle.py), so the remaining work is migration and consolidation rather than creating the modern test shape from scratch.
- Optimization: - Optimization:
@@ -101,7 +94,7 @@
- Faster contributor onboarding. - Faster contributor onboarding.
- Easier CI adoption later. - Easier CI adoption later.
9. Process resource limiting semantics could be clearer 8. Process resource limiting semantics could be clearer
- [`src/ffx/process.py`](/home/osgw/.local/src/codex/ffx/src/ffx/process.py) prepends `nice` and `cpulimit` directly when values are set. - [`src/ffx/process.py`](/home/osgw/.local/src/codex/ffx/src/ffx/process.py) prepends `nice` and `cpulimit` directly when values are set.
- Optimization: - Optimization:
- Validate and document effective behavior for combined `nice` + `cpulimit`. - Validate and document effective behavior for combined `nice` + `cpulimit`.
@@ -110,24 +103,16 @@
- Fewer surprises in production-like runs. - Fewer surprises in production-like runs.
- Easier support for user-reported performance behavior. - Easier support for user-reported performance behavior.
10. Import-time dependency coupling makes maintenance commands brittle 9. Regex and string utility cleanup
- Even after recent CLI maintenance additions, the top-level CLI module still imports most application modules before Click dispatch. - [`src/ffx/helper.py`](/home/osgw/.local/src/codex/ffx/src/ffx/helper.py) still has repeated string-replacement churn in filename/TMDB normalization helpers, and regex handling in helpers is easy to regress quietly.
- Optimization: - Optimization:
- Push imports for ORM, Textual, TMDB, ffmpeg helpers, and descriptors behind the commands that actually need them. - Keep regex literals raw and centralized where appropriate.
- Expected value:
- Maintenance commands such as setup and upgrade stay usable when optional runtime dependencies are broken.
- Better separation between media runtime code and maintenance tooling.
11. Regex and string utility cleanup
- [`src/ffx/helper.py`](/home/osgw/.local/src/codex/ffx/src/ffx/helper.py) still emits a `SyntaxWarning` for `RICH_COLOR_PATTERN`.
- Optimization:
- Convert regex literals to raw strings where appropriate.
- Review filename and TMDB substitution helpers for repeated string churn. - Review filename and TMDB substitution helpers for repeated string churn.
- Expected value: - Expected value:
- Cleaner runtime output. - Cleaner runtime output.
- Less warning noise during dry-run maintenance commands. - Less warning noise during dry-run maintenance commands.
12. Database startup always runs schema creation and version checks 10. Database startup always runs schema creation and version checks
- [`src/ffx/database.py`](/home/osgw/.local/src/codex/ffx/src/ffx/database.py) runs `Base.metadata.create_all(...)` and version checks every time a DB-backed context is created. - [`src/ffx/database.py`](/home/osgw/.local/src/codex/ffx/src/ffx/database.py) runs `Base.metadata.create_all(...)` and version checks every time a DB-backed context is created.
- Optimization: - Optimization:
- Measure startup cost and consider separating bootstrapping from ordinary command execution. - Measure startup cost and consider separating bootstrapping from ordinary command execution.
@@ -151,9 +136,9 @@
1. Triage the list into quick wins, medium refactors, and long-horizon cleanup. 1. Triage the list into quick wins, medium refactors, and long-horizon cleanup.
2. Tackle the cheapest high-impact items first: 2. Tackle the cheapest high-impact items first:
- regex raw-string warning cleanup,
- single-call `ffprobe` refactor. - single-call `ffprobe` refactor.
3. Decide whether maintenance/tooling command imports should be split from media-runtime imports before adding more CLI maintenance surface. - crop detection sampling or caching pass.
3. Decide which setup and upgrade entrypoints stay canonical before adding more maintenance surface.
## Delete When ## Delete When

View File

@@ -1,6 +1,9 @@
#! /usr/bin/python3 #! /usr/bin/python3
from __future__ import annotations
import os, sys, click, time, shutil, subprocess 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 # Allow direct execution via `python src/ffx/cli.py` by preferring the package
# root on sys.path. # 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 = [p for p in sys.path if os.path.abspath(p) != os.path.abspath(script_dir)]
sys.path.insert(0, package_root) 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 LIGHTWEIGHT_COMMANDS = {None, 'version', 'help', 'configure_workstation', 'upgrade'}
from ffx.ffx_controller import FfxController
from ffx.tmdb_controller import TmdbController
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() @click.group()
@@ -58,11 +43,18 @@ def ffx(ctx, database_file, verbose, dry_run):
ctx.obj = {} 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['dry_run'] = dry_run
ctx.obj['verbosity'] = verbose ctx.obj['verbosity'] = verbose
return 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['config'] = ConfigurationController()
ctx.obj['database'] = databaseContext(databasePath=database_file ctx.obj['database'] = databaseContext(databasePath=database_file
@@ -184,6 +176,7 @@ def upgrade(ctx, branch):
@click.pass_context @click.pass_context
@click.argument('filename', nargs=1) @click.argument('filename', nargs=1)
def inspect(ctx, filename): def inspect(ctx, filename):
from ffx.ffx_app import FfxApp
ctx.obj['command'] = 'inspect' ctx.obj['command'] = 'inspect'
ctx.obj['arguments'] = {} ctx.obj['arguments'] = {}
@@ -196,7 +189,7 @@ def inspect(ctx, filename):
def getUnmuxSequence(trackDescriptor: TrackDescriptor, sourcePath, targetPrefix, targetDirectory = ''): def getUnmuxSequence(trackDescriptor: TrackDescriptor, sourcePath, targetPrefix, targetDirectory = ''):
# executable and input file # executable and input file
commandTokens = FfxController.COMMAND_TOKENS + ['-i', sourcePath] commandTokens = list(FFMPEG_COMMAND_TOKENS) + ['-i', sourcePath]
trackType = trackDescriptor.getType() trackType = trackDescriptor.getType()
@@ -237,6 +230,10 @@ def unmux(ctx,
subtitles_only, subtitles_only,
nice, nice,
cpu): 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)] existingSourcePaths = [p for p in paths if os.path.isfile(p)]
ctx.obj['logger'].debug(f"\nUnmuxing {len(existingSourcePaths)} files") ctx.obj['logger'].debug(f"\nUnmuxing {len(existingSourcePaths)} files")
@@ -307,6 +304,7 @@ def cropdetect(ctx,
paths, paths,
nice, nice,
cpu): cpu):
from ffx.file_properties import FileProperties
existingSourcePaths = [p for p in paths if os.path.isfile(p)] existingSourcePaths = [p for p in paths if os.path.isfile(p)]
ctx.obj['logger'].debug(f"\nUnmuxing {len(existingSourcePaths)} files") ctx.obj['logger'].debug(f"\nUnmuxing {len(existingSourcePaths)} files")
@@ -333,6 +331,7 @@ def cropdetect(ctx,
@click.pass_context @click.pass_context
def shows(ctx): def shows(ctx):
from ffx.ffx_app import FfxApp
ctx.obj['command'] = 'shows' ctx.obj['command'] = 'shows'
@@ -341,6 +340,8 @@ def shows(ctx):
def checkUniqueDispositions(context, mediaDescriptor: MediaDescriptor): 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 # 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('-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('-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") @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. Filename extensions will be changed appropriately.
Suffices will we appended to filename in case of multiple created files Suffices will we appended to filename in case of multiple created files
or if the filename has not changed.""" 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() startTime = time.perf_counter()
@@ -519,8 +534,8 @@ def convert(ctx,
targetFormat = '' targetFormat = ''
targetExtension = 'mkv' targetExtension = 'mkv'
else: else:
targetFormat = FfxController.DEFAULT_FILE_FORMAT targetFormat = DEFAULT_CONTAINER_FORMAT
targetExtension = FfxController.DEFAULT_FILE_EXTENSION targetExtension = DEFAULT_CONTAINER_EXTENSION
context['use_tmdb'] = not no_tmdb context['use_tmdb'] = not no_tmdb
context['use_pattern'] = not no_pattern context['use_pattern'] = not no_pattern
@@ -540,7 +555,7 @@ def convert(ctx,
context['subtitle_prefix'] = subtitle_prefix 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 # CLI Overrides

View File

@@ -4,6 +4,13 @@ DATABASE_VERSION = 2
DEFAULT_QUALITY = 32 DEFAULT_QUALITY = 32
DEFAULT_AV1_PRESET = 5 DEFAULT_AV1_PRESET = 5
DEFAULT_VIDEO_ENCODER_LABEL = "vp9"
DEFAULT_CONTAINER_FORMAT = "webm"
DEFAULT_CONTAINER_EXTENSION = "webm"
SUPPORTED_INPUT_FILE_EXTENSIONS = ("mkv", "mp4", "avi", "flv", "webm")
FFMPEG_COMMAND_TOKENS = ("ffmpeg", "-y")
FFMPEG_NULL_OUTPUT_TOKENS = ("-f", "null", "/dev/null")
DEFAULT_STEREO_BANDWIDTH = "112" DEFAULT_STEREO_BANDWIDTH = "112"
DEFAULT_AC3_BANDWIDTH = "256" DEFAULT_AC3_BANDWIDTH = "256"
DEFAULT_DTS_BANDWIDTH = "320" DEFAULT_DTS_BANDWIDTH = "320"

View File

@@ -10,7 +10,16 @@ from ffx.track_codec import TrackCodec
from ffx.video_encoder import VideoEncoder from ffx.video_encoder import VideoEncoder
from ffx.process import executeProcess from ffx.process import executeProcess
from ffx.constants import DEFAULT_cut_start, DEFAULT_cut_length from ffx.constants import (
DEFAULT_CONTAINER_EXTENSION,
DEFAULT_CONTAINER_FORMAT,
DEFAULT_VIDEO_ENCODER_LABEL,
DEFAULT_cut_start,
DEFAULT_cut_length,
FFMPEG_COMMAND_TOKENS,
FFMPEG_NULL_OUTPUT_TOKENS,
SUPPORTED_INPUT_FILE_EXTENSIONS,
)
from ffx.filter.quality_filter import QualityFilter from ffx.filter.quality_filter import QualityFilter
from ffx.filter.preset_filter import PresetFilter from ffx.filter.preset_filter import PresetFilter
@@ -21,17 +30,17 @@ from ffx.model.pattern import Pattern
class FfxController(): class FfxController():
COMMAND_TOKENS = ['ffmpeg', '-y'] COMMAND_TOKENS = list(FFMPEG_COMMAND_TOKENS)
NULL_TOKENS = ['-f', 'null', '/dev/null'] # -f null /dev/null NULL_TOKENS = list(FFMPEG_NULL_OUTPUT_TOKENS) # -f null /dev/null
TEMP_FILE_NAME = "ffmpeg2pass-0.log" TEMP_FILE_NAME = "ffmpeg2pass-0.log"
DEFAULT_VIDEO_ENCODER = VideoEncoder.VP9.label() DEFAULT_VIDEO_ENCODER = DEFAULT_VIDEO_ENCODER_LABEL
DEFAULT_FILE_FORMAT = 'webm' DEFAULT_FILE_FORMAT = DEFAULT_CONTAINER_FORMAT
DEFAULT_FILE_EXTENSION = 'webm' DEFAULT_FILE_EXTENSION = DEFAULT_CONTAINER_EXTENSION
INPUT_FILE_EXTENSIONS = ['mkv', 'mp4', 'avi', 'flv', 'webm'] INPUT_FILE_EXTENSIONS = list(SUPPORTED_INPUT_FILE_EXTENSIONS)
CHANNEL_MAP_5_1 = 'FL-FL|FR-FR|FC-FC|LFE-LFE|SL-BL|SR-BR:5.1' CHANNEL_MAP_5_1 = 'FL-FL|FR-FR|FC-FC|LFE-LFE|SL-BL|SR-BR:5.1'

View File

@@ -16,7 +16,7 @@ DIFF_REMOVED_KEY = 'removed'
DIFF_CHANGED_KEY = 'changed' DIFF_CHANGED_KEY = 'changed'
DIFF_UNCHANGED_KEY = 'unchanged' DIFF_UNCHANGED_KEY = 'unchanged'
RICH_COLOR_PATTERN = '\[[a-z_]+\](.+)\[\/[a-z_]+\]' RICH_COLOR_PATTERN = '\\[[a-z_]+\\](.+)\\[\\/[a-z_]+\\]'
def dictDiff(a : dict, b : dict, ignoreKeys: list = [], removeKeys: list = []): def dictDiff(a : dict, b : dict, ignoreKeys: list = [], removeKeys: list = []):

View File

@@ -0,0 +1,104 @@
from __future__ import annotations
import json
from pathlib import Path
import subprocess
import sys
import textwrap
import unittest
REPO_ROOT = Path(__file__).resolve().parents[2]
SRC_ROOT = REPO_ROOT / "src"
HEAVY_MODULES = [
"ffx.configuration_controller",
"ffx.database",
"ffx.ffx_app",
"ffx.ffx_controller",
"ffx.file_properties",
"ffx.tmdb_controller",
]
class CliLazyImportTests(unittest.TestCase):
def run_python(self, code: str) -> dict:
completed = subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
cwd=REPO_ROOT,
text=True,
)
if completed.returncode != 0:
self.fail(
"Python helper failed\n"
f"STDOUT:\n{completed.stdout}\n"
f"STDERR:\n{completed.stderr}"
)
return json.loads(completed.stdout)
def test_importing_cli_keeps_runtime_modules_unloaded(self):
result = self.run_python(
textwrap.dedent(
f"""
import json
import sys
sys.path.insert(0, {str(SRC_ROOT)!r})
import ffx.cli
print(json.dumps({{
"modules": {{
module_name: module_name in sys.modules
for module_name in {HEAVY_MODULES!r}
}},
}}))
"""
)
)
self.assertTrue(
all(not is_loaded for is_loaded in result["modules"].values()),
result["modules"],
)
def test_lightweight_configure_workstation_command_stays_light(self):
result = self.run_python(
textwrap.dedent(
f"""
import json
import sys
from click.testing import CliRunner
sys.path.insert(0, {str(SRC_ROOT)!r})
import ffx.cli
runner = CliRunner()
invoke_result = runner.invoke(
ffx.cli.ffx,
["--dry-run", "configure_workstation", "--check"],
)
if invoke_result.exit_code != 0:
raise SystemExit(invoke_result.output)
print(json.dumps({{
"output": invoke_result.output,
"modules": {{
module_name: module_name in sys.modules
for module_name in {HEAVY_MODULES!r}
}},
}}))
"""
)
)
self.assertIn("configure_workstation.sh --check", result["output"])
self.assertTrue(
all(not is_loaded for is_loaded in result["modules"].values()),
result["modules"],
)
if __name__ == "__main__":
unittest.main()