I've been using mkvmerge to merge a video file and subtitles, I'm using the following command

mkvmerge -o output.mkv video.mp4 subtitles.srt

Which is working fine but as you can probably guess this is really slow operation

  • I've to open terminal in every one of those directories where movies are stored.
  • Then I've to copy and paste the names of the files in the command
  • Then I've to rename the output file
  • Then delete the original files which I used

This is a lengthy process.

Ideal situation that I want.

  • I'll go into the folder which contains a movie file and a subtitle file with the same name.
  • I select a movie file and and a subtitles file, right click and run a nautilus script on them.
  • They get merged
  • Original file gets deleted.
  • Rename the merged file with the name of the orignal file with mkv extension

This doesn't need to be recursive, I don't want my HDD creating and deleting 100's of files at ones. Movie files and Subtitle files generally have the same name but some times have "_en" post fixed to subtitle files. If you have another approach of doing it I'm on board with that too.

  • 1
    Hi, I'm not familiar with the command and how its functionality it is, but one question: you need this command run recursively in each directories and merge like this format in general? mkvmerge -o movie_original_name_output.mkv movie_original_name.mp4 movie_original_name.srt? if yes, I can write a script for this Jul 26, 2017 at 8:41
  • @AFSHIN not actually recursively, but the script should recognise movie file, subtitle file, then join them and then delete the originals. Jul 27, 2017 at 13:15

3 Answers 3


I've decided to gain some experience with Bash and wrote the below script, that has next features:

  • It can handle all files and folders, placed into the current directory.
  • If there are more than one subtitle or video file (except sample files), the script will ask for manual interaction.
  • In all other cases the script will automatically move all files and folders into a backup directory (be careful where you run it!) , that will be moved into the user's trash folder, instead of to be deleted.
  • It can handle several types of video and subtitle extensions.
  • It uses mkvmerge to merge video with subtitle files also uses notify-send to show some messages in GUI. Also it uses gvfs-trash to move files in user's trash folder.
  • It can be used as Nautilus script or as regular shell script but GIU environment is required while notify-send command exists into the script's body.
  • The name of the output file could be based on the directory name (default) or on the name of the source video file.
  • Additionally when only two files are selected (one video and one subtitle file), they will be merged and rest of the folder's content will be retained. The output file will be named after the source video file. This option is available only when the script is used as Nautilus script.

The script:

#!/bin/bash -e

# Check if all tools are available
[ -x /usr/bin/notify-send ] || (echo "Please, install 'notify-send'"; exit 1)
[ -x /usr/bin/mkvmerge ] || (echo "Please, install 'mkvmerge'"; exit 1)

# Allowed video and subtitle file extensions
EXT_VIDEO=("mp4 avi mpg mov mkv wmv")
EXT_SUB=("sub str srt vtt")

# Files, which names contains some of next strings will be removed in auto mode
FILTER=("sample Sample SAMPLE")

# Log file
echo > "$MERGE_LOG"

# Functions

function get-video-and-sub-file-names {
    # Get the names of the video and subtitle files and move the rest of the files into the Backup directory
    for ((i=0; i<${#FILE_LIST[@]}; i++)); do

        if   [[ "${EXT_SUB[@]}" == *"$FILE_EXT"* ]]; then
        elif [[ "${EXT_VIDEO[@]}" == *"$FILE_EXT"* ]]; then
            # We need 'find' to manipulate only with files, because "$BACKUP_DIR" is in the queue
            find ./* -maxdepth 0 -type f -name "${FILE_LIST[$i]}" -exec mv "{}" "$BACKUP_DIR" \; -exec echo -e "The file {} was REMOVED.\n" >> "$MERGE_LOG" \;

function get-the-content-of-the-current-directory {
    # Get the content of the current directory
    shopt -s nullglob
    shopt -u nullglob

function mkvmerge-video-and-sub-files {
    # Create merged file
    sleep 3

# Scenario 1: If exactly two files are selected in Nautilus! Then check if they are 1 video and 1 subtitle files, if yes - merge and remove them
# Scenario 2: Else run the standard procedure

# Get the files, selected in Nautilus as file list. Use next command to check the result: notify-send "MESSAGE" "`echo -e "${#FILE_LIST[@]}"; printf '%s\n' "${FILE_LIST[@]}"`"

if [ "${#FILE_LIST[@]}" -eq "2" ]
then # Scenario 1

    # Get the names of the video and subtitle files

    if   [[ "${EXT_SUB[@]}" == *" $SUB_FILE_EXT "* ]] && [[ "${EXT_VIDEO[@]}" == *" $VIDEO_FILE_EXT "* ]]
        notify-send "OK" "`echo -e "The following files will be MERGED and MOVED to trash:"; printf '\t-\ %s\n' "${FILE_LIST[@]##*/}"`"

        # Construct the name of the merged file. 

        # Merge the files

        # Move video and subtitle files into user's trash directory and create trash infofile
        if [ -f "$OUTPUT_FILE" ]
            gvfs-trash "$VIDEO_FULL_FILE_NAME"
            gvfs-trash "$SUB_FULL_FILE_NAME"            
            notify-send "OK" "`echo -e "THE NAME OF THE NEW MERGED FILE IS:\n${OUTPUT_FILE##*/}"`"
            notify-send "ERROR 1" "`echo "Something went wrong!"`"
        notify-send "ERROR" "`echo -e "\n\t\nTo use this function, please select exactly:\n\t- 1 video file and\n\t- 1 subtitle file!\n\t\nYou are selected these files:"; printf '\t-\ %s\n' "${FILE_LIST[@]##*/}"`"

else # Scenario 2

    # Get the current directory name

    # Create Backup sub-directory 
    [ -d "${BACKUP_DIR}" ] || mkdir "$BACKUP_DIR" && echo "The directory $BACKUP_DIR was CREATED.\n" > "$MERGE_LOG"

    # Move all sub-directories into the Backup directory
    shopt -s dotglob
    find ./* -maxdepth 0 -type d ! -name "*$BACKUP_DIR*" -prune -exec mv "{}" "$BACKUP_DIR" \; -exec echo "The directory {} was REMOVED.\n" >> "$MERGE_LOG" \;
    shopt -u dotglob

    # Move all files and folders, whose names contains a string, that exists in $FILTER[@]
    for f in $FILTER; do
        shopt -s dotglob
        find ./* -maxdepth 0 ! -name "*$BACKUP_DIR*" -type f -name "*$f*" -exec mv "{}" "$BACKUP_DIR" \; -exec echo "The file {} was REMOVED.\n" >> "$MERGE_LOG" \;
        shopt -u dotglob

    # Get the entire content of the current directory

    # Get the names of the video and subtitle files and move the rest of the files into the Backup directory

    # Construct the name of the merged file. It could be based on the parent directory or on the video file name Make your choice and comment/uncomment next lines

    # Get the entire content of the current directory after the filtering

    echo -e "$(cat $MERGE_LOG)" && notify-send "OK" "`echo -e "$(cat $MERGE_LOG)"`" && echo > "$MERGE_LOG"

    # Check the current structure of the directory
    if [ "${#FILE_LIST[@]}" -ne "3" ]; then
        echo "The content structure must consists of next 3 items:" > "$MERGE_LOG"
        echo "\t- 1 movie file,\n\t- 1 subtitle file and\n\t- 1 backup directory." >> "$MERGE_LOG"
        echo "\n\t\nThe current number of contained items is ${#FILE_LIST[@]}." >> "$MERGE_LOG" && echo >> "$MERGE_LOG"
        echo "\n\t\nPLEASE RESOLVE THIS MANUALLY!" >> "$MERGE_LOG"
        echo -e "$(cat $MERGE_LOG)" && notify-send "ERROR" "`echo -e "$(cat $MERGE_LOG)"`"
        echo "The directory structure looks good, is contains ${#FILE_LIST[@]} items." > "$MERGE_LOG"
        echo " - The source VIDEO file is: ${VIDEO_FULL_FILE_NAME::21}... .${VIDEO_FULL_FILE_NAME##*.}" >> "$MERGE_LOG"
        echo " - The source SUB file is: ${SUB_FULL_FILE_NAME::25}... .${SUB_FULL_FILE_NAME##*.}" >> "$MERGE_LOG"
        echo "They has been merged and removed!" | tr /a-z/ /A-Z/ >> "$MERGE_LOG"

        # Merge the files

        # Move video and subtitle files into the Backup directory

        # Move the Backup directory to trash and create trash infofile
        if [ -f "$OUTPUT_FILE" ]; then
            gvfs-trash "$BACKUP_DIR" 

            echo "\n\t\nThe Backup directory has been MOVED to Trash!\n\t\n" >> "$MERGE_LOG"
            echo "The name of the new merged file is:"  | tr /a-z/ /A-Z/ >> "$MERGE_LOG"
            echo "${OUTPUT_FILE##*/}" >> "$MERGE_LOG"
            echo -e "$(cat $MERGE_LOG)" && notify-send "OK" "`echo -e "$(cat $MERGE_LOG)"`"
            echo "Something went wrong!" && notify-send "ERROR 2" "`echo "Something went wrong!"`"


exit 1


  • Create executable file and paste the above content inside. Let's call this file merge-video-sub:

    touch merge-video-sub 
    chmod +x merge-video-sub
    nano merge-video-sub
  • Copy (or ln -s) this file into the folder ~/.local/share/nautilus/scripts to make it available as Nautilus script for the current user.

  • At the moment I can't find a way how to make it available system wide as Nautilus script.

  • Copy the file into ~/bin (and add export PATH=$PATH:~/bin to the bottom of ~/.bashrc if is needed) to make it available as shell command for the current user.

  • Copy the file into /usr/local/bin to make it available as shell command system wide.

  • The short way is to curl the script from my PasteBin directly into nautilus/scripts folder:

    curl https://pastebin.com/raw/HrLTibuR | sed -e 's/\r$//' > $HOME/.local/share/nautilus/scripts/merge-video-sub
    chmod +x $HOME/.local/share/nautilus/scripts/merge-video-sub


enter image description here

Additional references:

  • 1
    This has everything one could want and more! accepted it Jul 28, 2017 at 2:43
  • 1
    Hi, @SumeetDeshmukh, I've made two minor changes into the script's code. But in general they will not affect its work.
    – pa4080
    Jul 28, 2017 at 15:38
  • 1
    Hi, @SumeetDeshmukh, please don't waste your time to copy the new version. I will add another functionality and will ping you again. The script will be able to merge two selected files, for these cases when into the folder you have a collection as part-1.mp4, part-1.sub, part-2.mp4, part-2.sub, etc... I decided to tidy up my videos and I found this function is necessary.
    – pa4080
    Jul 29, 2017 at 10:19
  • 1
    Yes, it's necessary for Seasons of TV shows and other stuff like that Jul 30, 2017 at 3:15
  • 1
    @SumeetDeshmukh, this option is already available.
    – pa4080
    Jul 30, 2017 at 16:16

I made another script which has GUI. It can find all subtitle files with their related videos (searches for the same name) in folder and sub-folder and merge them at ones.

Here is GitHub link for the script: https://github.com/bhaktanishant/Total-Subtitle-Merger

enter image description here

Here is code :

#!/usr/bin/env python

from Tkinter import Tk, Listbox, Button, Scrollbar, Canvas, Frame, Label
from subprocess import call
from threading import Thread
import os, tkMessageBox
from time import sleep

class MergeApp:

    def __init__(self, root):
        self.root = root
        self.title = "Subtitle Merger By - Nishant Bhakta"
        self.messageBoxTitle = "Message Box"
        self.cancelWarning = "The video which has been started to merge will be merge. Rest will be cancel."
        self.movieListBox = Listbox(self.root)
        self.scrollBar = Scrollbar(self.root)
        self.startButton = Button(self.root, text = "start", state = "disable", command = self.startMerging)
        self.cancelButton = Button(self.root, text = "Stop", state = "disable", command = self.stopMerging)
        self.finishButton = Button(self.root, text = "Exit", state = "normal", command = self.endApplication)
        self.loadingLabel = Label(self.root)
        self.processState = Label(self.root)
        self.movieMap = {}
        self.keyList = []
        self.loadingIcons = ["--", "\\", "|", "/"]
        self.wantToMerge = True
        self.loading = False
        self.warningMessageLoaded = False

    def start(self):
        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()
        # calculate position x and y coordinates
        x = (screen_width/2) - (700/2)
        y = (screen_height/2) - (300/2)
        self.root.geometry('%dx%d+%d+%d' % (700, 300, x, y))

        self.movieListBox.config(width = 68, yscrollcommand = self.scrollBar.set)
        self.movieListBox.pack(side = "left", fill = "y")
        self.scrollBar.config(command = self.movieListBox.yview)
        self.scrollBar.pack(fill = "y", side = "left")
        self.startButton.pack(fill = "x")
        self.cancelButton.pack(fill = "x")
        self.finishButton.pack(fill = "x")
        self.processState.pack(fill = "x", side = "bottom")        
        self.loadingLabel.pack(fill = "x", side = "bottom")
        Thread(target = self.createMovieMap).start()
        self.mainThread = Thread(target = self.startMerge)
        self.root.protocol("WM_DELETE_WINDOW", self.ifCloseWindow)

    def createMovieMap(self):
        #Looking for subtitle
        index = 0
        Thread(target = self.startLoading, args = (True, )).start()
        self.processState.config(text = "Searching Videos..")
        for oneWalk in os.walk(os.getcwd()):
            for fileName in oneWalk[2]:
                if ".srt" in fileName:
                    subtitleName = fileName
                    #Now looking for movie with the name of subtitle
                    for oneWalk in os.walk(os.getcwd()):
                        for fileName in oneWalk[2]:
                            if ".srt" not in fileName:
                                key = subtitleName.replace(".srt", "")
                                if key in fileName:
                                    movieName = fileName
                                    if key not in self.movieMap:
                                        self.movieMap[key] = dict([("subtitleUri", oneWalk[0] + "/" + subtitleName)
                                            , ("movieUri", oneWalk[0] + "/" + movieName)
                                            , ("moviePath", oneWalk[0])])
                                        self.movieListBox.insert(index, " Queued - " + key)
                                        index += 1
        self.startButton.config(state = "normal")
        self.processState.config(text = "Search Complete.")
        self.loading = False

    def startMerge(self):
        for key, value in self.movieMap.iteritems():
            if self.wantToMerge:
                self.processState.config(text = "Merging Video..")
                Thread(target = self.startLoading, args = (True, )).start()
                index = self.keyList.index(key)
                self.movieListBox.insert(index, " Merging - " + key)
                self.movieListBox.itemconfig(index, bg = "yellow")
                if (call(["mkvmerge", "-o", value['moviePath'] + "/merging", value['movieUri'], value['subtitleUri']]) == 0):
                    call(["rm", value['movieUri'], value['subtitleUri']])
                    call(["mv", value['moviePath'] + "/merging", value['moviePath'] + "/"+ key + ".mkv"])
                    self.movieListBox.insert(index, " Successful - " + key)
                    self.movieListBox.itemconfig(index, bg = "green")
                    for name in os.listdir(value['moviePath'] + "/"):
                        if name == "merging":
                            call(["rm", value['moviePath'] + "/merging"])
                    self.movieListBox.insert(index, " Failed - "+ key)
                    self.movieListBox.itemconfig(index, bg = "red", foreground = "white")
        self.loading = False
        self.cancelButton.config(state = "disable")
        self.finishButton.config(state = "normal")
        if self.wantToMerge:
            self.processState.config(text = "Merge Complete.")

    def startLoading(self, loadOrNot):
        self.loading = loadOrNot
        while self.loading:
            for icon in self.loadingIcons:
                self.loadingLabel.config(text = icon)

    def startMerging(self):

    def changeButtonState(self):
        self.startButton.config(state = "disable")
        self.cancelButton.config(state = "normal")
        self.finishButton.config(state = "disable")  

    def stopMerging(self):
        self.wantToMerge = False
        self.startButton.config(state = "disable")
        self.cancelButton.config(state = "disable")
        self.finishButton.config(state = "normal")
        self.processState.config(text = "Merge Canceled.")
        if not self.warningMessageLoaded:
            tkMessageBox.showwarning(self.messageBoxTitle, self.cancelWarning)
            self.warningMessageLoaded = True

    def endApplication(self):

    def ifCloseWindow(self):
        if self.mainThread.is_alive():

if __name__ == "__main__":
    tk = Tk()
    app = MergeApp(tk)

How to use it as nautilus script:

paste this code to a file and name it merge.

Now, open a terminal at the directory where you saved file marge and put mv merge ~/.local/share/nautilus/scripts/merge and hit enter.

now put cd ~/.local/share/nautilus/scripts/ and hit enter.

now put chmod +x merge and hit enter.

now go to the root folder where movies and subtitles are and right click on any file or folder then select scripts > merge



Here, I made a script for this. Just make sure there are only two files in your movie folder, one is your movie and other is the subtitle (they don't need to have the same name).

Create a file called merge at Desktop and paste this code:

#!/usr/bin/env python

import subprocess, os, sys

def main():
    movieName = ""
    subtitleName = ""
    for name in os.listdir(os.getcwd()):
        if (name != sys.argv[0]):
            if ".srt" in name:
                subtitleName = name
                movieName = name

    start(movieName, subtitleName)

def start(movieName, subtitleName):
    if (subprocess.call(["mkvmerge", "-o", "merging", movieName, subtitleName]) == 0):
        subprocess.call(["rm", movieName, subtitleName])
        subprocess.call(["mv", "merging", movieName])
        for name in os.listdir(os.getcwd()):
            if name == "merging":
                subprocess.call("rm", "merging")
        print "Sorry, Something went wrong."

if __name__ == "__main__":

Now, open a terminal at the directory where you saved marge (In this case on Desktop) and put mv merge ~/.local/share/nautilus/scripts/merge and hit enter.

now put cd ~/.local/share/nautilus/scripts/ and hit enter.

now put chmod +x merge and hit enter.

now go to the folder where movie and subtitle is and right click on any file then select scripts > merge


If you have any problem, let me know.

enter image description here

  • 1
    Comments are not for extended discussion; this conversation has been moved to chat.
    – Thomas Ward
    Jul 27, 2017 at 12:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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