Skip to main content

Subtitle Stripping

Difficulty Setup Time Original Author

Stripping subtitle streams is really simple. This guide will add a simple script that will strip subtitle streams from your file. Optionally, you will have the ability to specify a number of streams that you wish to keep.

caution

Currently, this setup does not container a library scan file test runner. Therefore, it will not process files without another plugin in the library's plugin flow to test that the scanned files need to be processed.

tip

Compliment this configuration with the plugin Re-order subtitle streams by language.

You can re-order your subtitle streams to have a particular language first, then use this process to strip out all the ones remaining.

Instructions:

note

Once installed, you can configure the script by editing the top section:

###################################################################
# CONFIG:
# NUMBER_OF_SUB_STREAMS_TO_KEEP
# Specify the number of subtitle streams to keep.
# Script will preserve the number of subtitle streams specified
# in this value. All others will be stripped out.
# Set this value to 0 to remove all subtitle streams.
NUMBER_OF_SUB_STREAMS_TO_KEEP=1
###################################################################
Ensure you have installed the External Worker Processor Script plugin.

Configure the plugin with the following:

Execution Type:

Bash Script

Script:

#!/usr/bin/env bash
###
# File: strip-subs.sh
# Project: tmp.BXJ7lwK6lZ
# File Created: Friday, 14th October 2022 5:35:18 am
# Author: Josh Sunnex (jsunnex@gmail.com)
# -----
# Last Modified: Friday, 14th October 2022 7:17:32 am
# Modified By: Josh Sunnex (jsunnex@gmail.com)
###

###################################################################
# CONFIG:
# NUMBER_OF_SUB_STREAMS_TO_KEEP
# Specify the number of subtitle streams to keep.
# Script will preserve the number of subtitle streams specified
# in this value. All others will be stripped out.
# Set this value to 0 to remove all subtitle streams.
NUMBER_OF_SUB_STREAMS_TO_KEEP=1
###################################################################

# Parse Args
__library_id=""
__output_cache_file=""
__original_source_file=""
__original_source_size=""
__source_file=""
__source_size=""
__return_data_file=""
__positional_args=()
for i in "${@}"; do
case $i in
--library-id=*)
__library_id="${i#*=}"
shift # past argument=value
;;
--output-cache-file=*)
__output_cache_file="${i#*=}"
shift # past argument=value
;;
--original-source-file=*)
__original_source_file="${i#*=}"
shift # past argument=value
;;
--original-source-size=*)
__original_source_size="${i#*=}"
shift # past argument=value
;;
-s=*|--source-file=*)
__source_file="${i#*=}"
shift # past argument=value
;;
--source-size=*)
__source_size="${i#*=}"
shift # past argument=value
;;
--return-data-file=*)
__return_data_file="${i#*=}"
shift # past argument=value
;;
-*|--*)
echo "Unknown option $i"
exit 1
;;
*)
__positional_args+=("$1") # save positional arg
shift # past argument
;;
esac
done
set -- "${__positional_args[@]}" # restore positional parameters

# Helper functions
__jq_item() {
echo "${1}" | base64 --decode | jq -r "${2}"
}

# Generate ffmpeg command
echo
echo "Probing file '${__source_file}'..."
probe=$(ffprobe -show_format -show_streams -print_format json -loglevel quiet "${__source_file}")
if [[ $? -gt 0 ]]; then
# Probably not a video file
echo "Unable to probe file. This is probably not a valid video file so will ignore. EXIT!"
exit 0
fi
# Ensure no further pipes fail
set -eo pipefail

echo
echo "Building ffmpeg args..."
echo
args="-hide_banner -loglevel info -i '${__source_file}' -strict -2 -max_muxing_queue_size 10240 -map 0 -c:v copy -c:a copy"
subtitle_streams_count=0
found_things_to_do="false"
for stream in $(echo "${probe}" | jq -r '.streams[] | @base64'); do
index=$(__jq_item "${stream}" '.index')
codec_type=$(__jq_item "${stream}" '.codec_type')
codec_name=$(__jq_item "${stream}" '.codec_name')
echo " Stream #${index}: {type:${codec_type}, codec:${codec_name}}"
if [[ "${codec_type}" == "subtitle" ]]; then
sub_language=$(__jq_item "${stream}" '.tags.language')
sub_title=$(__jq_item "${stream}" '.tags.title')
echo " - Subtitile Info: {language:${sub_language}, title:${sub_title}}"
if [[ "${subtitle_streams_count}" -ge "${NUMBER_OF_SUB_STREAMS_TO_KEEP}" ]]; then
echo " - MARKING FOR REMOVAL"
args="${args} -map -0:s:${subtitle_streams_count}"
found_things_to_do="true"
else
args="${args} -c:s:${subtitle_streams_count} copy"
echo " - MARKING FOR COPY"
fi
((subtitle_streams_count+=1))
elif [[ "${codec_type}" == "video" ]]; then
vid_width=$(__jq_item "${stream}" '.width')
vid_height=$(__jq_item "${stream}" '.height')
echo " - Video Info: {width:${vid_width}, height:${vid_height}}"
echo " - MARKING FOR COPY"
elif [[ "${codec_type}" == "audio" ]]; then
audio_channels=$(__jq_item "${stream}" '.channels')
audio_bit_rate=$(__jq_item "${stream}" '.bit_rate')
echo " - Audio Info: {channels:${audio_channels}, bit_rate:${audio_bit_rate}}"
echo " - MARKING FOR COPY"
else
echo " - Stream Info: null"
fi
done
# Only provide a command to execute if streams were found to need modifying
if [[ "${found_things_to_do}" ]]; then
exec_command="ffmpeg ${args} -y '${__output_cache_file}'"
else
exec_command=""
fi
echo
echo "Command: ${exec_command}"

# Generate return object for Unmanic to execute the command
# This script could also execute the command from here if you wish, but Unmanic will process the log differently
return_object=$(jq --null-input \
--arg exec_command "${exec_command}" \
--arg file_out "${__output_cache_file}" \
'{"exec_command": $exec_command, "file_out": $file_out}')
if [[ ! -z ${__return_data_file} ]]; then
echo "${return_object}" > "${__return_data_file}"
cat "${__return_data_file}"
fi

Arguments:

--source-file="{source_file_path}" 
--output-cache-file="{file_out_path}"
--return-data-file="{data_json_file}"