One of the steps I need after MakeMKV generated the MKV is to add a missing AC3 audio track to avoid transcoding through Plex as most of my clients to not support DTS.
I do not like the internal conversion of MakeMKV as it takes ages to rip and convert multiple discs. Instead I create the discs and afterwards/parallel I use PopCorn MKV AudioConverter to add missing AC3 tracks. But as some movies still contain AC3 and DTS tracks and some only have AC3 tracks for comments I need to sort all the MKVs first by "with AC3" and "with DTS".
And to save this work I created the MKVsortbyAudio script that recursively scans all mkvs in the folder <movies_path> and moves them according their audio codec to <movies_path>_AC3, <movies_path>_DTS and so on. It uses the same folder structure and leaves all other files in <movies_path> untouched so you can drag & drop them back. MKVs without the <required_audio_lang> are moved to <movies_path>_Unknown_Audio so you can check those manually. If an MKV contains multiple audio codecs <prefer_audio_codec> is used for sorting. MKVsortbyAudio sorts <iterations> mkvs per script execution.
Requirements
- Linux
- mkvtoolnix or docker
MKVsortbyAudio bash shell script to sort MKVs by audio codec
MKVsortbyAudio bash shell script to sort MKVs by audio codec
Last edited by mgutt on Fri Nov 22, 2019 5:37 pm, edited 2 times in total.
Re: MKVsortbyAudio bash shell script to sort MKVs by audio codec
Script
Code: Select all
#!/bin/sh
# #####################################
# MKVsortbyAudio v0.3
#
# Description:
# MKVsortbyAudio scans recursive all mkvs in the folder <movies_path> and moves them
# according their audio codec to <movies_path>_AC3, <movies_path>_DTS and so on.
# It uses the same folder structure and leaves all other files in <movies_path>
# untouched so you can drag & drop them back. MKVs without the <required_audio_lang>
# are moved to <movies_path>_Unknown_Audio so you can check those manually.
# If an MKV contains multiple audio codecs <prefer_audio_codec> is used for sorting.
# MKVsortbyAudio requires mkvtoolnix or docker.
#
# Changelog:
# 0.3
# - the source folder will be deleted if its empty
# 0.2
# - check mkv modification filetime to ensure its not currently written through an other app
# 0.1
# - first release
#
# Todo:
#
# #####################################
#
# ######### Settings ##################
movies_path="/volume1/movies/"
iterations=10 # mkv files to process per script execution
required_audio_lang="ger"
prefer_audio_codec="A_AC3" # A_AC3, A_DTS, A_AAC, A_PCM, A_MPEG, etc
docker_config_path="/volume1/docker" # do not mind if you do not use docker
# #####################################
#
# ######### Script ####################
# check user settings
movies_path=$([[ "${movies_path: -1}" == "/" ]] && echo "${movies_path%?}" || echo "$movies_path")
docker_config_path=$([[ "${docker_config_path: -1}" != "/" ]] && echo "${docker_config_path}/" || echo "$docker_config_path")
# globals
file_basename=""
file_extension=""
mkv_path=""
mkv_dirbasename=""
mkv_filename=""
function exitus() {
exit_status=$1
if [[ -x "$(command -v docker)" ]] && [[ "$(docker ps -q -f name=mkvtoolnix)" ]]; then
echo "Stop mkvtoolnix container"
docker stop mkvtoolnix
docker rm mkvtoolnix
fi
exit $exit_status
}
function mkv_next() {
path=$1
echo "Parsing $path ..."
for file in "$path"/*; do
# mkv has been already found
if [[ -n $mkv_path ]]; then
return
# regular file
elif [ -f "$file" ]; then
file_time=$(stat -c %Y "$file") # file modification time
file_time=$(($file_time+60)) # the last modification of the file should be a few time ago
current_time=$(date +%s) # actual timestamp
if [[ $file_time -gt $current_time ]]; then
continue
fi
file_basename=$(basename -- "$file")
file_extension="${file_basename##*.}"
if [[ $file_extension != "mkv" ]]; then
continue
fi
mkv_path=$file
mkv_dirname=$(dirname "$file")
mkv_dirname="${mkv_dirname/$movies_path/}"
mkv_dirname="${mkv_dirname:1}" # remove first slash
mkv_dirname="${mkv_dirname/$movies_path/}" # remove <movies_path> from path
docker_mkv_path="/storage/${mkv_dirname}/${file_basename}"
echo "Found $mkv_path"
break
# dir
else
mkv_next "$file"
fi
done
}
function mkv_move() {
path=$1
subdir="${path}/${mkv_dirname}"
mkdir -p "$subdir/"
if [[ $? != 0 ]]; then
echo "Error while creating the directory '$subdir/' in line $LINENO"
exitus 1
fi
echo "Created the directory '$subdir/'"
mkv_path_new="${subdir}/${file_basename}"
mv "$mkv_path" "$mkv_path_new"
if [[ $? != 0 ]]; then
echo "Error moving the file '$mkv_path_new' in line $LINENO"
exitus 1
fi
echo "Moved MKV to '$mkv_path_new'"
mkv_dirname=$(dirname "$mkv_path")
if [[ -z "$(ls -A "$mkv_dirname")" ]]; then
echo "Delete empty folder '$mkv_dirname'"
rmdir "$mkv_dirname"
fi
}
function mkv_getinfo() {
# check if mkvtoolnix exists
if [[ -x "$(command -v mkvmerge)" ]]; then
echo "mkvtoolnix will be used to fetch tracks information"
mkv_info="$(mkvmerge -J "$mkv_path")"
return "$mkv_info"
# check if docker exists
elif [[ -x "$(command -v docker)" ]]; then
echo "Docker will be used to fetch tracks information"
# check if mkvtoolnix container exists
if [[ ! "$(docker ps -q -f name=mkvtoolnix)" ]]; then # https://stackoverflow.com/a/38576401/318765
# check for blocking container
if [[ ! "$(docker ps -aq -f status=exited -f name=mkvtoolnix)" ]]; then
docker rm mkvtoolnix
fi
echo "mkvtoolnixcontainer needs to be started"
# start mkvtoolnix container
docker_options=(
run -d
--name=mkvtoolnix
-e TZ=Europe/Berlin
-v "${docker_config_path}mkvtoolnix:/config:rw"
-v "${movies_path}:/storage:rw"
jlesage/mkvtoolnix
)
echo "docker ${docker_options[@]}"
docker "${docker_options[@]}"
fi
mkv_info="$(docker exec mkvtoolnix /usr/bin/mkvmerge -J "$docker_mkv_path")"
return
fi
echo "mkvtoolnix and docker do not exist!"
exitus 1
}
function delete_empty_folder() {
path=$1
}
for i in $(seq 1 $iterations); do
# obtain next mkv file
shopt -s nullglob # avoid empty directory errors (https://unix.stackexchange.com/questions/56051/avoiding-errors-due-to-unexpanded-asterisk)
mkv_next "$movies_path" # fills $mkv_path
shopt -u nullglob # its important to reset this setting (https://unix.stackexchange.com/questions/534858/why-does-shopt-s-nullglob-remove-a-string-with-question-mark-in-an-array-elemen)
# no mkv file found
if [[ -z $mkv_path ]]; then
echo "No mkv file found!"
exitus 0
fi
# get tracks info
mkv_getinfo # fills $mkv_info
if [[ -z $mkv_info ]]; then
echo "Error while fetching tracks information with mkvmerge"
exitus 1
fi
echo "Informations of all tracks have been obtained."
# parse info
best_channels=0
best_codec=false
sub_track_ids=(); track_langs=(); track_names=();
while read -r line ; do
# Note: we did not use "jq -r" to parse JSON as it needs installation
track_codec_name=$(echo $line | grep -oP '^.*?(?=\")')
track_id=$(echo $line | grep -oP '(?<="id": )[0-9]+')
track_bits=$(echo $line | grep -oP '(?<="audio_bits_per_sample": )[0-9]+')
track_channels=$(echo $line | grep -oP '(?<="audio_channels": )[0-9]+')
track_codec_id=$(echo $line | grep -oP '(?<="codec_id": ").*?[^\\](?=\",)')
track_lang=$(echo $line | grep -oP '(?<="language": ")[a-z]+')
track_name=$(echo $line | grep -oP '(?<="track_name": ").*?[^\\](?=\",)') # most flexible way of getting a JSON value (https://stackoverflow.com/a/6852427/318765)
track_default=$(echo $line | grep -oP '(?<="default_track": )(true|false)')
track_forced=$(echo $line | grep -oP '(?<="forced_track": )(true|false)')
track_type=$(echo $line | grep -oP '(?<=")[a-z]+$')
echo "track_id:$track_id track_type:$track_type track_channels:$track_channels track_codec_id:$track_codec_id track_lang:$track_lang best_codec:$best_codec"
# collect subtitles in prefered languages
if [[ $track_type == "audio" ]] && [[ $track_lang == $required_audio_lang ]]; then
if [[ $best_channels -gt $track_channels ]]; then
continue
fi
best_codec="$track_codec_id"
best_channels="$track_channels"
# we already found the audio codec with the hightest priority
if [[ $best_codec == $prefer_audio_codec ]] && [[ $best_channels -ge 6 ]]; then
break
fi
fi
done < <(echo "$mkv_info" |
tr -d '\n' | # we need to remove line breaks with "tr" to force grep to return one-liners
grep -oP '(?<=codec": ").*?"type": "[a-z]+') # Regex is faster than looping through all lines
# those mkvs need manual checking
if [[ $best_codec == false ]]; then
best_codec="Unknown_Audio"
fi
# sort mkv by audio codec
best_codec="${best_codec/A_/}" # remove "A_" from "A_AC3"
mkv_subdir="${movies_path}_${best_codec}"
mkv_move "$mkv_subdir"
# unset mkv path to be able to obtain the next mkv file
mkv_path=""
mkv_info=""
done
exitus 0
Last edited by mgutt on Mon Nov 25, 2019 9:16 am, edited 2 times in total.
Re: MKVsortbyAudio bash shell script to sort MKVs by audio codec
This is how it looks after several MKVs have been sorted:
The source folder and the cover.jpg is left untouched:
And the MKV has been moved to the the target "DTS" folder:
The source folder and the cover.jpg is left untouched:
And the MKV has been moved to the the target "DTS" folder:
Last edited by mgutt on Fri Nov 22, 2019 5:01 pm, edited 1 time in total.
Re: MKVsortbyAudio bash shell script to sort MKVs by audio codec
As you can see this MKV is moved to the "AC3" folder as the best German audio track is "AC3 Stereo":
Instead, this MKV has been moved to "DTS" as the German "AC3 Stereo" track has less channels than the 5.1 DTS track (and finally is an AC3 stereo track that follows a 5.1 track in 99% of all cases a "Director's Comment"):
And finally this MKV has been moved to "AC3" even tough it contains an DTS track with more channels (6-channels are all I need - feel free to change the script to fit your needs):
Instead, this MKV has been moved to "DTS" as the German "AC3 Stereo" track has less channels than the 5.1 DTS track (and finally is an AC3 stereo track that follows a 5.1 track in 99% of all cases a "Director's Comment"):
And finally this MKV has been moved to "AC3" even tough it contains an DTS track with more channels (6-channels are all I need - feel free to change the script to fit your needs):
Re: MKVsortbyAudio bash shell script to sort MKVs by audio codec
Version 0.3 has been released. Since 0.1 I did small changes:
Code: Select all
# 0.3
# - the source folder will be deleted if its empty
# 0.2
# - check mkv modification filetime to ensure its not currently written through an other app