Adds UI tweaks nightly
This commit is contained in:
@@ -247,7 +247,8 @@ class MediaDescriptorChangeSetTests(unittest.TestCase):
|
||||
self.assertIn("title=German", metadata_tokens)
|
||||
self.assertNotIn("title=Deutsch", metadata_tokens)
|
||||
|
||||
def test_non_subtitle_track_without_title_does_not_get_language_name(self):
|
||||
def test_audio_track_without_title_gets_language_name_when_normalization_enabled(self):
|
||||
set_current_language("de")
|
||||
context = {
|
||||
"logger": get_ffx_logger(),
|
||||
"config": StaticConfig({}),
|
||||
@@ -278,6 +279,73 @@ class MediaDescriptorChangeSetTests(unittest.TestCase):
|
||||
|
||||
self.assertIn("-metadata:s:a:0", metadata_tokens)
|
||||
self.assertIn("language=deu", metadata_tokens)
|
||||
self.assertIn("title=Deutsch", metadata_tokens)
|
||||
|
||||
def test_video_track_without_title_gets_language_name_when_normalization_enabled(self):
|
||||
set_current_language("de")
|
||||
context = {
|
||||
"logger": get_ffx_logger(),
|
||||
"config": StaticConfig({}),
|
||||
}
|
||||
|
||||
source_track = TrackDescriptor(
|
||||
index=0,
|
||||
source_index=0,
|
||||
sub_index=0,
|
||||
track_type=TrackType.VIDEO,
|
||||
tags={"language": "ger"},
|
||||
)
|
||||
target_track = TrackDescriptor(
|
||||
index=0,
|
||||
source_index=0,
|
||||
sub_index=0,
|
||||
track_type=TrackType.VIDEO,
|
||||
tags={"language": "ger"},
|
||||
)
|
||||
|
||||
change_set = MediaDescriptorChangeSet(
|
||||
context,
|
||||
MediaDescriptor(track_descriptors=[target_track]),
|
||||
MediaDescriptor(track_descriptors=[source_track]),
|
||||
)
|
||||
|
||||
metadata_tokens = change_set.generateMetadataTokens()
|
||||
|
||||
self.assertIn("language=deu", metadata_tokens)
|
||||
self.assertIn("title=Deutsch", metadata_tokens)
|
||||
|
||||
def test_changed_track_language_does_not_autofill_title_when_title_already_exists(self):
|
||||
set_current_language("de")
|
||||
context = {
|
||||
"logger": get_ffx_logger(),
|
||||
"config": StaticConfig({}),
|
||||
}
|
||||
|
||||
source_track = TrackDescriptor(
|
||||
index=0,
|
||||
source_index=0,
|
||||
sub_index=0,
|
||||
track_type=TrackType.SUBTITLE,
|
||||
tags={"language": "ger", "title": "Deutsch [FN]"},
|
||||
)
|
||||
target_track = TrackDescriptor(
|
||||
index=0,
|
||||
source_index=0,
|
||||
sub_index=0,
|
||||
track_type=TrackType.SUBTITLE,
|
||||
tags={"language": "jpn", "title": "Deutsch [FN]"},
|
||||
)
|
||||
|
||||
change_set = MediaDescriptorChangeSet(
|
||||
context,
|
||||
MediaDescriptor(track_descriptors=[target_track]),
|
||||
MediaDescriptor(track_descriptors=[source_track]),
|
||||
)
|
||||
|
||||
metadata_tokens = change_set.generateMetadataTokens()
|
||||
|
||||
self.assertIn("language=jpn", metadata_tokens)
|
||||
self.assertNotIn("title=Japanisch", metadata_tokens)
|
||||
self.assertNotIn("title=Deutsch", metadata_tokens)
|
||||
|
||||
def test_target_only_tracks_still_emit_remove_tokens_for_configured_stream_keys(self):
|
||||
|
||||
@@ -18,6 +18,7 @@ from ffx.logging_utils import get_ffx_logger # noqa: E402
|
||||
from ffx.media_descriptor import MediaDescriptor # noqa: E402
|
||||
from ffx.metadata_editor import ( # noqa: E402
|
||||
apply_metadata_edits,
|
||||
build_metadata_edit_command,
|
||||
build_metadata_edit_context,
|
||||
create_temporary_output_path,
|
||||
)
|
||||
@@ -77,15 +78,45 @@ class MetadataEditorTests(unittest.TestCase):
|
||||
self.assertEqual(".mkv", Path(temporary_path).suffix)
|
||||
self.assertEqual(Path(source_path).parent, Path(temporary_path).parent)
|
||||
|
||||
def test_build_metadata_edit_command_maps_all_streams_and_uses_single_copy_codec(self):
|
||||
context = build_metadata_edit_context(make_context())
|
||||
baseline_descriptor = make_descriptor()
|
||||
draft_descriptor = baseline_descriptor.clone(context=context)
|
||||
|
||||
command = build_metadata_edit_command(
|
||||
context,
|
||||
"/tmp/example.mkv",
|
||||
"/tmp/.edit.mkv",
|
||||
baseline_descriptor,
|
||||
draft_descriptor,
|
||||
)
|
||||
|
||||
self.assertEqual(1, command.count("-map"))
|
||||
self.assertEqual(1, command.count("-c"))
|
||||
self.assertNotIn("-c:v:0", command)
|
||||
self.assertNotIn("-c:a:0", command)
|
||||
self.assertNotIn("-c:s:0", command)
|
||||
self.assertEqual(
|
||||
["-map", "0", "-c", "copy"],
|
||||
command[command.index("-map"):command.index("-c") + 2],
|
||||
)
|
||||
|
||||
def test_apply_metadata_edits_rewrites_via_temporary_file_then_replaces_source(self):
|
||||
context = make_context()
|
||||
baseline_descriptor = make_descriptor()
|
||||
draft_descriptor = baseline_descriptor.clone(context=context)
|
||||
source_path = "/tmp/example.mkv"
|
||||
expected_command = build_metadata_edit_command(
|
||||
build_metadata_edit_context(context),
|
||||
source_path,
|
||||
"/tmp/.edit.mkv",
|
||||
baseline_descriptor,
|
||||
draft_descriptor,
|
||||
)
|
||||
|
||||
with (
|
||||
patch("ffx.metadata_editor.create_temporary_output_path", return_value="/tmp/.edit.mkv"),
|
||||
patch("ffx.metadata_editor.FfxController.runJob") as mocked_run_job,
|
||||
patch("ffx.metadata_editor.executeProcess", return_value=("", "", 0)) as mocked_execute,
|
||||
patch("ffx.metadata_editor.os.replace") as mocked_replace,
|
||||
):
|
||||
result = apply_metadata_edits(
|
||||
@@ -95,32 +126,43 @@ class MetadataEditorTests(unittest.TestCase):
|
||||
draft_descriptor,
|
||||
)
|
||||
|
||||
mocked_run_job.assert_called_once_with(
|
||||
source_path,
|
||||
"/tmp/.edit.mkv",
|
||||
targetFormat="",
|
||||
chainIteration=[],
|
||||
)
|
||||
mocked_execute.assert_called_once_with(expected_command, context=build_metadata_edit_context(context))
|
||||
mocked_replace.assert_called_once_with("/tmp/.edit.mkv", source_path)
|
||||
self.assertEqual(
|
||||
{
|
||||
"applied": True,
|
||||
"dry_run": False,
|
||||
"target_path": source_path,
|
||||
"command_sequence": expected_command,
|
||||
},
|
||||
{
|
||||
"applied": result["applied"],
|
||||
"dry_run": result["dry_run"],
|
||||
"target_path": result["target_path"],
|
||||
"command_sequence": result["command_sequence"],
|
||||
},
|
||||
result,
|
||||
)
|
||||
self.assertIn("timings", result)
|
||||
self.assertIn("ffmpeg_seconds", result["timings"])
|
||||
self.assertIn("replace_seconds", result["timings"])
|
||||
self.assertIn("write_seconds", result["timings"])
|
||||
|
||||
def test_apply_metadata_edits_dry_run_skips_replace_and_cleans_temp_path(self):
|
||||
context = make_context(dry_run=True)
|
||||
baseline_descriptor = make_descriptor()
|
||||
draft_descriptor = baseline_descriptor.clone(context=context)
|
||||
notifications = []
|
||||
expected_command = build_metadata_edit_command(
|
||||
build_metadata_edit_context(context),
|
||||
"/tmp/example.mkv",
|
||||
"/tmp/.edit.mkv",
|
||||
baseline_descriptor,
|
||||
draft_descriptor,
|
||||
)
|
||||
|
||||
with (
|
||||
patch("ffx.metadata_editor.create_temporary_output_path", return_value="/tmp/.edit.mkv"),
|
||||
patch("ffx.metadata_editor.FfxController.runJob") as mocked_run_job,
|
||||
patch("ffx.metadata_editor.os.path.exists", return_value=True),
|
||||
patch("ffx.metadata_editor.os.remove") as mocked_remove,
|
||||
patch("ffx.metadata_editor.executeProcess") as mocked_execute,
|
||||
patch("ffx.metadata_editor.os.replace") as mocked_replace,
|
||||
):
|
||||
result = apply_metadata_edits(
|
||||
@@ -128,19 +170,57 @@ class MetadataEditorTests(unittest.TestCase):
|
||||
"/tmp/example.mkv",
|
||||
baseline_descriptor,
|
||||
draft_descriptor,
|
||||
notify=notifications.append,
|
||||
)
|
||||
|
||||
mocked_run_job.assert_called_once()
|
||||
mocked_execute.assert_not_called()
|
||||
mocked_replace.assert_not_called()
|
||||
mocked_remove.assert_called_once_with("/tmp/.edit.mkv")
|
||||
self.assertEqual(["ffmpeg dry-run prepared."], notifications)
|
||||
self.assertEqual(
|
||||
{
|
||||
"applied": False,
|
||||
"dry_run": True,
|
||||
"target_path": "/tmp/.edit.mkv",
|
||||
"command_sequence": expected_command,
|
||||
},
|
||||
{
|
||||
"applied": result["applied"],
|
||||
"dry_run": result["dry_run"],
|
||||
"target_path": result["target_path"],
|
||||
"command_sequence": result["command_sequence"],
|
||||
},
|
||||
result,
|
||||
)
|
||||
self.assertEqual(
|
||||
{
|
||||
"ffmpeg_seconds": 0.0,
|
||||
"replace_seconds": 0.0,
|
||||
"write_seconds": 0.0,
|
||||
},
|
||||
result["timings"],
|
||||
)
|
||||
|
||||
def test_apply_metadata_edits_notifies_with_command_when_verbose(self):
|
||||
context = make_context()
|
||||
context["verbosity"] = 1
|
||||
baseline_descriptor = make_descriptor()
|
||||
draft_descriptor = baseline_descriptor.clone(context=context)
|
||||
notifications = []
|
||||
|
||||
with (
|
||||
patch("ffx.metadata_editor.create_temporary_output_path", return_value="/tmp/.edit.mkv"),
|
||||
patch("ffx.metadata_editor.executeProcess", return_value=("", "", 0)),
|
||||
patch("ffx.metadata_editor.os.replace"),
|
||||
):
|
||||
apply_metadata_edits(
|
||||
context,
|
||||
"/tmp/example.mkv",
|
||||
baseline_descriptor,
|
||||
draft_descriptor,
|
||||
notify=notifications.append,
|
||||
)
|
||||
|
||||
self.assertEqual(1, len(notifications))
|
||||
self.assertTrue(notifications[0].startswith("ffmpeg: ffmpeg "))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -99,6 +99,7 @@ class FakeMediaDescriptor:
|
||||
class FakeValueWidget:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
self.disabled = False
|
||||
|
||||
|
||||
class FakeInputWidget:
|
||||
@@ -106,10 +107,21 @@ class FakeInputWidget:
|
||||
self.value = value
|
||||
|
||||
|
||||
class FakeStaticWidget:
|
||||
def __init__(self, value=""):
|
||||
self.value = value
|
||||
|
||||
def update(self, value):
|
||||
self.value = value
|
||||
|
||||
|
||||
class FakeSelectionListWidget:
|
||||
def __init__(self, selected):
|
||||
self.selected = selected
|
||||
|
||||
def add_option(self, _option):
|
||||
return None
|
||||
|
||||
|
||||
def make_track_descriptor(index, sub_index, track_type):
|
||||
return TrackDescriptor(
|
||||
@@ -244,6 +256,49 @@ class TagTableScreenStateTests(unittest.TestCase):
|
||||
|
||||
self.assertEqual("Preset", widgets["#title_input"].value)
|
||||
|
||||
def test_track_details_screen_metadata_only_mount_shows_normalized_title_preview(self):
|
||||
set_current_language("de")
|
||||
screen = object.__new__(TrackDetailsScreen)
|
||||
screen._TrackDetailsScreen__index = 2
|
||||
screen._TrackDetailsScreen__subIndex = 0
|
||||
screen._TrackDetailsScreen__patternLabel = "demo"
|
||||
screen._TrackDetailsScreen__trackType = TrackType.AUDIO
|
||||
screen._TrackDetailsScreen__audioLayout = AudioLayout.LAYOUT_STEREO
|
||||
screen._TrackDetailsScreen__trackDescriptor = TrackDescriptor(
|
||||
index=2,
|
||||
source_index=2,
|
||||
sub_index=0,
|
||||
track_type=TrackType.AUDIO,
|
||||
codec_name=TrackCodec.DTS,
|
||||
audio_layout=AudioLayout.LAYOUT_STEREO,
|
||||
tags={"language": "ger"},
|
||||
)
|
||||
screen._TrackDetailsScreen__metadataOnly = True
|
||||
screen._TrackDetailsScreen__titleAutoManaged = True
|
||||
screen._TrackDetailsScreen__suppressTitleChanged = False
|
||||
screen._TrackDetailsScreen__lastAutoTitle = ""
|
||||
screen._TrackDetailsScreen__removeTrackKeys = []
|
||||
screen._TrackDetailsScreen__ignoreTrackKeys = []
|
||||
screen._TrackDetailsScreen__draftTrackTags = {}
|
||||
screen._TrackDetailsScreen__tagRowData = {}
|
||||
screen.updateTags = lambda: None
|
||||
|
||||
widgets = {
|
||||
"#index_label": FakeStaticWidget(),
|
||||
"#subindex_label": FakeStaticWidget(),
|
||||
"#pattern_label": FakeStaticWidget(),
|
||||
"#type_select": FakeValueWidget(None),
|
||||
"#audio_layout_select": FakeValueWidget(None),
|
||||
"#dispositions_selection_list": FakeSelectionListWidget(set()),
|
||||
"#language_select": FakeValueWidget(None),
|
||||
"#title_input": FakeInputWidget(""),
|
||||
}
|
||||
screen.query_one = lambda selector, _widget_type=None: widgets[selector]
|
||||
|
||||
screen.on_mount()
|
||||
|
||||
self.assertEqual("Deutsch", widgets["#title_input"].value)
|
||||
|
||||
def test_track_details_screen_language_options_are_sorted_by_localized_label(self):
|
||||
set_current_language("de")
|
||||
|
||||
@@ -326,12 +381,84 @@ class TagTableScreenStateTests(unittest.TestCase):
|
||||
screen.tracksTable = FakeTagTable()
|
||||
screen._sourceMediaDescriptor = FakeMediaDescriptor([first_track])
|
||||
screen._trackRowData = {}
|
||||
screen._applyNormalization = False
|
||||
|
||||
screen.updateTracks()
|
||||
|
||||
self.assertEqual(9, len(screen.tracksTable.columns))
|
||||
self.assertIn("A much longer updated title", screen.tracksTable.rows["row-0"])
|
||||
|
||||
def test_media_edit_screen_shows_normalized_audio_title_preview(self):
|
||||
set_current_language("de")
|
||||
audio_track = TrackDescriptor(
|
||||
index=1,
|
||||
source_index=1,
|
||||
sub_index=0,
|
||||
track_type=TrackType.AUDIO,
|
||||
codec_name=TrackCodec.DTS,
|
||||
audio_layout=AudioLayout.LAYOUT_STEREO,
|
||||
tags={"language": "ger"},
|
||||
)
|
||||
|
||||
screen = object.__new__(MediaEditScreen)
|
||||
screen.tracksTable = FakeTagTable()
|
||||
screen._sourceMediaDescriptor = FakeMediaDescriptor([audio_track])
|
||||
screen._trackRowData = {}
|
||||
screen._applyNormalization = True
|
||||
|
||||
screen.updateTracks()
|
||||
|
||||
self.assertIn("Deutsch", screen.tracksTable.rows["row-0"])
|
||||
|
||||
def test_media_edit_screen_shows_normalized_video_title_preview(self):
|
||||
set_current_language("de")
|
||||
video_track = TrackDescriptor(
|
||||
index=0,
|
||||
source_index=0,
|
||||
sub_index=0,
|
||||
track_type=TrackType.VIDEO,
|
||||
codec_name=TrackCodec.H264,
|
||||
tags={"language": "ger"},
|
||||
)
|
||||
|
||||
screen = object.__new__(MediaEditScreen)
|
||||
screen.tracksTable = FakeTagTable()
|
||||
screen._sourceMediaDescriptor = FakeMediaDescriptor([video_track])
|
||||
screen._trackRowData = {}
|
||||
screen._applyNormalization = True
|
||||
|
||||
screen.updateTracks()
|
||||
|
||||
self.assertIn("Deutsch", screen.tracksTable.rows["row-0"])
|
||||
|
||||
def test_media_edit_screen_toggle_normalization_refreshes_tracks(self):
|
||||
screen = object.__new__(MediaEditScreen)
|
||||
screen._applyNormalization = False
|
||||
|
||||
calls = []
|
||||
|
||||
screen.setApplyNormalization = lambda enabled: (
|
||||
setattr(screen, "_applyNormalization", bool(enabled)),
|
||||
calls.append("setApplyNormalization"),
|
||||
)
|
||||
screen.updateToggleButtons = lambda: calls.append("updateToggleButtons")
|
||||
screen.updateTracks = lambda: calls.append("updateTracks")
|
||||
screen.updateDifferences = lambda: calls.append("updateDifferences")
|
||||
screen.setMessage = lambda _message: calls.append("setMessage")
|
||||
|
||||
screen.action_toggle_normalization()
|
||||
|
||||
self.assertEqual(
|
||||
[
|
||||
"setApplyNormalization",
|
||||
"updateToggleButtons",
|
||||
"updateTracks",
|
||||
"updateDifferences",
|
||||
"setMessage",
|
||||
],
|
||||
calls,
|
||||
)
|
||||
|
||||
def test_pattern_details_screen_reads_selected_shifted_season_from_row_mapping(self):
|
||||
screen = object.__new__(PatternDetailsScreen)
|
||||
screen.shiftedSeasonsTable = FakeTagTable()
|
||||
|
||||
Reference in New Issue
Block a user