Automatic Subtitle and Audio Transfer/Sync Scripts

GitHub: https://github.com/iwalton3/media-scripts/tree/master/subtitle-audio-transfer

Sometimes, the media you want to watch is available in a higher video or audio quality without the desired subtitles or appropriate audio tracks. Merging the videos to contain the desired tracks is usually error-prone, as the tracks are not usually synced and even a sync error of less than a second can render the video unwatchable.

These scripts automatically process video files and sync the subtitle or audio tracks, using the sync parameters in the mkv video format. They are great for adding subtitles to raw anime releases when there are not comparable subtitled versions. The subtitle sync uses the audio sync script as a fallback, so you should install both even if you do not plan to transfer audio tracks.

You’ll need a copy of mkvtoolnix and python3 for these scripts to work. I suggest using cygwin or WSL to run the scripts if you are not running Linux. The sync work is done by subsync and align-videos-by-sound, which should be installed with the following commands:

sudo pip3 install git+https://github.com/smacke/subsync
sudo pip3 install git+https://github.com/jeorgen/align-videos-by-sound

The subtitle sync script processes files in the subtitle and video folder. The files in the subtitle folder can be videos with the desired subtitles, or raw subtitle files. Note that the fallback mode for subtitle sync using the audio tracks will not work with raw subtitle files. The script is fairly fast at processing, and has error handling that will by default not tolerate sync errors over 20 seconds. (You can change that in the script if you have something with a higher margin of error.)

subtitle-sync.sh:

#!/bin/bash
i=0
paste <(ls subtitle) <(ls video) | column -ts $'\t'
read -p "Please confirm the numbering is correct. [y/N] " verify
if [[ ! "$verify" =~ [yY] ]]
then
    echo "Aborted!"
    exit 1
fi

paste <(ls subtitle) <(ls video) | while IFS=$'\t' read -r s v
do
    ((i++))
    out="$(sed -e 's/\.[^.]\+$/.mkv/g' <<< "$s")"
    if [[ ! -e "$out" ]]
    then
        if [[ -e "$i.txt" ]]
        then
            rm "CHECKME_$out"
            offset=$(cat "$i.txt")
        else
            ffmpeg -loglevel quiet -nostdin -y -i subtitle/"$s" subtmp.srt < /dev/null
            offset=$(subsync video/"$v" -i subtmp.srt 2>&1 1>/dev/null | grep 'offset seconds' | sed 's/.*: /1000*/g' | bc | sed 's/\..*//g')
            echo "Sync: $offset"
            rm subtmp.srt
            if [[ "${offset#-}" -gt "20000" ]]
            then
                echo "Sync using subtitles failed. Trying audio sync..."
                offset=$(audio-transfer.py -print-offset-only video/"$v" subtitle/"$s")
                echo "Sync: $offset"
                if [[ "${offset#-}" -gt "20000" ]] || [[ "$offset" == "" ]]
                then
                    echo "Sync failure!"
                    echo "Please write $i.txt"
                    echo "$i" >> failed.txt
                    out="CHECKME_$out"
                    offset=0
                fi
            fi
        fi
        mkvmerge -o "$out" -A -D -B -T -y "-1:$offset" subtitle/"$s" video/"$v"
    fi
done

The audio subtitle sync script is much slower, but it is precise enough to sync audio files in most cases. Checking the result before replacing your files is highly encouraged. The script does not have a fallback mode, so I suggest checking to make sure the sync settings are reasonable on all the videos. The usage of this script is more complicated, as audio tracks do use more space, and removal of unwanted audio tracks may be desired. The usage is: audio-transfer.py [-copy-subtitles] [+lang,] [-all|-lang,] main_src audio_src destination (Eg: +eng,jpn adds audio from audio_src. -jpn removes audio from main_src. -all removes all audio from main_src.) An example usage would be to copy all English audio tracks from audio folder into tracks from video folder, using names from audio folder:

paste <(ls audio) <(ls video) | while IFS=$'\t' read -r a v; do audio-transfer.py +eng video/"$v" audio/"$a" "$(sed 's/\.[^.]\+$/.mkv/g' <<< "$a")" < /dev/null; done

audio-transfer.py:

#!/usr/bin/env python3
import sys
import subprocess
from align_videos_by_soundtrack.align import SyncDetector, cli_common

remove = []
remove_specified = False
remove_all = False
add = []
add_specified = False
copy_subtitles = False
files = []
print_offset_only = False

for argument in sys.argv[1:]:
    if argument == "-copy-subtitles":
        copy_subtitles = True
    elif argument == "-print-offset-only":
        print_offset_only = True
    elif argument.startswith("-"):
        if argument == "-all":
            remove_all = True
        else:
            remove_specified = True
            remove = argument[1:].split(",")
    elif argument.startswith("+"):
        add_specified = True
        add = argument[1:].split(",")
    else:
        files.append(argument)

if print_offset_only:
    main_src, audio_src = files
    destination = None
else:
    main_src, audio_src, destination = files

cli_common.logger_config()

with SyncDetector() as det:
    result = det.align([audio_src, main_src])

offset = 0
if result[0]["trim"] > 0:
    offset = int(-result[0]["trim"]*1000)
else:
    offset = int(result[0]["pad"]*1000)

arguments = ["mkvmerge","-o",destination,"-D","-B","-T"]
if not copy_subtitles:
    arguments.extend(["-S","-M"])
if add_specified:
    arguments.extend(["-a",",".join(add)])
arguments.extend(["-y", "-1:" + str(offset), audio_src])
if remove_specified:
    arguments.extend(["-a","!"+(",".join(remove))])
elif remove_all:
    arguments.append("-A")
arguments.append(main_src)

if print_offset_only:
    print(offset)
else:
    print("Sync: {0}".format(offset))
    subprocess.check_call(arguments)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.