Add DVD encode profiles and subtitle hardsub burn-in

Introduce three project-level DVD encode profiles (Compatibility 4500k /
High quality VBR / Custom) via new config keys, and burn a chosen
embedded subtitle track into the picture:

- file_movie carries the hardsub selection (index/kind), persists it, and
  builds a subtitle-picker combobox in its properties page; set_final_rates
  maps the profile to the final video/audio bitrate.
- ffmpeg burns text subs with the subtitles= filter after scaling and image
  subs with an overlay filtergraph before scaling; NTSC DVD audio is now AC3
  for all profiles. A start_offset (-ss) is threaded through for segmenting.
- avconv gets the text-sub path and start_offset for parity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Luna 2026-06-02 12:45:36 -07:00
parent a158119d41
commit 8b041f0725
4 changed files with 310 additions and 13 deletions

View file

@ -112,8 +112,9 @@ class avconv(devedeng.avbase.avbase):
self.config = devedeng.configuration_data.configuration.get_config()
self.check_version(["avconv", "-version"])
def convert_file(self, file_project, output_file, video_length, pass2=False):
def convert_file(self, file_project, output_file, video_length, pass2=False, start_offset=0):
self.start_offset = start_offset
if file_project.two_pass_encoding:
if pass2:
self.text = _("Converting %(X)s (pass 2)") % {
@ -123,7 +124,8 @@ class avconv(devedeng.avbase.avbase):
"X": file_project.title_name}
# Prepare the converting process for the second pass
tmp = devedeng.avconv.avconv()
tmp.convert_file(file_project, output_file, video_length, True)
tmp.convert_file(file_project, output_file, video_length, True,
start_offset)
# it deppends of this process
tmp.add_dependency(self)
# add it as a child process of this one
@ -147,6 +149,9 @@ class avconv(devedeng.avbase.avbase):
self.final_length = video_length
self.command_var = []
self.command_var.append("avconv")
if start_offset and start_offset > 0:
self.command_var.append("-ss")
self.command_var.append(str(start_offset))
self.command_var.append("-i")
self.command_var.append(file_project.file_name)
@ -159,6 +164,9 @@ class avconv(devedeng.avbase.avbase):
self.command_var.append("-itsoffset")
self.command_var.append(str(file_project.audio_delay))
if start_offset and start_offset > 0:
self.command_var.append("-ss")
self.command_var.append(str(start_offset))
self.command_var.append("-i")
self.command_var.append(file_project.file_name)
self.command_var.append("-map")
@ -243,6 +251,16 @@ class avconv(devedeng.avbase.avbase):
str(file_project.width_final) + ":h=" + \
str(file_project.height_final)
# text subtitle burn-in (hardsub) as the last filter, after scaling.
# Image-based subs need an overlay filtergraph and are only handled
# by the ffmpeg backend; avconv users get text subs burned in.
if second_pass:
text_filter = self._avconv_text_subtitle_filter(file_project)
if text_filter is not None:
if (cmd_line != ""):
cmd_line += ",fifo,"
cmd_line += text_filter
if cmd_line != "":
self.command_var.append("-vf")
self.command_var.append(cmd_line)
@ -519,6 +537,27 @@ class avconv(devedeng.avbase.avbase):
return a
return 384
def _avconv_text_subtitle_filter(self, file_project):
""" Build a `subtitles=` filter to burn a text-based embedded subtitle
track into the picture, or None. Image-based subs are not handled by
the avconv backend (use the ffmpeg backend for those). """
if getattr(file_project, "hardsub_kind", "text") != "text":
return None
index = getattr(file_project, "hardsub_index", -1)
if index is None or index < 0:
return None
subs = getattr(file_project, "embedded_subtitles", []) or []
rel = None
for i, track in enumerate(subs):
if track["index"] == index:
rel = i
break
if rel is None:
return None
path = file_project.file_name.replace("\\", "\\\\").replace(":", "\\:")
path = path.replace("'", "\\'")
return "subtitles='%s':si=%d" % (path, rel)
def create_menu_mpeg(self, n_page, background_music, sound_length, pal, video_rate, audio_rate, output_path, use_mp2, widescreen):
self.n_page = n_page