TF Fix styled ASS font tracks
This commit is contained in:
@@ -69,3 +69,7 @@
|
|||||||
## Delete When
|
## Delete When
|
||||||
|
|
||||||
- Delete this scratchpad once the optimization backlog is either converted into issues/work items or distilled into durable project guidance.
|
- Delete this scratchpad once the optimization backlog is either converted into issues/work items or distilled into durable project guidance.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## TODO: Review styled ASS separate handling
|
||||||
@@ -1393,13 +1393,22 @@ def convert(ctx,
|
|||||||
|
|
||||||
from ffx.attachment_format import AttachmentFormat
|
from ffx.attachment_format import AttachmentFormat
|
||||||
|
|
||||||
if ([smd for smd in sourceMediaDescriptor.getSubtitleTracks()
|
styledAssSourceDetected = (
|
||||||
if smd.getCodec() == TrackCodec.ASS]
|
sourceMediaDescriptor.hasStyledAssSubtitlesWithFontAttachments()
|
||||||
and [amd for amd in sourceMediaDescriptor.getAttachmentTracks()
|
)
|
||||||
if amd.getAttachmentFormat() == AttachmentFormat.TTF]):
|
if styledAssSourceDetected:
|
||||||
|
styledAssMessage = (
|
||||||
|
"Styled ASS subtitles with embedded font attachments detected; "
|
||||||
|
+ "preserving source font attachments."
|
||||||
|
)
|
||||||
|
click.echo(styledAssMessage)
|
||||||
targetFormat = ''
|
targetFormat = ''
|
||||||
targetExtension = 'mkv'
|
targetExtension = 'mkv'
|
||||||
|
if context['import_subtitles']:
|
||||||
|
raise click.ClickException(
|
||||||
|
"External subtitle import is incompatible with styled ASS "
|
||||||
|
+ "sources that carry embedded font attachments."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#HINT: This is None if the filename did not match anything in database
|
#HINT: This is None if the filename did not match anything in database
|
||||||
@@ -1426,6 +1435,11 @@ def convert(ctx,
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
targetMediaDescriptor = currentPattern.getMediaDescriptor(ctx.obj)
|
targetMediaDescriptor = currentPattern.getMediaDescriptor(ctx.obj)
|
||||||
|
if styledAssSourceDetected:
|
||||||
|
targetMediaDescriptor = targetMediaDescriptor.withoutAttachmentTracks(
|
||||||
|
AttachmentFormat.TTF,
|
||||||
|
context=ctx.obj,
|
||||||
|
)
|
||||||
checkUniqueDispositions(context, targetMediaDescriptor)
|
checkUniqueDispositions(context, targetMediaDescriptor)
|
||||||
currentShowDescriptor = currentPattern.getShowDescriptor(ctx.obj)
|
currentShowDescriptor = currentPattern.getShowDescriptor(ctx.obj)
|
||||||
|
|
||||||
|
|||||||
@@ -329,6 +329,49 @@ class MediaDescriptor:
|
|||||||
if s.getType() == TrackType.ATTACHMENT
|
if s.getType() == TrackType.ATTACHMENT
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def hasStyledAssSubtitlesWithFontAttachments(self) -> bool:
|
||||||
|
return (
|
||||||
|
any(
|
||||||
|
trackDescriptor.getCodec() == TrackCodec.ASS
|
||||||
|
for trackDescriptor in self.getSubtitleTracks()
|
||||||
|
)
|
||||||
|
and any(
|
||||||
|
trackDescriptor.getAttachmentFormat() == AttachmentFormat.TTF
|
||||||
|
for trackDescriptor in self.getAttachmentTracks()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def withoutAttachmentTracks(
|
||||||
|
self,
|
||||||
|
attachmentFormat: AttachmentFormat | None = None,
|
||||||
|
context: dict | None = None,
|
||||||
|
):
|
||||||
|
filteredTrackDescriptors = []
|
||||||
|
for trackDescriptor in self.__trackDescriptors:
|
||||||
|
if trackDescriptor.getType() == TrackType.ATTACHMENT and (
|
||||||
|
attachmentFormat is None
|
||||||
|
or trackDescriptor.getAttachmentFormat() == attachmentFormat
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
filteredTrackDescriptors.append(
|
||||||
|
trackDescriptor.clone(
|
||||||
|
context=context if context is not None else self.__context
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
kwargs = {
|
||||||
|
MediaDescriptor.TAGS_KEY: dict(self.__mediaTags),
|
||||||
|
MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY: filteredTrackDescriptors,
|
||||||
|
}
|
||||||
|
if context is not None:
|
||||||
|
kwargs[MediaDescriptor.CONTEXT_KEY] = context
|
||||||
|
elif self.__context:
|
||||||
|
kwargs[MediaDescriptor.CONTEXT_KEY] = self.__context
|
||||||
|
|
||||||
|
filteredMediaDescriptor = MediaDescriptor(**kwargs)
|
||||||
|
filteredMediaDescriptor.reindexSubIndices()
|
||||||
|
return filteredMediaDescriptor
|
||||||
|
|
||||||
|
|
||||||
def getImportFileTokens(self, use_sub_index: bool = True):
|
def getImportFileTokens(self, use_sub_index: bool = True):
|
||||||
"""Generate ffmpeg import options for external stream files"""
|
"""Generate ffmpeg import options for external stream files"""
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from tests.support.ffx_bundle import (
|
|||||||
write_vtt,
|
write_vtt,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ffx.attachment_format import AttachmentFormat
|
||||||
from ffx.track_type import TrackType
|
from ffx.track_type import TrackType
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -280,6 +281,72 @@ class SubtrackMappingBundleTests(unittest.TestCase):
|
|||||||
self.assertIn("non-existent source track #99", error_output)
|
self.assertIn("non-existent source track #99", error_output)
|
||||||
self.assertFalse(expected_output_path(self.workdir, source_filename).exists())
|
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):
|
def test_external_subtitle_file_replaces_payload_and_overrides_metadata(self):
|
||||||
source_filename = "substitute_s01e01.mkv"
|
source_filename = "substitute_s01e01.mkv"
|
||||||
self.write_config(
|
self.write_config(
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ if str(SRC_ROOT) not in sys.path:
|
|||||||
sys.path.insert(0, str(SRC_ROOT))
|
sys.path.insert(0, str(SRC_ROOT))
|
||||||
|
|
||||||
|
|
||||||
|
from ffx.attachment_format import AttachmentFormat
|
||||||
from ffx.audio_layout import AudioLayout
|
from ffx.audio_layout import AudioLayout
|
||||||
from ffx.database import databaseContext
|
from ffx.database import databaseContext
|
||||||
from ffx.pattern_controller import PatternController
|
from ffx.pattern_controller import PatternController
|
||||||
@@ -56,6 +57,7 @@ class PatternTrackSpec:
|
|||||||
tags: Mapping[str, str] = field(default_factory=dict)
|
tags: Mapping[str, str] = field(default_factory=dict)
|
||||||
dispositions: tuple[TrackDisposition, ...] = ()
|
dispositions: tuple[TrackDisposition, ...] = ()
|
||||||
audio_layout: AudioLayout = AudioLayout.LAYOUT_STEREO
|
audio_layout: AudioLayout = AudioLayout.LAYOUT_STEREO
|
||||||
|
attachment_format: AttachmentFormat = AttachmentFormat.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
def make_logger(name: str) -> logging.Logger:
|
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:
|
if track.track_type == TrackType.AUDIO:
|
||||||
kwargs[TrackDescriptor.AUDIO_LAYOUT_KEY] = track.audio_layout
|
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))
|
track_descriptors.append(TrackDescriptor(**kwargs))
|
||||||
|
|
||||||
pattern_id = PatternController(context).savePatternSchema(
|
pattern_id = PatternController(context).savePatternSchema(
|
||||||
|
|||||||
Reference in New Issue
Block a user