Adds Q/P values to output file metadata

This commit is contained in:
Javanaut
2026-04-11 17:46:16 +02:00
parent ebdc23c3ce
commit 0a41998e29
5 changed files with 168 additions and 0 deletions

View File

@@ -74,6 +74,7 @@
- No explicit prioritization owner or milestone for the optimization backlog. - No explicit prioritization owner or milestone for the optimization backlog.
- No benchmark or timing harness exists for startup, probe, DB, or conversion orchestration overhead. - No benchmark or timing harness exists for startup, probe, DB, or conversion orchestration overhead.
- Repo hygiene is still mixed with generated artifacts and some clearly unfinished files. - Repo hygiene is still mixed with generated artifacts and some clearly unfinished files.
- The legacy TMDB-backed `Scenario 4` path is currently blocked by a pattern/track regression: `Patterns must define at least one track before they can be stored.` This surfaced while rerunning TMDB-dependent checks after the zero-track pattern hardening.
## Next ## Next
@@ -81,6 +82,7 @@
2. Tackle the cheapest remaining product-surface cleanup first: 2. Tackle the cheapest remaining product-surface cleanup first:
- placeholder UI surfaces and dead helper cleanup. - placeholder UI surfaces and dead helper cleanup.
3. Continue replacing oversized legacy test matrices with focused modern integration and unit coverage. 3. Continue replacing oversized legacy test matrices with focused modern integration and unit coverage.
4. Triage the legacy `Scenario 4` pattern/track failure and decide whether to fix the harness, adapt it to the zero-track guard, or retire that path during the ongoing test-suite migration.
## Delete When ## Delete When

View File

@@ -14,6 +14,13 @@ that area.
- Agents shall not silently substitute `python`, `python3`, or another interpreter for Python-side test work. - Agents shall not silently substitute `python`, `python3`, or another interpreter for Python-side test work.
- If `~/.local/share/ffx.venv/bin/python` is missing or not executable, agents shall stop and report the missing venv instead of continuing with Python-side test execution. - If `~/.local/share/ffx.venv/bin/python` is missing or not executable, agents shall stop and report the missing venv instead of continuing with Python-side test execution.
## Shell Environment Requirement
- Agents shall source `~/.bashrc` from an interactive Bash shell before running TMDB-dependent test commands or TMDB-dependent `python -m ffx ...` test invocations.
- Agents shall not source `~/.bashrc.d/interactive/77_tmdb.sh` directly for normal test work; `~/.bashrc` is the required entry point.
- In automation this means agents shall use an interactive Bash invocation such as `bash -ic 'source ~/.bashrc && ...'`, because a non-interactive `bash -lc` returns from `~/.bashrc` before the interactive fragments are loaded.
- If sourcing `~/.bashrc` still does not provide required shell environment such as `TMDB_API_KEY`, agents shall stop and report the missing environment instead of continuing with TMDB-dependent test execution.
## Current Harness ## Current Harness
- Entrypoint: `~/.local/share/ffx.venv/bin/python tests/legacy_runner.py run` - Entrypoint: `~/.local/share/ffx.venv/bin/python tests/legacy_runner.py run`

View File

@@ -171,6 +171,18 @@ class FfxController():
return [outputFilePath] return [outputFilePath]
def generateEncodingMetadataTags(self, videoEncoder: VideoEncoder, quality, preset) -> dict:
metadataTags = {}
if videoEncoder in (VideoEncoder.AV1, VideoEncoder.H264, VideoEncoder.VP9):
metadataTags["ENCODING_QUALITY"] = str(quality)
if videoEncoder == VideoEncoder.AV1:
metadataTags["ENCODING_PRESET"] = str(preset)
return metadataTags
def generateAudioEncodingTokens(self): def generateAudioEncodingTokens(self):
"""Generates ffmpeg options audio streams including channel remapping, codec and bitrate""" """Generates ffmpeg options audio streams including channel remapping, codec and bitrate"""
@@ -261,6 +273,11 @@ class FfxController():
preset = presetFilters[0]['parameters']['preset'] if presetFilters else PresetFilter.DEFAULT_PRESET preset = presetFilters[0]['parameters']['preset'] if presetFilters else PresetFilter.DEFAULT_PRESET
self.__context['encoding_metadata_tags'] = self.generateEncodingMetadataTags(
videoEncoder,
quality,
preset,
)
filterParamTokens = [] filterParamTokens = []

View File

@@ -295,6 +295,9 @@ class MediaDescriptorChangeSet():
+ f":{trackDescriptor.getSubIndex()}", + f":{trackDescriptor.getSubIndex()}",
f"{removeKey}="] f"{removeKey}="]
for tagKey, tagValue in self.__context.get('encoding_metadata_tags', {}).items():
metadataTokens += [f"-metadata:g", f"{tagKey}={tagValue}"]
return metadataTokens return metadataTokens

View File

@@ -0,0 +1,139 @@
from __future__ import annotations
from pathlib import Path
import sys
import unittest
from unittest.mock import patch
SRC_ROOT = Path(__file__).resolve().parents[2] / "src"
if str(SRC_ROOT) not in sys.path:
sys.path.insert(0, str(SRC_ROOT))
from ffx.ffx_controller import FfxController # noqa: E402
from ffx.logging_utils import get_ffx_logger # noqa: E402
from ffx.media_descriptor import MediaDescriptor # noqa: E402
from ffx.track_codec import TrackCodec # noqa: E402
from ffx.track_descriptor import TrackDescriptor # noqa: E402
from ffx.track_type import TrackType # noqa: E402
from ffx.video_encoder import VideoEncoder # noqa: E402
class StaticConfig:
def __init__(self, data: dict | None = None):
self._data = data or {}
def getData(self):
return self._data
class FfxControllerTests(unittest.TestCase):
def make_context(self, video_encoder: VideoEncoder) -> dict:
return {
"logger": get_ffx_logger(),
"config": StaticConfig(),
"video_encoder": video_encoder,
"dry_run": False,
"perform_cut": False,
"bitrates": {
"stereo": "112k",
"ac3": "256k",
"dts": "320k",
},
}
def make_media_descriptors(self) -> tuple[MediaDescriptor, MediaDescriptor]:
descriptor = MediaDescriptor(
track_descriptors=[
TrackDescriptor(
index=0,
source_index=0,
sub_index=0,
track_type=TrackType.VIDEO,
codec_name=TrackCodec.H264,
)
]
)
source_descriptor = MediaDescriptor(
track_descriptors=[
TrackDescriptor(
index=0,
source_index=0,
sub_index=0,
track_type=TrackType.VIDEO,
codec_name=TrackCodec.H264,
)
]
)
return descriptor, source_descriptor
def test_vp9_run_job_emits_file_level_encoding_quality_metadata(self):
context = self.make_context(VideoEncoder.VP9)
target_descriptor, source_descriptor = self.make_media_descriptors()
controller = FfxController(context, target_descriptor, source_descriptor)
commands = []
with (
patch.object(
controller,
"executeCommandSequence",
side_effect=lambda command: commands.append(command) or ("", "", 0),
),
patch("ffx.ffx_controller.os.path.exists", return_value=False),
):
controller.runJob(
"input.mkv",
"output.webm",
targetFormat="webm",
chainIteration=[
{
"identifier": "quality",
"parameters": {"quality": 27},
}
],
)
self.assertEqual(2, len(commands))
self.assertIn("-metadata:g", commands[1])
self.assertIn("ENCODING_QUALITY=27", commands[1])
self.assertFalse(
any(token.startswith("ENCODING_PRESET=") for token in commands[1])
)
def test_av1_run_job_emits_file_level_quality_and_preset_metadata(self):
context = self.make_context(VideoEncoder.AV1)
target_descriptor, source_descriptor = self.make_media_descriptors()
controller = FfxController(context, target_descriptor, source_descriptor)
commands = []
with patch.object(
controller,
"executeCommandSequence",
side_effect=lambda command: commands.append(command) or ("", "", 0),
):
controller.runJob(
"input.mkv",
"output.webm",
targetFormat="webm",
chainIteration=[
{
"identifier": "quality",
"parameters": {"quality": 29},
},
{
"identifier": "preset",
"parameters": {"preset": 7},
},
],
)
self.assertEqual(1, len(commands))
self.assertIn("-metadata:g", commands[0])
self.assertIn("ENCODING_QUALITY=29", commands[0])
self.assertIn("ENCODING_PRESET=7", commands[0])
if __name__ == "__main__":
unittest.main()