feat(ffmpeg_handling): remove subs with remove_russian

add remove stream function
change remove audio and remove subs to use this function
fix: show_metadata returns default values and return_tags is given as
parameter
This commit is contained in:
Lucas Vivo committed 2026-10-27 09:48:06 +01:00
1 parent 7f2048cda6
commit c4a717a8a8
1 file changed
+120 -118
+120 -118
View File
@@ -520,91 +520,31 @@ def cmd_convert_video(args):
) )
return True, out return True, out
from collections.abc import Callable
def cmd_remove_subs(args): def remove_streams(
""" args,
Strips all subtitle data. remove_indices: set[int] | list[int],
*,
FFmpeg Equivalent: output_subtitle_codec: bool = False,
ffmpeg -i [input] -sn -c copy [output] ) -> tuple[bool, str]:
Parameters:
- sn: Disables subtitle recording.
"""
probe = ffmpeg.probe(str(args.input)) probe = ffmpeg.probe(str(args.input))
remove_indices = set(remove_indices)
cmd = [
"ffmpeg",
"-y",
"-i",
str(args.input),
]
if args.num is None:
# remove all subtitle streams
for stream in probe["streams"]:
if stream["codec_type"] != "subtitle":
cmd.extend(["-map", f"0:{stream['index']}"])
else:
# remove only specified subtitle streams
for stream in probe["streams"]:
if stream["codec_type"] == "subtitle" and stream["index"] in args.num:
continue
cmd.extend(["-map", f"0:{stream['index']}"])
cmd.extend(
[
"-c",
"copy",
"-map_metadata",
"0",
str(args.output),
]
)
logger.debug("%s", " ".join(cmd))
if args.dry_run:
logger.debug(f"Dry run: {' '.join(cmd)}")
return True, "nooutputneeded"
try:
subprocess.run(
cmd,
capture_output=True,
text=True,
check=True,
)
except subprocess.CalledProcessError as e:
logger.error("Command: %s", " ".join(e.cmd))
logger.error("Return code: %s", e.returncode)
logger.error("STDOUT:\n%s", e.stdout)
logger.error("STDERR:\n%s", e.stderr)
raise
return True, "nooutputneeded"
def cmd_remove_audio_track(args):
"""
1. Removes a specific audio track from the video (Main Output).
"""
probe = ffmpeg.probe(str(args.input))
remove_indices = []
for audio_index in args.audio_index:
remove_indices.append(select_audio_stream(args.input, audio_index))
remaining_audio = [ remaining_audio = [
s s
for s in probe["streams"] for s in probe["streams"]
if s["codec_type"] == "audio" and s["index"] not in remove_indices if s["codec_type"] == "audio"
and s["index"] not in remove_indices
] ]
if not remaining_audio:
logger.error("No audiotracks remaining")
return False, "nooutputneeded"
remaining_audio = translate_audio_tracks(remaining_audio)
removed_was_default = any( if not remaining_audio:
s["index"] in remove_indices and s.get("disposition", {}).get("default", 0) == 1 logger.warning("No audiotracks remaining")
return False, "nooutputneeded"
removed_default_audio = any(
s["codec_type"] == "audio"
and s["index"] in remove_indices
and s.get("disposition", {}).get("default", 0) == 1
for s in probe["streams"] for s in probe["streams"]
) )
@@ -625,13 +565,22 @@ def cmd_remove_audio_track(args):
"copy", "copy",
"-c:a", "-c:a",
"copy", "copy",
"-c:s",
get_scodec(args.output),
] ]
) )
if removed_was_default and remaining_audio: if output_subtitle_codec:
# First remaining audio stream in the output becomes default cmd.extend(["-c:s", get_scodec(args.output)])
else:
cmd.extend(["-c:s", "copy"])
cmd.extend(
[
"-map_metadata",
"0",
]
)
if removed_default_audio:
cmd.extend( cmd.extend(
[ [
"-disposition:a", "-disposition:a",
@@ -642,10 +591,13 @@ def cmd_remove_audio_track(args):
) )
cmd.append(str(args.output)) cmd.append(str(args.output))
logger.trace(" ".join(cmd)) logger.trace(" ".join(cmd))
if args.dry_run: if args.dry_run:
logger.debug(f"Dry run: {' '.join(cmd)}") logger.debug("Dry run: %s", " ".join(cmd))
return True, "nooutputneeded" return True, "nooutputneeded"
try: try:
result = subprocess.run( result = subprocess.run(
cmd, cmd,
@@ -653,20 +605,47 @@ def cmd_remove_audio_track(args):
text=True, text=True,
check=True, check=True,
) )
logger.debug("FFmpeg subprocess successful") logger.debug("FFmpeg subprocess successful")
logger.trace(result.stdout) logger.trace(result.stdout)
return True, "nooutputneeded" return True, "nooutputneeded"
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
logger.error( logger.error("Command: %s", " ".join(e.cmd))
"Command failed (%s):\n%s\n\nstderr:\n%s", logger.error("Return code: %s", e.returncode)
e.returncode, logger.error("STDOUT:\n%s", e.stdout)
" ".join(map(str, e.cmd)), logger.error("STDERR:\n%s", e.stderr)
e.stderr, raise
)
raise RuntimeError(f"Command failed: {' '.join(e.cmd)}") from e
def cmd_remove_subs(args):
probe = ffmpeg.probe(str(args.input))
if args.num is None:
remove_indices = {
s["index"]
for s in probe["streams"]
if s["codec_type"] == "subtitle"
}
else:
remove_indices = set(args.num)
return remove_streams(
args=args,
remove_indices=remove_indices,
)
def cmd_remove_audio_track(args):
remove_indices = {
select_audio_stream(args.input, audio_index)
for audio_index in args.audio_index
}
return remove_streams(
args=args,
remove_indices=remove_indices,
output_subtitle_codec=True,
)
def cmd_extract_audio_track(args): def cmd_extract_audio_track(args):
""" """
1. Saves that specific audio track as a standalone MP3 (Extra Output). 1. Saves that specific audio track as a standalone MP3 (Extra Output).
@@ -950,20 +929,20 @@ def cmd_set_primary_audio(args):
return True, out return True, out
def cmd_show_metadata(args, show_metadata=False, return_tags=None):
def cmd_show_metadata(args, show_metadata=False):
"""
Prints metadata directly from ffprobe output.
"""
probe = ffmpeg.probe(str(args.input)) probe = ffmpeg.probe(str(args.input))
format_tags = probe.get("format", {}).get("tags", {}) format_tags = probe.get("format", {}).get("tags", {})
if return_tags is not None:
return_tags.clear()
return_tags.update(format_tags)
if show_metadata: if show_metadata:
logger.info("=== FORMAT METADATA ===") logger.info("=== FORMAT METADATA ===")
for k, v in format_tags.items(): for k, v in format_tags.items():
logger.info(f"{k}: {v}") logger.info(f"{k}: {v}")
# logger.info(f"\n=== STREAM METADATA ===") # logger.info(f"\n=== STREAM METADATA ===")
# for i, stream in enumerate(probe.get("streams", [])): # for i, stream in enumerate(probe.get("streams", [])):
# logger.info(f"\n--- Stream {i} ({stream.get('codec_type')}) ---") # logger.info(f"\n--- Stream {i} ({stream.get('codec_type')}) ---")
@@ -974,7 +953,7 @@ def cmd_show_metadata(args, show_metadata=False):
# else: # else:
# for k, v in tags.items(): # for k, v in tags.items():
# logger.info(f"{k}: {v}") # logger.info(f"{k}: {v}")
return False, format_tags return False, "nooutputneeded"
def cmd_set_metadata(args): def cmd_set_metadata(args):
@@ -1121,34 +1100,55 @@ def cmd_apply_chapters(args):
return True, "nooutputneeded" return True, "nooutputneeded"
def cmd_remove_russian_from_db(args_in): def cmd_remove_russian_from_db(args):
""" """
Find all media with russian audio Find all media with russian audio
remove russian audio tracks remove russian audio tracks
""" """
file = args_in.input
file = args.input
logger.trace("Run remove russian cmd") logger.trace("Run remove russian cmd")
try: try:
audio_tracks = translate_audio_tracks(get_audio_tracks(file)) audio_tracks = translate_audio_tracks(get_audio_tracks(file))
subtitle_tracks = translate_audio_tracks(get_subtitle_tracks(file))
except ValueError as e: except ValueError as e:
logger.warning("%s", e) logger.warning("%s", e)
return False, "nooutputneeded" return False, "nooutputneeded"
logger.trace(f"Remove Russian from File: {file.name}") logger.trace("Remove Russian from File: %s", file.name)
logger.trace(f"With Audio tracks: {audio_tracks}")
remove_tracks = [] remove_indices = {
track["number"]
for track in audio_tracks + subtitle_tracks
if is_russian_track(track)
}
for track in audio_tracks: for track in audio_tracks:
if is_russian_track(track): if track["number"] in remove_indices:
remove_tracks.append(track["number"]) logger.debug(
logger.debug(f"Removing russian track {track['number']} from {file}") "Removing russian audio track %s from %s",
args = args_in track["number"],
args.audio_index = remove_tracks file,
if remove_tracks: )
cmd_remove_audio_track(args)
return True, "nooutputneeded" for track in subtitle_tracks:
else: if track["number"] in remove_indices:
logger.debug(
"Removing russian subtitle track %s from %s",
track["number"],
file,
)
if not remove_indices:
return False, "nooutputneeded" return False, "nooutputneeded"
return remove_streams(
args=args,
remove_indices=remove_indices,
)
def cmd_test(args): def cmd_test(args):
""" """
@@ -1289,7 +1289,8 @@ def cmd_test(args):
) )
args = ArgsBuffer() args = ArgsBuffer()
args.input = file_path args.input = file_path
tags = cmd_show_metadata(args) tags = {}
cmd_show_metadata(args,return_tags=tags)
return_value = True return_value = True
@@ -1398,17 +1399,18 @@ def cmd_test(args):
audio_tracks = translate_audio_tracks(get_audio_tracks(file_path)) audio_tracks = translate_audio_tracks(get_audio_tracks(file_path))
logger.debug(f"Audio tracks: {audio_tracks}") logger.debug(f"Audio tracks: {audio_tracks}")
for audio_track in audio_tracks: for audio_track in audio_tracks:
if is_russian_track(audio_track["description"]): if is_russian_track(audio_track):
logger.error( logger.error(
f"Found russian track: {audio_track} in file {file_path}" f"Found russian track: {audio_track} in file {file_path}"
) )
return return_value and False return return_value and False
# logger.debug(f"Subtitle tracks: {subtitle_tracks}") subtitle_tracks = translate_audio_tracks(get_audio_tracks(file_path))
# for subtitle_track in subtitle_tracks: logger.debug(f"Subtitle tracks: {subtitle_tracks}")
# if is_russian_track(audio_track["description"]): for subtitle_track in subtitle_tracks:
# logger.error(f"Found russian track: {subtitle_track} in file {file}") if is_russian_track(subtitle_track):
# return return_value and False logger.error(f"Found russian track: {subtitle_track} in file {file_path}")
return return_value and False
logger.info(f"{green_tag('PASS')} {file_path}") logger.info(f"{green_tag('PASS')} {file_path}")
return return_value return return_value