Merge branch 'dev' of gitea.maveno.de:Javanaut/ffx into dev

This commit is contained in:
Javanaut
2026-06-15 11:17:21 +02:00
16 changed files with 415 additions and 21 deletions

View File

@@ -18,6 +18,7 @@ from tests.support.ffx_bundle import (
write_vtt,
)
from ffx.attachment_format import AttachmentFormat
from ffx.track_type import TrackType
try:
@@ -280,6 +281,72 @@ class SubtrackMappingBundleTests(unittest.TestCase):
self.assertIn("non-existent source track #99", error_output)
self.assertFalse(expected_output_path(self.workdir, source_filename).exists())
def test_styled_ass_source_preserves_current_font_attachments_when_pattern_count_differs(self):
source_filename = "styled_ass_s01e01.mkv"
source_path = create_source_fixture(
self.workdir,
source_filename,
[
SourceTrackSpec(TrackType.VIDEO, identity="video-0"),
SourceTrackSpec(TrackType.AUDIO, identity="audio-1", language="eng"),
SourceTrackSpec(
TrackType.SUBTITLE,
identity="subtitle-2",
language="eng",
subtitle_lines=("styled subtitle payload",),
),
SourceTrackSpec(TrackType.ATTACHMENT, attachment_name="current.ttf"),
],
subtitle_encoder="ass",
)
prepare_pattern_database(
self.database_path,
r"^styled_ass_(s[0-9]+e[0-9]+)\.mkv$",
[
PatternTrackSpec(index=0, source_index=0, track_type=TrackType.VIDEO),
PatternTrackSpec(index=1, source_index=1, track_type=TrackType.AUDIO),
PatternTrackSpec(index=2, source_index=2, track_type=TrackType.SUBTITLE),
PatternTrackSpec(
index=3,
source_index=3,
track_type=TrackType.ATTACHMENT,
attachment_format=AttachmentFormat.TTF,
),
PatternTrackSpec(
index=4,
source_index=4,
track_type=TrackType.ATTACHMENT,
attachment_format=AttachmentFormat.TTF,
),
],
)
completed = run_ffx_convert(
self.workdir,
self.home_dir,
self.database_path,
"--video-encoder",
"copy",
"--no-tmdb",
"--no-prompt",
"--no-signature",
str(source_path),
)
self.assertCompleted(completed)
self.assertIn("Styled ASS subtitles", completed.stdout)
output_path = expected_output_path(self.workdir, source_filename)
streams = ffprobe_json(output_path)["streams"]
self.assertEqual(
[stream["codec_type"] for stream in streams],
["video", "audio", "subtitle", "attachment"],
)
self.assertEqual(streams[2]["codec_name"], "ass")
self.assertEqual(streams[3]["codec_name"], "ttf")
self.assertEqual(get_tag(streams[3], "filename"), "current.ttf")
def test_external_subtitle_file_replaces_payload_and_overrides_metadata(self):
source_filename = "substitute_s01e01.mkv"
self.write_config(

View File

@@ -18,6 +18,7 @@ if str(SRC_ROOT) not in sys.path:
sys.path.insert(0, str(SRC_ROOT))
from ffx.attachment_format import AttachmentFormat
from ffx.audio_layout import AudioLayout
from ffx.database import databaseContext
from ffx.pattern_controller import PatternController
@@ -56,6 +57,7 @@ class PatternTrackSpec:
tags: Mapping[str, str] = field(default_factory=dict)
dispositions: tuple[TrackDisposition, ...] = ()
audio_layout: AudioLayout = AudioLayout.LAYOUT_STEREO
attachment_format: AttachmentFormat = AttachmentFormat.UNKNOWN
def make_logger(name: str) -> logging.Logger:
@@ -299,6 +301,8 @@ def prepare_pattern_database(database_path: Path, filename_pattern: str, track_s
}
if track.track_type == TrackType.AUDIO:
kwargs[TrackDescriptor.AUDIO_LAYOUT_KEY] = track.audio_layout
if track.track_type == TrackType.ATTACHMENT:
kwargs[TrackDescriptor.ATTACHMENT_FORMAT_KEY] = track.attachment_format
track_descriptors.append(TrackDescriptor(**kwargs))
pattern_id = PatternController(context).savePatternSchema(

View File

@@ -13,6 +13,7 @@ if str(SRC_ROOT) not in sys.path:
from ffx.media_descriptor import MediaDescriptor # noqa: E402
from ffx.media_descriptor_change_set import MediaDescriptorChangeSet # noqa: E402
from ffx.attachment_format import AttachmentFormat # noqa: E402
from ffx.track_descriptor import TrackDescriptor # noqa: E402
from ffx.track_type import TrackType # noqa: E402
from ffx.i18n import set_current_language # noqa: E402
@@ -436,6 +437,47 @@ class MediaDescriptorChangeSetTests(unittest.TestCase):
self.assertNotIn("creation_time=", metadata_tokens)
self.assertNotIn("BPS=", metadata_tokens)
def test_attachment_tracks_are_ignored_for_pattern_comparison(self):
context = {
"logger": get_ffx_logger(),
"config": StaticConfig({}),
}
source_track = TrackDescriptor(
index=0,
source_index=0,
sub_index=0,
track_type=TrackType.ATTACHMENT,
attachment_format=AttachmentFormat.TTF,
tags={"filename": "current.ttf", "mimetype": "font/ttf"},
)
target_track = TrackDescriptor(
index=0,
source_index=0,
sub_index=0,
track_type=TrackType.ATTACHMENT,
attachment_format=AttachmentFormat.TTF,
tags={"filename": "stored.ttf", "mimetype": "font/ttf"},
)
stale_target_track = TrackDescriptor(
index=1,
source_index=1,
sub_index=1,
track_type=TrackType.ATTACHMENT,
attachment_format=AttachmentFormat.TTF,
tags={"filename": "missing.ttf", "mimetype": "font/ttf"},
)
change_set = MediaDescriptorChangeSet(
context,
MediaDescriptor(track_descriptors=[target_track, stale_target_track]),
MediaDescriptor(track_descriptors=[source_track]),
)
self.assertEqual({}, change_set.getChangeSetObj())
self.assertEqual([], change_set.generateMetadataTokens())
self.assertEqual([], change_set.generateDispositionTokens())
def test_normalization_can_be_disabled_per_context(self):
context = {
"logger": get_ffx_logger(),

View File

@@ -193,6 +193,36 @@ class PatternManagementTests(unittest.TestCase):
self.assertIn("at least one track", str(caught.exception))
def test_save_pattern_schema_does_not_persist_attachment_tracks(self):
pattern_id = self.save_pattern(
1,
r"^noattachments_(s[0-9]+e[0-9]+)\.mkv$",
tracks=[
make_track_descriptor(0, track_type=TrackType.VIDEO),
make_track_descriptor(1, track_type=TrackType.ATTACHMENT),
],
)
Session = self.context["database"]["session"]
session = Session()
try:
tracks = session.query(Pattern).filter(Pattern.id == pattern_id).first().tracks
self.assertEqual(1, len(tracks))
self.assertEqual(TrackType.VIDEO, tracks[0].getType())
finally:
session.close()
def test_track_controller_does_not_add_attachment_tracks_to_patterns(self):
pattern_id = self.save_pattern(1, r"^skipadd_(s[0-9]+e[0-9]+)\.mkv$")
added = self.track_controller.addTrack(
make_track_descriptor(1, track_type=TrackType.ATTACHMENT),
patternId=pattern_id,
)
self.assertFalse(added)
self.assertEqual(1, len(self.track_controller.findTracks(pattern_id)))
def test_match_filename_rejects_existing_trackless_pattern_rows(self):
self.insert_trackless_pattern_row(1, r"^invalid_(s[0-9]+e[0-9]+)\.mkv$")

View File

@@ -15,12 +15,13 @@ if str(SRC_ROOT) not in sys.path:
from ffx.audio_layout import AudioLayout # noqa: E402
from ffx.attachment_format import AttachmentFormat # noqa: E402
from ffx.helper import DIFF_ADDED_KEY # noqa: E402
from ffx.helper import DIFF_ADDED_KEY, DIFF_REMOVED_KEY # noqa: E402
from ffx.iso_language import IsoLanguage # noqa: E402
from ffx.logging_utils import get_ffx_logger # noqa: E402
from ffx.inspect_details_screen import InspectDetailsScreen # noqa: E402
from ffx.i18n import set_current_language # noqa: E402
from ffx.media_descriptor import MediaDescriptor # noqa: E402
from ffx.media_descriptor_change_set import MediaDescriptorChangeSet # noqa: E402
from ffx.media_edit_screen import MediaEditScreen # noqa: E402
from ffx.pattern_details_screen import PatternDetailsScreen # noqa: E402
from ffx.show_descriptor import ShowDescriptor # noqa: E402
@@ -822,6 +823,89 @@ class TagTableScreenStateTests(unittest.TestCase):
self.assertEqual("unknown", row[3])
self.assertEqual(" ", row[5])
def test_inspect_details_screen_uses_source_font_attachments_for_styled_ass(self):
class _Config:
def getData(self):
return {}
class _Pattern:
def __init__(self, media_descriptor):
self._media_descriptor = media_descriptor
def getMediaDescriptor(self, _context):
return self._media_descriptor
source_descriptor = MediaDescriptor(
track_descriptors=[
TrackDescriptor(
index=0,
source_index=0,
sub_index=0,
track_type=TrackType.SUBTITLE,
codec_name=TrackCodec.ASS,
tags={"title": "Styled Subtitle"},
),
TrackDescriptor(
index=1,
source_index=1,
sub_index=0,
track_type=TrackType.ATTACHMENT,
attachment_format=AttachmentFormat.TTF,
tags={"filename": "current.ttf", "mimetype": "font/ttf"},
),
]
)
pattern_descriptor = MediaDescriptor(
track_descriptors=[
TrackDescriptor(
index=0,
source_index=0,
sub_index=0,
track_type=TrackType.SUBTITLE,
codec_name=TrackCodec.ASS,
tags={"title": "Styled Subtitle"},
),
TrackDescriptor(
index=1,
source_index=1,
sub_index=0,
track_type=TrackType.ATTACHMENT,
attachment_format=AttachmentFormat.TTF,
tags={"filename": "old.ttf", "mimetype": "font/ttf"},
),
TrackDescriptor(
index=2,
source_index=2,
sub_index=1,
track_type=TrackType.ATTACHMENT,
attachment_format=AttachmentFormat.TTF,
tags={"filename": "missing.ttf", "mimetype": "font/ttf"},
),
]
)
screen = object.__new__(InspectDetailsScreen)
screen.context = {"logger": get_ffx_logger(), "config": _Config()}
resolved_descriptor = screen._resolve_target_media_descriptor(
_Pattern(pattern_descriptor),
source_descriptor,
)
attachment_tracks = resolved_descriptor.getAttachmentTracks()
self.assertEqual(1, len(attachment_tracks))
self.assertEqual({"filename": "current.ttf", "mimetype": "font/ttf"}, attachment_tracks[0].getTags())
change_set = MediaDescriptorChangeSet(
screen.context,
resolved_descriptor,
source_descriptor,
).getChangeSetObj()
self.assertNotIn(
1,
change_set.get("tracks", {}).get(DIFF_REMOVED_KEY, {}),
)
def test_inspect_details_screen_maps_target_selection_back_to_source_track(self):
source_track = TrackDescriptor(
index=3,