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:
parent
a158119d41
commit
8b041f0725
4 changed files with 310 additions and 13 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -118,6 +118,11 @@ class configuration(GObject.GObject):
|
|||
self.subt_fill_color = (1, 1, 1, 1)
|
||||
self.subt_outline_color = (0, 0, 0, 1)
|
||||
self.subt_outline_thickness = 0.0
|
||||
# DVD encode profile: "compatibility" (CBR 4500k, old-player safe),
|
||||
# "high" (2-pass VBR filling the disc), or "custom" (user bitrates).
|
||||
self.encode_profile = "compatibility"
|
||||
self.custom_video_kbps = 4500
|
||||
self.custom_audio_kbps = 192
|
||||
|
||||
config_folder = self._get_config_folder()
|
||||
config_path = os.path.join(config_folder, "devedeng.cfg")
|
||||
|
|
@ -185,6 +190,23 @@ class configuration(GObject.GObject):
|
|||
float(c[0]), float(c[1]), float(c[2]), 1.0)
|
||||
if linea[:27] == "subtitle_outilne_thickness:":
|
||||
self.subt_outline_thickness = float(linea[27:].strip())
|
||||
if linea[:15] == "encode_profile:":
|
||||
profile = linea[15:].strip()
|
||||
if profile in ("compatibility", "high", "custom"):
|
||||
self.encode_profile = profile
|
||||
continue
|
||||
if linea[:18] == "custom_video_kbps:":
|
||||
try:
|
||||
self.custom_video_kbps = int(linea[18:].strip())
|
||||
except ValueError:
|
||||
pass
|
||||
continue
|
||||
if linea[:18] == "custom_audio_kbps:":
|
||||
try:
|
||||
self.custom_audio_kbps = int(linea[18:].strip())
|
||||
except ValueError:
|
||||
pass
|
||||
continue
|
||||
config_data.close()
|
||||
except:
|
||||
pass
|
||||
|
|
@ -249,7 +271,12 @@ class configuration(GObject.GObject):
|
|||
config_data.write("subtitle_outline_color:" + str(self.subt_outline_color[0]) + "," + str(
|
||||
self.subt_outline_color[1]) + "," + str(self.subt_outline_color[2]) + "\n")
|
||||
config_data.write("subtitle_outilne_thickness:" +
|
||||
str(self.subt_outline_thickness))
|
||||
str(self.subt_outline_thickness) + "\n")
|
||||
config_data.write("encode_profile:" + str(self.encode_profile) + "\n")
|
||||
config_data.write("custom_video_kbps:" +
|
||||
str(self.custom_video_kbps) + "\n")
|
||||
config_data.write("custom_audio_kbps:" +
|
||||
str(self.custom_audio_kbps))
|
||||
|
||||
config_data.close()
|
||||
# remove old configuration files
|
||||
|
|
|
|||
|
|
@ -133,8 +133,9 @@ class ffmpeg(devedeng.executor.executor):
|
|||
self._filter_sep = ","
|
||||
|
||||
|
||||
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)") % {
|
||||
|
|
@ -144,7 +145,8 @@ class ffmpeg(devedeng.executor.executor):
|
|||
"X": file_project.title_name}
|
||||
# Prepare the converting process for the second pass
|
||||
tmp = devedeng.ffmpeg.ffmpeg()
|
||||
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
|
||||
|
|
@ -162,12 +164,24 @@ class ffmpeg(devedeng.executor.executor):
|
|||
# codification
|
||||
second_pass = True
|
||||
|
||||
# whether this encode burns in an image-based (PGS/VOBSUB) subtitle via
|
||||
# an overlay filtergraph; only applies on the (final) encoding pass and
|
||||
# when we are actually re-encoding the video
|
||||
overlay_subs = (second_pass
|
||||
and (not file_project.no_reencode_audio_video)
|
||||
and self._image_overlay_active(file_project))
|
||||
|
||||
if (video_length == 0):
|
||||
self.final_length = file_project.original_length
|
||||
else:
|
||||
self.final_length = video_length
|
||||
self.command_var = []
|
||||
self.command_var.append("ffmpeg")
|
||||
# input seek (for splitting a long movie across discs); placed before
|
||||
# -i so ffmpeg seeks quickly and the output starts at offset 0
|
||||
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)
|
||||
|
||||
|
|
@ -180,10 +194,18 @@ class ffmpeg(devedeng.executor.executor):
|
|||
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")
|
||||
self.command_var.append("1:" + str(file_project.video_list[0]))
|
||||
if overlay_subs:
|
||||
# video comes from the overlay filtergraph output, mapped later
|
||||
self.command_var.append("-map")
|
||||
self.command_var.append("[vsub]")
|
||||
else:
|
||||
self.command_var.append("-map")
|
||||
self.command_var.append("1:" + str(file_project.video_list[0]))
|
||||
if (not file_project.copy_sound) and (not file_project.no_reencode_audio_video):
|
||||
for l in file_project.audio_list:
|
||||
self.command_var.append("-map")
|
||||
|
|
@ -255,9 +277,23 @@ class ffmpeg(devedeng.executor.executor):
|
|||
str(file_project.width_final) + ":" + \
|
||||
str(file_project.height_final)
|
||||
|
||||
if cmd_line != "":
|
||||
self.command_var.append("-vf")
|
||||
self.command_var.append(cmd_line)
|
||||
# --- subtitle burn-in (hardsub) ---------------------------------
|
||||
# Image-based subs (PGS/VOBSUB) must be overlaid via filter_complex
|
||||
# BEFORE scaling, so they stay aligned with the source frame; text
|
||||
# subs are rendered by libass AFTER scaling, as the last -vf entry.
|
||||
if overlay_subs:
|
||||
self._apply_image_overlay(file_project, cmd_line)
|
||||
cmd_line = "" # consumed by filter_complex above
|
||||
else:
|
||||
if second_pass:
|
||||
text_filter = self.get_hardsub_text_filter(file_project)
|
||||
if text_filter is not None:
|
||||
if (cmd_line != ""):
|
||||
cmd_line += self._filter_sep
|
||||
cmd_line += text_filter
|
||||
if cmd_line != "":
|
||||
self.command_var.append("-vf")
|
||||
self.command_var.append(cmd_line)
|
||||
|
||||
self.command_var.append("-y")
|
||||
|
||||
|
|
@ -304,7 +340,13 @@ class ffmpeg(devedeng.executor.executor):
|
|||
self.command_var.append("ac3")
|
||||
else:
|
||||
self.command_var.append("-c:a")
|
||||
if file_project.format_pal:
|
||||
# PAL always uses AC3; for NTSC the original used MP2,
|
||||
# but the DVD-safe profiles use AC3 (matches the known-
|
||||
# good baseline and is more broadly player-compatible)
|
||||
profile = getattr(self.config, "encode_profile",
|
||||
"compatibility")
|
||||
if file_project.format_pal or profile in (
|
||||
"compatibility", "high", "custom"):
|
||||
self.command_var.append("ac3")
|
||||
else:
|
||||
self.command_var.append("mp2")
|
||||
|
|
@ -532,6 +574,73 @@ class ffmpeg(devedeng.executor.executor):
|
|||
return a
|
||||
return 384
|
||||
|
||||
@staticmethod
|
||||
def _escape_subtitle_path(path):
|
||||
""" Escape a file path for use inside an ffmpeg filtergraph argument.
|
||||
The `subtitles`/`ass` filters parse ':' and '\\' specially, and the
|
||||
whole option may be wrapped in single quotes by the caller. """
|
||||
# backslash first, then the filtergraph-special characters
|
||||
path = path.replace("\\", "\\\\")
|
||||
path = path.replace(":", "\\:")
|
||||
path = path.replace("'", "\\'")
|
||||
path = path.replace("[", "\\[").replace("]", "\\]")
|
||||
path = path.replace(",", "\\,")
|
||||
return path
|
||||
|
||||
def _hardsub_relative_index(self, file_project):
|
||||
""" Returns the 0-based position of the chosen hardsub track among the
|
||||
file's subtitle streams (what ffmpeg's `subtitles` filter `si=`
|
||||
expects), or None if no track is selected / found. """
|
||||
index = getattr(file_project, "hardsub_index", -1)
|
||||
if index is None or index < 0:
|
||||
return None
|
||||
subs = getattr(file_project, "embedded_subtitles", []) or []
|
||||
for rel, track in enumerate(subs):
|
||||
if track["index"] == index:
|
||||
return rel
|
||||
return None
|
||||
|
||||
def _image_overlay_active(self, file_project):
|
||||
""" True when an image-based (PGS/VOBSUB) subtitle track is selected for
|
||||
burn-in, which requires an overlay filtergraph rather than -vf. """
|
||||
if getattr(file_project, "hardsub_kind", "text") != "image":
|
||||
return False
|
||||
return self._hardsub_relative_index(file_project) is not None
|
||||
|
||||
def _apply_image_overlay(self, file_project, pre_chain):
|
||||
""" Replaces the plain -vf path with a -filter_complex that overlays the
|
||||
bitmap subtitle stream onto the video and then applies the same
|
||||
scaling/padding chain. The chosen subtitle stream is taken from the
|
||||
primary input (index 1, the one used for -map 1:video).
|
||||
|
||||
Overlay happens at source resolution; `pre_chain` (crop/pad/scale,
|
||||
possibly empty) is applied to the overlaid result so the final size
|
||||
and aspect are identical to the no-subtitle path. """
|
||||
rel = self._hardsub_relative_index(file_project)
|
||||
video_idx = file_project.video_list[0]
|
||||
# input 1 is the one mapped for video (see -map 1:video above)
|
||||
graph = "[1:%d][1:s:%d]overlay" % (video_idx, rel)
|
||||
if pre_chain:
|
||||
graph += self._filter_sep + pre_chain
|
||||
graph += "[vsub]"
|
||||
self.command_var.append("-filter_complex")
|
||||
self.command_var.append(graph)
|
||||
self._overlay_video_label = "[vsub]"
|
||||
|
||||
def get_hardsub_text_filter(self, file_project):
|
||||
""" Builds the `subtitles=` filtergraph entry to burn a text-based
|
||||
embedded subtitle track into the picture, or None if not applicable.
|
||||
Must run AFTER scaling so the rendered text matches the DVD size. """
|
||||
if getattr(file_project, "hardsub_kind", "text") != "text":
|
||||
return None
|
||||
rel = self._hardsub_relative_index(file_project)
|
||||
if rel is None:
|
||||
return None
|
||||
path = self._escape_subtitle_path(file_project.file_name)
|
||||
# original_size used so libass renders at source res; ffmpeg then maps
|
||||
# onto the already-scaled frame. The filter is placed last in the chain.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -66,6 +66,11 @@ class file_movie(devedeng.interface_manager.interface_manager):
|
|||
self.add_toggle("audio_rate_automatic", True)
|
||||
self.add_toggle("divide_in_chapters", True)
|
||||
self.add_toggle("force_subtitles", False)
|
||||
# Hardsub (burn-in) of an embedded subtitle track. -1 means "none".
|
||||
# hardsub_index is the ffmpeg stream index of the chosen track, and
|
||||
# hardsub_kind is "text" or "image" (decides the burn-in filter).
|
||||
self.hardsub_index = -1
|
||||
self.hardsub_kind = "text"
|
||||
self.add_toggle("mirror_horizontal", False)
|
||||
self.add_toggle("mirror_vertical", False)
|
||||
self.add_toggle("two_pass_encoding", False)
|
||||
|
|
@ -181,6 +186,11 @@ class file_movie(devedeng.interface_manager.interface_manager):
|
|||
self.original_fps = film_analizer.original_fps
|
||||
self.original_file_size = film_analizer.original_file_size
|
||||
|
||||
# embedded subtitle tracks detected in the source file; the
|
||||
# user can pick one of these to burn into the picture (hardsub)
|
||||
self.embedded_subtitles = getattr(
|
||||
film_analizer, "subtitle_tracks", [])
|
||||
|
||||
if self.original_audiorate <= 0:
|
||||
# just a guess, but usually correct
|
||||
self.original_audiorate = 224
|
||||
|
|
@ -230,6 +240,16 @@ class file_movie(devedeng.interface_manager.interface_manager):
|
|||
|
||||
return estimated_size
|
||||
|
||||
def get_kbytes_per_second(self):
|
||||
""" Estimated output size rate in kBytes per second of video, used to
|
||||
plan how much of a movie fits on one disc when splitting. """
|
||||
self.set_final_rates()
|
||||
sub_rate = 8 * len(self.subtitles_list)
|
||||
total_kbps = (self.video_rate_final
|
||||
+ (self.audio_rate_final * self.audio_streams)
|
||||
+ sub_rate)
|
||||
return total_kbps / 8.0
|
||||
|
||||
def get_size_data(self):
|
||||
|
||||
estimated_size = self.get_estimated_size()
|
||||
|
|
@ -271,6 +291,18 @@ class file_movie(devedeng.interface_manager.interface_manager):
|
|||
else:
|
||||
self.audio_rate_auto = 224
|
||||
|
||||
# DVD encode profiles override the auto audio rate (and, for the
|
||||
# fixed-bitrate profiles, the video rate too). "high" leaves the video
|
||||
# rate to the disc-fitting logic so it fills the disc.
|
||||
profile = getattr(self.config, "encode_profile", "compatibility")
|
||||
if (self.disc_type == "dvd") and (not self.sound5_1):
|
||||
if profile == "compatibility":
|
||||
self.audio_rate_auto = 192
|
||||
elif profile == "high":
|
||||
self.audio_rate_auto = 448
|
||||
elif profile == "custom":
|
||||
self.audio_rate_auto = self.config.custom_audio_kbps
|
||||
|
||||
if self.is_mpeg_ps or self.no_reencode_audio_video:
|
||||
self.video_rate_final = self.original_videorate
|
||||
self.audio_rate_final = self.original_audiorate
|
||||
|
|
@ -280,6 +312,13 @@ class file_movie(devedeng.interface_manager.interface_manager):
|
|||
else:
|
||||
self.video_rate_final = self.video_rate
|
||||
|
||||
# fixed-bitrate DVD profiles pin the final video rate directly
|
||||
if (self.disc_type == "dvd"):
|
||||
if profile == "compatibility":
|
||||
self.video_rate_final = 4500
|
||||
elif profile == "custom":
|
||||
self.video_rate_final = self.config.custom_video_kbps
|
||||
|
||||
if self.copy_sound:
|
||||
self.audio_rate_final = self.original_audiorate
|
||||
else:
|
||||
|
|
@ -491,6 +530,11 @@ class file_movie(devedeng.interface_manager.interface_manager):
|
|||
selection = self.wtreview_subtitles.get_selection()
|
||||
selection.set_mode(Gtk.SelectionMode.BROWSE)
|
||||
|
||||
# embedded-subtitle (hardsub) picker, built in code and packed at the
|
||||
# top of the subtitles page (only meaningful for single-file editing)
|
||||
if self.list_files is None:
|
||||
self._build_hardsub_picker()
|
||||
|
||||
# elements in page VIDEO OPTIONS
|
||||
self.wsize_1920x1080 = self.builder.get_object("size_1920x1080")
|
||||
self.wsize_1280x720 = self.builder.get_object("size_1280x720")
|
||||
|
|
@ -677,7 +721,73 @@ class file_movie(devedeng.interface_manager.interface_manager):
|
|||
else:
|
||||
self.wdel_subtitles.set_sensitive(True)
|
||||
|
||||
def do_conversion(self, output_path, duration=0):
|
||||
def _build_hardsub_picker(self):
|
||||
""" Build and pack a combobox that lets the user pick one embedded
|
||||
subtitle track to burn into the picture (hardsub). The chosen track
|
||||
is stored on self.hardsub_index / self.hardsub_kind. """
|
||||
tracks = getattr(self, "embedded_subtitles", []) or []
|
||||
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
|
||||
box.set_margin_top(6)
|
||||
box.set_margin_bottom(6)
|
||||
box.set_margin_start(6)
|
||||
box.set_margin_end(6)
|
||||
|
||||
label = Gtk.Label(label=_("Burn in embedded subtitle:"))
|
||||
box.pack_start(label, False, True, 0)
|
||||
|
||||
self.whardsub_combo = Gtk.ComboBoxText()
|
||||
# entry 0 is always "None"; remaining entries map to tracks in order
|
||||
self.whardsub_combo.append_text(_("None (no hardsub)"))
|
||||
for track in tracks:
|
||||
lang = track.get("language") or "und"
|
||||
kind = _("image") if track["kind"] == "image" else _("text")
|
||||
extra = []
|
||||
if track.get("default"):
|
||||
extra.append(_("default"))
|
||||
if track.get("forced"):
|
||||
extra.append(_("forced"))
|
||||
suffix = (" [" + ", ".join(extra) + "]") if extra else ""
|
||||
title = track.get("title")
|
||||
title_part = (" - " + title) if title else ""
|
||||
self.whardsub_combo.append_text(
|
||||
"[%s] %s (%s)%s%s" % (lang, track.get("codec", ""), kind,
|
||||
title_part, suffix))
|
||||
|
||||
# restore current selection
|
||||
active = 0
|
||||
for i, track in enumerate(tracks):
|
||||
if track["index"] == self.hardsub_index:
|
||||
active = i + 1
|
||||
break
|
||||
self.whardsub_combo.set_active(active)
|
||||
self.whardsub_combo.connect("changed", self.on_hardsub_combo_changed)
|
||||
box.pack_start(self.whardsub_combo, True, True, 0)
|
||||
|
||||
box.show_all()
|
||||
if len(tracks) == 0:
|
||||
# nothing to burn in; show a disabled hint instead of an empty combo
|
||||
self.whardsub_combo.set_sensitive(False)
|
||||
|
||||
# pack at the top of the subtitles page (parent of the external-subs
|
||||
# scrolled window)
|
||||
page = self.wscrolledwindow_subtitles.get_parent()
|
||||
if page is not None:
|
||||
page.pack_start(box, False, True, 0)
|
||||
page.reorder_child(box, 0)
|
||||
|
||||
def on_hardsub_combo_changed(self, combo):
|
||||
idx = combo.get_active()
|
||||
tracks = getattr(self, "embedded_subtitles", []) or []
|
||||
if idx <= 0 or (idx - 1) >= len(tracks):
|
||||
self.hardsub_index = -1
|
||||
self.hardsub_kind = "text"
|
||||
else:
|
||||
track = tracks[idx - 1]
|
||||
self.hardsub_index = track["index"]
|
||||
self.hardsub_kind = track["kind"]
|
||||
|
||||
def do_conversion(self, output_path, duration=0, start_offset=0):
|
||||
|
||||
self.converted_filename = output_path
|
||||
if self.is_mpeg_ps:
|
||||
|
|
@ -690,7 +800,8 @@ class file_movie(devedeng.interface_manager.interface_manager):
|
|||
cv = devedeng.converter.converter.get_converter()
|
||||
disc_converter = cv.get_disc_converter()
|
||||
converter = disc_converter()
|
||||
converter.convert_file(self, output_path, duration)
|
||||
converter.convert_file(self, output_path, duration,
|
||||
start_offset=start_offset)
|
||||
|
||||
if len(self.subtitles_list) != 0:
|
||||
last_process = converter
|
||||
|
|
@ -751,10 +862,21 @@ class file_movie(devedeng.interface_manager.interface_manager):
|
|||
if "files_to_set" in data:
|
||||
del data["files_to_set"]
|
||||
data["element_type"] = "file_movie"
|
||||
# the hardsub selection isn't a registered interface variable, so
|
||||
# serialize() doesn't capture it; store it explicitly. Skip it in
|
||||
# group ("multiproperties") mode: the chosen stream index is per-file
|
||||
# and must not be copied across a selection of different movies.
|
||||
if self.list_files is None:
|
||||
data["hardsub_index"] = self.hardsub_index
|
||||
data["hardsub_kind"] = self.hardsub_kind
|
||||
return data
|
||||
|
||||
def restore_element(self, data):
|
||||
self.unserialize(data)
|
||||
if "hardsub_index" in data:
|
||||
self.hardsub_index = data["hardsub_index"]
|
||||
if "hardsub_kind" in data:
|
||||
self.hardsub_kind = data["hardsub_kind"]
|
||||
|
||||
def on_select_all_clicked(self, b):
|
||||
sel = self.wtreeview_multiproperties.get_selection()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue