0

I have a bunch of video files with associated subtitles. I am trying to program a bash script that merges the subtitles and videos into matroshka containers.

The input video files are named "videoName.mkv", "videoName.webm" or videoName.mp4" depending on the original container. The input subtitles files are named "videoName.languageCode.vtt". The output video files are named "subsvideoName.mkv"

Here is the script I came up with:

#!/bin/bash

for video in *; do
    if [[ $video =~ \.(mkv|webm|mp4)$ ]]; then
        [[ $video =~ \.(mkv|webm|mp4) ]]
        format=${BASH_REMATCH}
        [[ $video =~ .*[^$format] ]]
        name=${BASH_REMATCH}
        echo $name
        arsubs=()
        for subs in *; do
            echo "$subs"
            if [[ $subs =~ $name.*\.vtt$ ]]; then
                [[ $subs =~ \.[a-zA-Z0-9\-]*\.vtt$ ]]
                lang=$BASH_REMATCH
                [[ $lang =~ [^\.][a-zA-Z0-9\-]*[^\.] ]]
                lang=$BASH_REMATCH
                if [[ $lang =~ [a-z]*[^a-z]+ ]]; then
                    lang=$BASH_REMATCH
                    [[ $lang =~ [a-z]* ]]
                    lang=$BASH_REMATCH
                fi
                arsubs+=(--language 0:$lang)
                arsubs+=("$subs")
            fi
        done
        nameout=subs$name.mkv
        mkvmerge -o "$nameout" "$video" "${arsubs[@]}"
    fi
done

The regex I'm having trouble with is [[ $subs =~ $nom.*.vtt$ ]] (line 13) that doesn't match if the filename contains regex tags such as "+" or "$". If the filename doesn't contains such characters, the script works well.

I looked around, but the only solutions I could find require escaping the problematic characters which, I think, cannot be done here.

Thank you for your time.

1

You seem to go about this in a very roundabout way, with far too much regular expression matching.

What about this instead?

#!/bin/bash

shopt -s nullglob

for video in *.mkv *.webm *.mp4; do
        name=${video%.*}

        set -- "$name".*.vtt
        if [ -e "$1" ]; then
                lang=${1#$name.}
                lang=${lang%.vtt}

                mkvmerge -o "subs$name.mkv" "$video" --language "0:$lang" "$1"
        fi
done

This assumes that you want to use the first .vtt file that is found for a given $name (if there are multiple .vtt files, e.g. for many languages).

The loop runs over all names that matches any of the three patterns *.mkv, *.webm, and *.mp4. This way you don't have to test your loop variable against these suffixes to figure out whether it matches any of them.

Given a filename in $video, the name portion is then extracted by simply removing the filename suffix, whatever it happens to be. This gives us $name.

With set, we then match the pattern "$name".*.vtt. If this pattern matches at least one filename, then that filename would be in $1, and we extract the language part of it (the bit that matched the * in the pattern) by removing $name. from the front and then .vtt from the end of the matched filename.

We then call mkvmerge with the necessary bits of info. We don't call mkvmerge if there was no .vtt file (which your code seems to be doing).

Note too that if there are multiple files with the same name before the filename suffix, e.g. thing.mp4 and thing.mkv, then these would result in identical mkvmerge commands with the same -o name (substhing.mkv).

You could work around this by instead looping over the .vtt files and then pick the video file format you're most interested in corresponding to the .vtt filename.

At no time do you have to use regular expressions for any of this though.

The following is a variation that would consider all .vtt files for a given name:

#!/bin/bash

shopt -s nullglob

for video in *.mkv *.webm *.mp4; do
        name=${video%.*}

        sub_opts=()
        for sub in "$name".*.vtt; do
                lang=${sub#$name.}
                lang=${lang%.vtt}

                sub_opts+=( --language "0:$lang" "$sub" )
        done

        if [ "${#sub_opts[@]}" -gt 0 ]; then
                mkvmerge -o "subs$name.mkv" "$video" "${sub_opts[@]}"
        fi
done
2
  • I could solve my problem thanks to your answer. I adapted it a bit by looping on the found subtitles because I want to find all the subtitles for every video. – igidrau Nov 25 '20 at 11:57
  • @igidrau See updated answer. – Kusalananda Nov 25 '20 at 12:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.