Code: Select all
#!/usr/bin/env python
#Copyright (C) 2012 Drew Thomson
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
##############################################################################
### NZBGET POST-PROCESSING SCRIPT ###
# mkvdts2ac3.py is a python script for linux, windows or os x which can be used
# for converting the DTS in Matroska (MKV) files to AC3. It provides you with a
# set of options for controlling the resulting file.
##############################################################################
### OPTIONS ###
## These options take an argument.
# NZBGetdestdir.
#
#nzbgetdestdir=
# aaccustom.
#
#aaccustom=
# custom.
#
#custom=
# compress.
#
#compress=
# destdir.
#
#destdir=
# ffmpegpath.
#
#ffmpegpath=
# mkvtoolnixpath
#
#mkvtoolnixpath=
# track.
#
#track=
# wd.
#
#wd=
# verbose.
#
#verbose=
## These options don't take any arguments, they are only True or False
# aac (False, True).
#
#aac=
# aacstereo (False, True).
#
#aacstereo=
# default (False, True).
#
#default=
# external (False, True).
#
#external=
# force (False, True).
#
#force=
# initial (False, True).
#
#initial=
# keepdts (False, True).
#
#keepdts=
# mp4 (False, True).
#
#mp4=
# new (False, True).
#
#new=
# nodts (False, True).
#
#nodts=
# no subtitles (False, True).
#
#no_subtitles=
# no overwrite (False, True).
#
#overwrite=
# recursive (False, True).
#
#recursive=
# test (False, True).
#
#test=
#debug (False, True).
#
#debug=
### NZBGET POST-PROCESSING SCRIPT ###
##############################################################################
import argparse
import os
import subprocess
import time
import glob
import re
import tempfile
import sys
import ConfigParser
import shutil
import hashlib
import textwrap
import errno
version = "1.0"
# check if called from sabnzbdplus
sab = False
if len(sys.argv) == 8:
nzbgroup = sys.argv[6]
ppstatus = sys.argv[7]
if ppstatus.isdigit():
if int(ppstatus) >= 0 and int(ppstatus) <= 3 and "." in nzbgroup:
sab = True
elif os.environ.has_key('NZBOP_SCRIPTDIR') and not os.environ['NZBOP_VERSION'][0:5] < '11.0':
#Logger.info("MAIN: Script triggered from NZBGet (11.0 or later).")
# NZBGet argv: all passed as environment variables.
# Exit codes used by NZBGet
POSTPROCESS_PARCHECK=92
POSTPROCESS_SUCCESS=93
POSTPROCESS_ERROR=94
POSTPROCESS_NONE=95
# Check nzbget.conf options
status = 0
if os.environ['NZBOP_UNPACK'] != 'yes':
#Logger.error("Please enable option \"Unpack\" in nzbget configuration file, exiting")
sys.exit(POSTPROCESS_ERROR)
# Check par status
if os.environ['NZBPP_PARSTATUS'] == '3':
#Logger.warning("Par-check successful, but Par-repair disabled, exiting")
sys.exit(POSTPROCESS_NONE)
if os.environ['NZBPP_PARSTATUS'] == '1':
#Logger.warning("Par-check failed, setting status \"failed\"")
status = 1
# Check unpack status
if os.environ['NZBPP_UNPACKSTATUS'] == '1':
#Logger.warning("Unpack failed, setting status \"failed\"")
status = 1
if os.environ['NZBPP_UNPACKSTATUS'] == '0' and os.environ['NZBPP_PARSTATUS'] != '2':
# Unpack is disabled or was skipped due to nzb-file properties or due to errors during par-check
for dirpath, dirnames, filenames in os.walk(os.environ['NZBPP_DIRECTORY']):
for file in filenames:
fileExtension = os.path.splitext(file)[1]
if fileExtension in ['.rar', '.7z'] or os.path.splitext(fileExtension)[1] in ['.rar', '.7z']:
#Logger.warning("Post-Process: Archive files exist but unpack skipped, setting status \"failed\"")
status = 1
break
if fileExtension in ['.par2']:
#Logger.warning("Post-Process: Unpack skipped and par-check skipped (although par2-files exist), setting status \"failed\"g")
status = 1
break
if os.path.isfile(os.path.join(os.environ['NZBPP_DIRECTORY'], "_brokenlog.txt")) and not status == 1:
#Logger.warning("Post-Process: _brokenlog.txt exists, download is probably damaged, exiting")
status = 1
#if not status == 1:
#Logger.info("Neither archive- nor par2-files found, _brokenlog.txt doesn't exist, considering download successful")
# Check if destination directory exists (important for reprocessing of history items)
if not os.path.isdir(os.environ['NZBPP_DIRECTORY']):
#Logger.error("Post-Process: Nothing to post-process: destination directory %s doesn't exist", os.environ['NZBPP_DIRECTORY'])
status = 1
# All checks done, now launching the script.
ppstatus = status
if ppstatus.isdigit():
if int(ppstatus) >= 0 and int(ppstatus) <= 3:
sab = True
# create parser
parser = argparse.ArgumentParser(description='convert matroska (.mkv) video files audio portion from dts to ac3')
if os.environ.has_key('NZBOP_SCRIPTDIR'): # NZBGet
args = parser.parse_args()
args.fileordir = os.environ['NZBPP_DIRECTORY']
args.aac = os.environ[NZBPO_AAC]
args.aacstereo = os.environ[NZBPO_AACSTEREO]
args.aaccustom = os.environ[NZBPO_AACCUSTOM]
args.custom = os.environ[NZBPO_CUSTOM]
args.default = os.environ[NZBPO_DEFAULT]
args.sabdestdir = os.environ[NZBPO_NZBGETDESTDIR]
args.compress = os.environ[NZBPO_COMPRESS]
args.destdir = os.environ[NZBPO_DESTDIR]
args.ffmpegpath = os.environ[NZBPO_FFMPEGPATH]
args.mkvtoolnixpath = os.environ[NZBPO_MKVTOOLNIXPATH]
args.track = os.environ[NZBPO_TRACK]
args.wd = os.environ[NZBPO_WD]
args.verbose = os.environ[NZBPO_VERBOSE]
args.external = os.environ[NZBPO_EXTERNAL]
args.force = os.environ[NZBPO_FORCE]
args.initial = os.environ[NZBPO_INITIAL]
args.keepdts = os.environ[NZBPO_KEEPDTS
args.mp4 = os.environ[NZBPO_MP4]
args.new = os.environ[NZBPO_NEW]
args.nodts = os.environ[NZBPO_NODTS]
args.no_subtitles = os.environ[NZBPO_NO_SUBTITLES]
args.overwrite = os.environ[NZBPO_OVERWRITE]
args.recursive = os.environ[NZBPO_RECURSIVE]
args.test = os.environ[NZBPO_TEST]
args.debug = os.environ[NZBPO_DEBUG]
if not args.verbose:
args.verbose = 0
else: # default handling
# set config file arguments
configFilename = os.path.join(os.path.dirname(sys.argv[0]), "mkvdts2ac3.cfg")
if os.path.isfile(configFilename):
config = ConfigParser.SafeConfigParser()
config.read(configFilename)
defaults = dict(config.items("mkvdts2ac3"))
for key in defaults:
if key == "verbose":
defaults["verbose"] = int(defaults["verbose"])
parser.set_defaults(**defaults)
parser.add_argument('fileordir', metavar='FileOrDirectory', nargs='+', help='a file or directory (wildcards may be used)')
parser.add_argument("--aac", help="Also add aac track", action="store_true")
parser.add_argument("--aacstereo", help="Make aac track stereo instead of 6 channel", action="store_true")
parser.add_argument("--aaccustom", metavar="TITLE", help="Custom AAC track title")
parser.add_argument("-c", "--custom", metavar="TITLE", help="Custom AC3 track title")
parser.add_argument("-d", "--default", help="Mark AC3 track as default", action="store_true")
parser.add_argument("--destdir", metavar="DIRECTORY", help="Destination Directory")
parser.add_argument("-e", "--external", action="store_true",
help="Leave AC3 track out of file. Does not modify the original matroska file. This overrides '-n' and '-d' arguments")
parser.add_argument("-f", "--force", help="Force processing when AC3 track is detected", action="store_true")
parser.add_argument("--ffmpegpath", metavar="DIRECTORY", help="Path of ffmpeg")
parser.add_argument("-i", "--initial", help="New AC3 track will be first in the file", action="store_true")
parser.add_argument("-k", "--keepdts", help="Keep external DTS track (implies '-n')", action="store_true")
parser.add_argument("--md5", help="check md5 of files before removing the original if destination directory is on a different device than the original file", action="store_true")
parser.add_argument("--mp4", help="create output in mp4 format", action="store_true")
parser.add_argument("--mkvtoolnixpath", metavar="DIRECTORY", help="Path of mkvextract, mkvinfo and mkvmerge")
parser.add_argument("-n", "--nodts", help="Do not retain the DTS track", action="store_true")
parser.add_argument("--new", help="Do not copy over original. Create new adjacent file", action="store_true")
parser.add_argument("--no-subtitles", help="Remove subtitles", action="store_true")
parser.add_argument("-o", "--overwrite", help="Overwrite file if already there. This only applies if destdir or sabdestdir is set", action="store_true")
parser.add_argument("-r", "--recursive", help="Recursively descend into directories", action="store_true")
parser.add_argument("-s", "--compress", metavar="MODE", help="Apply header compression to streams (See mkvmerge's --compression)", default='none')
parser.add_argument("--sabdestdir", metavar="DIRECTORY", help="SABnzbd Destination Directory")
parser.add_argument("--stereo", help="Make ac3 track stereo instead of 6 channel", action="store_true")
parser.add_argument("-t", "--track", metavar="TRACKID", help="Specify alternate DTS track. If it is not a DTS track it will default to the first DTS track found")
parser.add_argument("-w", "--wd", metavar="FOLDER", help="Specify alternate temporary working directory")
parser.add_argument("-v", "--verbose", help="Turn on verbose output. Use more v's for more verbosity. -v will output what it is doing. -vv will also output the command that it is running. -vvv will also output the command output", action="count")
parser.add_argument("-V", "--version", help="Print script version information", action='version', version='%(prog)s ' + version + ' by Drew Thomson')
parser.add_argument("--test", help="Print commands only, execute nothing", action="store_true")
parser.add_argument("--debug", help="Print commands and pause before executing each", action="store_true")
args = parser.parse_args()
if not args.verbose:
args.verbose = 0
def winexe(program):
if sys.platform == "win32" and not program.endswith(".exe"):
program += ".exe"
return program
# set ffmpeg and mkvtoolnix paths
if args.mkvtoolnixpath:
mkvinfo = os.path.join(args.mkvtoolnixpath, "mkvinfo")
mkvinfo = winexe(mkvinfo)
mkvmerge = os.path.join(args.mkvtoolnixpath, "mkvmerge")
mkvmerge = winexe(mkvmerge)
mkvextract = os.path.join(args.mkvtoolnixpath, "mkvextract")
mkvextract = winexe(mkvextract)
if not args.mkvtoolnixpath or not os.path.exists(mkvinfo):
mkvinfo = "mkvinfo"
if not args.mkvtoolnixpath or not os.path.exists(mkvmerge):
mkvmerge = "mkvmerge"
if not args.mkvtoolnixpath or not os.path.exists(mkvextract):
mkvextract = "mkvextract"
if args.ffmpegpath:
ffmpeg = os.path.join(args.ffmpegpath, "ffmpeg")
ffmpeg = winexe(ffmpeg)
if not args.ffmpegpath or not os.path.exists(ffmpeg):
ffmpeg = "ffmpeg"
# check paths
def which(program):
if sys.platform == "win32" and not program.endswith(".exe"):
program += ".exe"
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath = os.path.split(program)[0]
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
missingprereqs = False
missinglist = []
if not which(mkvextract):
missingprereqs = True
missinglist.append("mkvextract")
if not which(mkvinfo):
missingprereqs = True
missinglist.append("mkvinfo")
if not which(mkvmerge):
missingprereqs = True
missinglist.append("mkvmerge")
if not which(ffmpeg):
missingprereqs = True
missinglist.append("ffmpeg")
if missingprereqs:
sys.stdout.write("You are missing the following prerequisite tools: ")
for tool in missinglist:
sys.stdout.write(tool + " ")
if not args.mkvtoolnixpath and not args.ffmpegpath:
print "\nYou can use --mkvtoolnixpath and --ffmpegpath to specify the path"
else:
print
sys.exit(1)
if not args.verbose:
args.verbose = 0
if args.verbose < 2 and (args.test or args.debug):
args.verbose = 2
if sab:
args.fileordir = [args.fileordir[0]]
args.verbose = 3
if args.debug and args.verbose == 0:
args.verbose = 1
def doprint(mystr, v):
if args.verbose >= v:
sys.stdout.write(mystr)
def silentremove(filename):
try:
os.remove(filename)
except OSError, e:
if e.errno != errno.ENOENT: # errno.ENOENT = no such file or directory
raise # re-raise exception if a different error occured
def elapsedstr(starttime):
elapsed = (time.time() - starttime)
minutes = int(elapsed / 60)
mplural = 's'
if minutes == 1:
mplural = ''
seconds = int(elapsed) % 60
splural = 's'
if seconds == 1:
splural = ''
return str(minutes) + " minute" + mplural + " " + str(seconds) + " second" + splural
def getduration(time):
(hms, ms) = time.split('.')
(h, m, s) = hms.split(':')
totalms = int(ms) + (int(s) * 100) + (int(m) * 100 * 60) + (int(h) * 100 * 60 * 60)
return totalms
def runcommand(title, cmdlist):
if args.debug:
raw_input("Press Enter to continue...")
cmdstarttime = time.time()
if args.verbose >= 1:
sys.stdout.write(title)
if args.verbose >= 2:
cmdstr = ''
for e in cmdlist:
cmdstr += e + ' '
print
print " Running command:"
print textwrap.fill(cmdstr.rstrip(), initial_indent=' ', subsequent_indent=' ')
if not args.test:
if args.verbose >= 3:
subprocess.call(cmdlist)
elif args.verbose >= 1:
if "ffmpeg" in cmdlist[0]:
proc = subprocess.Popen(cmdlist, stderr=subprocess.PIPE)
line = ''
duration_regex = re.compile(" Duration: (\d+:\d\d:\d\d\.\d\d),")
progress_regex = re.compile("size= +\d+.*time=(\d+:\d\d:\d\d\.\d\d) bitrate=")
duration = False
while True:
if not duration:
durationline = proc.stderr.readline()
match = duration_regex.match(durationline)
if match:
duration = getduration(match.group(1))
else:
out = proc.stderr.read(1)
if out == '' and proc.poll() != None:
break
if out != '\r':
line += out
else:
if 'size= ' in line:
match = progress_regex.search(line)
if match:
percentage = int(float(getduration(match.group(1)) / float(duration)) * 100)
if percentage > 100:
percentage = 100
sys.stdout.write("\r" + title + str(percentage) + '%')
line = ''
sys.stdout.flush()
print "\r" + title + elapsedstr(cmdstarttime)
else:
proc = subprocess.Popen(cmdlist, stdout=subprocess.PIPE)
line = ''
progress_regex = re.compile("Progress: (\d+%)")
while True:
out = proc.stdout.read(1)
if out == '' and proc.poll() != None:
break
if out != '\r':
line += out
else:
if 'Progress: ' in line:
match = progress_regex.search(line)
if match:
percentage = match.group(1)
sys.stdout.write("\r" + title + percentage)
line = ''
sys.stdout.flush()
print "\r" + title + elapsedstr(cmdstarttime)
else:
subprocess.call(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def find_mount_point(path):
path = os.path.abspath(path)
while not os.path.ismount(path):
path = os.path.dirname(path)
return path
def getmd5(fname, block_size=2**12):
md5 = hashlib.md5()
with open(fname, 'rb') as f:
while True:
data = f.read(block_size)
if not data:
break
md5.update(data)
doprint(fname + ": " + md5.hexdigest() + "\n", 3)
return md5.hexdigest()
def check_md5tree(orig, dest):
rt = True
orig = os.path.abspath(orig)
dest = os.path.abspath(dest)
for ofile in os.listdir(orig):
if rt == True:
if os.path.isdir(os.path.join(orig, ofile)):
doprint("dir: " + os.path.join(orig, ofile) + "\n", 3)
odir = os.path.join(orig, ofile)
ddir = os.path.join(dest, ofile)
rt = check_md5tree(odir, ddir)
else:
doprint("file: " + os.path.join(orig, ofile) + "\n", 3)
if getmd5(os.path.join(orig, ofile)) != getmd5(os.path.join(dest, ofile)):
rt = False
return rt
def process(ford):
if os.path.isdir(ford):
doprint(" Processing dir: " + ford + "\n", 3)
if args.recursive:
for f in os.listdir(ford):
process(os.path.join(ford, f))
else:
doprint(" Processing file: " + ford + "\n", 3)
# check if file is an mkv file
child = subprocess.Popen([mkvmerge, "-i", ford], stdout=subprocess.PIPE)
child.communicate()[0]
if child.returncode == 0:
starttime = time.time()
# set up temp dir
tempdir = False
if args.wd:
tempdir = args.wd
if not os.path.exists(tempdir):
os.makedirs(tempdir)
else:
tempdir = tempfile.mkdtemp()
tempdir = os.path.join(tempdir, "mkvdts2ac3")
(dirName, fileName) = os.path.split(ford)
fileBaseName = os.path.splitext(fileName)[0]
doprint("filename: " + fileName + "\n", 1)
dtsfile = fileBaseName + '.dts'
tempdtsfile = os.path.join(tempdir, dtsfile)
ac3file = fileBaseName + '.ac3'
tempac3file = os.path.join(tempdir, ac3file)
aacfile = fileBaseName + '.aac'
tempaacfile = os.path.join(tempdir, aacfile)
tcfile = fileBaseName + '.tc'
temptcfile = os.path.join(tempdir, tcfile)
newmkvfile = fileBaseName + '.mkv'
tempnewmkvfile = os.path.join(tempdir, newmkvfile)
adjacentmkvfile = os.path.join(dirName, fileBaseName + '.new.mkv')
mp4file = os.path.join(dirName, fileBaseName + '.mp4')
fname = fileName
# get dts track id and video track id
output = subprocess.check_output([mkvmerge, "-i", ford])
lines = output.split("\n")
altdtstrackid = False
dtstrackid = False
videotrackid = False
alreadygotac3 = False
audiotracks = []
for line in lines:
linelist = line.split(' ')
trackid = False
if len(linelist) > 2:
trackid = linelist[2]
linelist = trackid.split(':')
trackid = linelist[0]
if 'audio (A_' in line:
audiotracks.append(trackid)
if ': audio (A_DTS)' in line:
dtstrackid = trackid
elif 'video (V_' in line:
videotrackid = trackid
elif ': audio (A_AC3)' in line:
alreadygotac3 = True
if args.track:
if "Track ID " + args.track + ": audio (A_DTS)" in line:
altdtstrackid = args.track
if altdtstrackid:
dtstrackid = altdtstrackid
if not dtstrackid:
doprint(" No DTS track found\n", 1)
elif alreadygotac3 and not args.force:
doprint(" Already has AC3 track\n", 1)
else:
# get dtstrack info
output = subprocess.check_output([mkvinfo, ford])
lines = output.split("\n")
dtstrackinfo = []
startcount = 0
for line in lines:
match = re.search(r'^\|( *)\+', line)
linespaces = startcount
if match:
linespaces = len(match.group(1))
if startcount == 0:
if "track ID for mkvmerge & mkvextract:" in line:
if "track ID for mkvmerge & mkvextract: " + dtstrackid in line:
startcount = linespaces
elif "+ Track number: " + dtstrackid in line:
startcount = linespaces
if linespaces < startcount:
break
if startcount != 0:
dtstrackinfo.append(line)
# get dts language
dtslang = "eng"
for line in dtstrackinfo:
if "Language" in line:
dtslang = line.split()[-1]
# get ac3 track name
ac3name = False
if args.custom:
ac3name = args.custom
else:
for line in dtstrackinfo:
if "+ Name: " in line:
ac3name = line.split("+ Name: ")[-1]
ac3name = ac3name.replace("DTS", "AC3")
ac3name = ac3name.replace("dts", "ac3")
if args.stereo:
ac3name = ac3name.replace("5.1", "Stereo")
# get aac track name
aacname = False
if args.aaccustom:
aacname = args.aaccustom
else:
for line in dtstrackinfo:
if "+ Name: " in line:
aacname = line.split("+ Name: ")[-1]
aacname = aacname.replace("DTS", "AAC")
aacname = aacname.replace("dts", "aac")
if args.aacstereo:
aacname = aacname.replace("5.1", "Stereo")
totaljobs = 4
jobnum = 1
if args.aac:
totaljobs += 1
if args.mp4:
totaljobs += 1
# extract timecodes
tctitle = " Extracting Timecodes [" + str(jobnum) + "/" + str(totaljobs) + "]..."
jobnum += 1
tccmd = [mkvextract, "timecodes_v2", ford, dtstrackid + ":" + temptcfile]
runcommand(tctitle, tccmd)
delay = False
if not args.test:
# get the delay if there is any
fp = open(temptcfile)
for i, line in enumerate(fp):
if i == 1:
delay = line
break
fp.close()
# extract dts track
extracttitle = " Extracting DTS track [" + str(jobnum) + "/" + str(totaljobs) + "]..."
jobnum += 1
extractcmd = [mkvextract, "tracks", ford, dtstrackid + ':' + tempdtsfile]
runcommand(extracttitle, extractcmd)
# convert DTS to AC3
converttitle = " Converting DTS to AC3 [" + str(jobnum) + "/" + str(totaljobs) + "]..."
jobnum += 1
audiochannels = 6
if args.stereo:
audiochannels = 2
convertcmd = [ffmpeg, "-y", "-i", tempdtsfile, "-acodec", "ac3", "-ac", str(audiochannels), "-ab", "448k", tempac3file]
runcommand(converttitle, convertcmd)
if args.aac:
converttitle = " Converting DTS to AAC [" + str(jobnum) + "/" + str(totaljobs) + "]..."
jobnum += 1
audiochannels = 6
if args.aacstereo:
audiochannels = 2
convertcmd = [ffmpeg, "-y", "-i", tempdtsfile, "-acodec", "libfaac", "-ac", str(audiochannels), "-ab", "448k", tempaacfile]
runcommand(converttitle, convertcmd)
if not os.path.isfile(tempaacfile) or os.path.getsize(tempaacfile) == 0:
convertcmd = [ffmpeg, "-y", "-i", tempdtsfile, "-acodec", "libvo_aacenc", "-ac", str(audiochannels), "-ab", "448k", tempaacfile]
runcommand(converttitle, convertcmd)
if not os.path.isfile(tempaacfile) or os.path.getsize(tempaacfile) == 0:
convertcmd = [ffmpeg, "-y", "-i", tempdtsfile, "-acodec", "aac", "-strict", "experimental", "-ac", str(audiochannels), "-ab", "448k", tempaacfile]
runcommand(converttitle, convertcmd)
if not os.path.isfile(tempaacfile) or os.path.getsize(tempaacfile) == 0:
args.aac = False
print "ERROR: ffmpeg can't use any aac codecs. Please try to get libfaac, libvo_aacenc, or a newer version of ffmpeg with the experimental aac codec installed"
if args.external:
if not args.test:
shutil.move(tempac3file, os.path.join(dirName, fileBaseName + '.ac3'))
fname = ac3file
else:
# remux
remuxtitle = " Remuxing AC3 into MKV [" + str(jobnum) + "/" + str(totaljobs) + "]..."
jobnum += 1
# Start to "build" command
remux = [mkvmerge]
# Remove subtitles
if args.no_subtitles:
remux.append("--no-subtitles")
# Puts the AC3 track as the second in the file if indicated as initial
if args.initial:
remux.append("--track-order")
if args.aac:
remux.append("1:0,2:0")
else:
remux.append("1:0")
# If user doesn't want the original DTS track drop it
comp = args.compress
if args.nodts or args.keepdts:
if len(audiotracks) == 1:
remux.append("-A")
else:
audiotracks = [audiotrack for audiotrack in audiotracks if audiotrack != dtstrackid]
remux.append("-a")
remux.append(",".join(audiotracks))
for tid in audiotracks:
remux.append("--compression")
remux.append(tid + ":" + comp)
# Add original MKV file, set header compression scheme
remux.append("--compression")
remux.append(videotrackid + ":" + comp)
remux.append(ford)
# If user wants new AC3 as default then add appropriate arguments to command
if args.default:
remux.append("--default-track")
remux.append("0:0")
# Set the language
remux.append("--language")
remux.append("0:" + dtslang)
# If the name was set for the original DTS track set it for the AC3
if ac3name:
remux.append("--track-name")
remux.append("0:\"" + ac3name.rstrip() + "\"")
# set delay if there is any
if delay:
remux.append("--sync")
remux.append("0:" + delay.rstrip())
# Set track compression scheme and append new AC3
remux.append("--compression")
remux.append("0:" + comp)
remux.append(tempac3file)
if args.aac:
# If the name was set for the original DTS track set it for the AAC
if aacname:
remux.append("--track-name")
remux.append("0:\"" + aacname.rstrip() + "\"")
# Set track compression scheme and append new AAC
remux.append("--compression")
remux.append("0:" + comp)
remux.append(tempaacfile)
# Declare output file
remux.append("-o")
remux.append(tempnewmkvfile)
runcommand(remuxtitle, remux)
if not args.test:
if args.mp4:
converttitle = " Converting MKV to MP4 [" + str(jobnum) + "/" + str(totaljobs) + "]..."
convertcmd = [ffmpeg, "-i", tempnewmkvfile, "-map", "0", "-vcodec", "copy", "-acodec", "copy", "-c:s", "mov_text", mp4file]
runcommand(converttitle, convertcmd)
silentremove(ford)
else:
#~ replace old mkv with new mkv
if args.new:
shutil.move(tempnewmkvfile, adjacentmkvfile)
else:
silentremove(ford)
shutil.move(tempnewmkvfile, ford)
if not args.test:
#~ clean up temp folder
if args.keepdts and not args.external:
shutil.move(tempdtsfile, os.path.join(dirName, fileBaseName + ".dts"))
fname = dtsfile
else:
silentremove(tempdtsfile)
if not args.external:
silentremove(tempac3file)
silentremove(tempaacfile)
silentremove(temptcfile)
if not os.listdir(tempdir):
os.rmdir(tempdir)
#~ print out time taken
elapsed = (time.time() - starttime)
minutes = int(elapsed / 60)
seconds = int(elapsed) % 60
doprint(" " + fileName + " finished in: " + str(minutes) + " minutes " + str(seconds) + " seconds\n", 1)
return fname
totalstime = time.time()
for a in args.fileordir:
for ford in glob.glob(a):
fname = False
if os.path.isdir(ford):
for f in os.listdir(ford):
process(os.path.join(ford, f))
else:
fname = process(ford)
destdir = False
if args.destdir:
destdir = args.destdir
if sab and args.sabdestdir:
destdir = args.sabdestdir
if destdir:
if fname:
(dirName, fileName) = os.path.split(ford)
destfile = os.path.join(destdir, fname)
origfile = os.path.join(dirName, fname)
if args.md5 and (find_mount_point(dirName) != find_mount_point(destdir)):
if os.path.exists(destfile):
if args.overwrite:
silentremove(destfile)
shutil.copyfile(origfile, destfile)
if getmd5(origfile) == getmd5(destfile):
silentremove(origfile)
else:
print "MD5's don't match."
else:
print "File " + destfile + " already exists"
else:
doprint("copying: " + origfile + " --> " + destfile + "\n", 3)
shutil.copyfile(origfile, destfile)
if getmd5(origfile) == getmd5(destfile):
silentremove(origfile)
else:
print "MD5's don't match."
else:
if os.path.exists(destfile):
if args.overwrite:
silentremove(destfile)
shutil.move(origfile, destfile)
else:
print "File " + destfile + " already exists"
else:
shutil.move(origfile, destfile)
else:
origpath = os.path.abspath(ford)
destpath = os.path.join(destdir, os.path.basename(os.path.normpath(ford)))
if args.md5 and (find_mount_point(origpath) != find_mount_point(destpath)):
if os.path.exists(destpath) and args.overwrite:
shutil.rmtree(destpath)
elif os.path.exists(destpath):
print "Directory " + destpath + " already exists"
else:
shutil.copytree(origpath, destpath)
if check_md5tree(origpath, destpath):
shutil.rmtree(origpath)
else:
print "MD5's don't match."
else:
shutil.move(origpath, destpath)
if sab:
sys.stdout.write("mkv dts -> ac3 conversion: " + elapsedstr(totalstime))
else:
doprint("Total Time: " + elapsedstr(totalstime), 1)
Code: Select all
error Wed Dec 04 2013 11:07:32 Post-process-script mkvdts2ac3/mkvdts2ac3.py for dts sample failed (terminated with unknown status)
info Wed Dec 04 2013 11:07:32 mkvdts2ac3: SyntaxError: invalid syntax
info Wed Dec 04 2013 11:07:32 mkvdts2ac3: ^
info Wed Dec 04 2013 11:07:32 mkvdts2ac3: args.mp4 = os.environ[NZBPO_MP4]
info Wed Dec 04 2013 11:07:32 mkvdts2ac3: File "/mnt/disk1/share/nzbget/ppscripts/mkvdts2ac3/mkvdts2ac3.py", line 250
info Wed Dec 04 2013 11:07:31 Executing post-process-script mkvdts2ac3/mkvdts2ac3.py for dts sample
Code: Select all
root@Nas:~# /mnt/disk1/share/nzbget/ppscripts_backup/mkvdts2ac3/mkvdts2ac3.py -n --stereo -w /mnt/disk1/share/test/ -vvv /mnt/disk1/share/test/dts/dts.mkv
Processing file: /mnt/disk1/share/test/dts/dts.mkv
filename: dts.mkv
Extracting Timecodes [1/4]...
Running command:
mkvextract timecodes_v2 /mnt/disk1/share/test/dts/dts.mkv
2:/mnt/disk1/share/test/dts.tc
Progress: 100%
Extracting DTS track [2/4]...
Running command:
mkvextract tracks /mnt/disk1/share/test/dts/dts.mkv
2:/mnt/disk1/share/test/dts.dts
Extracting track 2 with the CodecID 'A_DTS' to the file '/mnt/disk1/share/test/dts.dts'. Container format: Digital Theater System (DTS)
Progress: 100%
Converting DTS to AC3 [3/4]...
Running command:
ffmpeg -y -i /mnt/disk1/share/test/dts.dts -acodec ac3 -ac 2 -ab 448k
/mnt/disk1/share/test/dts.ac3
FFmpeg version UNKNOWN, Copyright (c) 2000-2008 Fabrice Bellard, et al.
configuration: --enable-cross-compile --cross-prefix=/home/slug/optware/cs08q1armel/toolchain/arm-2008q1/bin/arm-none-linux-gnueabi- --arch=arm --disable-encoder=snow --disable-decoder=snow --enable-shared --disable-static --enable-gpl --enable-postproc --prefix=/opt
libavutil version: 49.6.0
libavcodec version: 51.54.0
libavformat version: 52.13.0
libavdevice version: 52.0.0
built on Nov 23 2010 22:02:29, gcc: 4.2.3
Input #0, dts, from '/mnt/disk1/share/test/dts.dts':
Duration: 00:00:49.3, bitrate: 1536 kb/s
Stream #0.0: Audio: dca, 48000 Hz, 5:1, 1536 kb/s
Output #0, ac3, to '/mnt/disk1/share/test/dts.ac3':
Stream #0.0: Audio: ac3, 48000 Hz, stereo, 448 kb/s
Stream mapping:
Stream #0.0 -> #0.0
Press [q] to stop encoding
size= 2748kB time=50.2 bitrate= 448.0kbits/s
video:0kB audio:2748kB global headers:0kB muxing overhead 0.000000%
Remuxing AC3 into MKV [4/4]...
Running command:
mkvmerge -A --compression 1:none /mnt/disk1/share/test/dts/dts.mkv
--language 0:eng --track-name
0:"Antiviral.2012.BluRay.720p.AC3.x264-CHD" --sync 0:6 --compression
0:none /mnt/disk1/share/test/dts.ac3 -o /mnt/disk1/share/test/dts.mkv
mkvmerge v4.2.0 ('No Talking') built on Feb 14 2012 14:16:48
'/mnt/disk1/share/test/dts/dts.mkv': Using the Matroska demultiplexer.
'/mnt/disk1/share/test/dts.ac3': Using the AC3 demultiplexer.
'/mnt/disk1/share/test/dts/dts.mkv' track 1: Using the MPEG-4 part 10 (AVC) video output module.
'/mnt/disk1/share/test/dts.ac3' track 0: Using the AC3 output module.
The file '/mnt/disk1/share/test/dts.mkv' has been opened for writing.
Progress: 100%
The cue entries (the index) are being written...
Muxing took 30 seconds.
dts.mkv finished in: 3 minutes 13 seconds
Total Time: 3 minutes 14 secondsroot@Nas:~#
Code: Select all
os.environ[NZBPO_AAC]
Code: Select all
os.environ['NZBPO_AAC']
Code: Select all
#!/usr/bin/env python
#Copyright (C) 2012 Drew Thomson
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
##############################################################################
### NZBGET POST-PROCESSING SCRIPT ###
# mkvdts2ac3.py is a python script for linux, windows or os x which can be used
# for converting the DTS in Matroska (MKV) files to AC3. It provides you with a
# set of options for controlling the resulting file.
##############################################################################
### OPTIONS ###
## These options take an argument.
# NZBGetdestdir.
#
#nzbgetdestdir=
# aaccustom.
#
#aaccustom=
# custom.
#
#custom=
# compress.
#
#compress=
# destdir.
#
#destdir=
# ffmpegpath.
#
#ffmpegpath=
# mkvtoolnixpath
#
#mkvtoolnixpath=
# track.
#
#track=
# wd.
#
#wd=
# verbose.
#
#verbose=
## These options don't take any arguments, they are only True or False
# aac (False, True).
#
#aac=
# aacstereo (False, True).
#
#aacstereo=
# default (False, True).
#
#default=
# external (False, True).
#
#external=
# force (False, True).
#
#force=
# initial (False, True).
#
#initial=
# keepdts (False, True).
#
#keepdts=
# mp4 (False, True).
#
#mp4=
# new (False, True).
#
#new=
# nodts (False, True).
#
#nodts=
# no subtitles (False, True).
#
#no_subtitles=
# no overwrite (False, True).
#
#overwrite=
# recursive (False, True).
#
#recursive=
# test (False, True).
#
#test=
#debug (False, True).
#
#debug=
### NZBGET POST-PROCESSING SCRIPT ###
##############################################################################
import argparse
import os
import subprocess
import time
import glob
import re
import tempfile
import sys
import ConfigParser
import shutil
import hashlib
import textwrap
import errno
version = "1.0"
# check if called from sabnzbdplus
sab = False
if len(sys.argv) == 8:
nzbgroup = sys.argv[6]
ppstatus = sys.argv[7]
if ppstatus.isdigit():
if int(ppstatus) >= 0 and int(ppstatus) <= 3 and "." in nzbgroup:
sab = True
elif os.environ.has_key('NZBOP_SCRIPTDIR') and not os.environ['NZBOP_VERSION'][0:5] < '11.0':
#Logger.info("MAIN: Script triggered from NZBGet (11.0 or later).")
# NZBGet argv: all passed as environment variables.
# Exit codes used by NZBGet
POSTPROCESS_PARCHECK=92
POSTPROCESS_SUCCESS=93
POSTPROCESS_ERROR=94
POSTPROCESS_NONE=95
# Check nzbget.conf options
status = 0
if os.environ['NZBOP_UNPACK'] != 'yes':
#Logger.error("Please enable option \"Unpack\" in nzbget configuration file, exiting")
sys.exit(POSTPROCESS_ERROR)
# Check par status
if os.environ['NZBPP_PARSTATUS'] == '3':
#Logger.warning("Par-check successful, but Par-repair disabled, exiting")
sys.exit(POSTPROCESS_NONE)
if os.environ['NZBPP_PARSTATUS'] == '1':
#Logger.warning("Par-check failed, setting status \"failed\"")
status = 1
# Check unpack status
if os.environ['NZBPP_UNPACKSTATUS'] == '1':
#Logger.warning("Unpack failed, setting status \"failed\"")
status = 1
if os.environ['NZBPP_UNPACKSTATUS'] == '0' and os.environ['NZBPP_PARSTATUS'] != '2':
# Unpack is disabled or was skipped due to nzb-file properties or due to errors during par-check
for dirpath, dirnames, filenames in os.walk(os.environ['NZBPP_DIRECTORY']):
for file in filenames:
fileExtension = os.path.splitext(file)[1]
if fileExtension in ['.rar', '.7z'] or os.path.splitext(fileExtension)[1] in ['.rar', '.7z']:
#Logger.warning("Post-Process: Archive files exist but unpack skipped, setting status \"failed\"")
status = 1
break
if fileExtension in ['.par2']:
#Logger.warning("Post-Process: Unpack skipped and par-check skipped (although par2-files exist), setting status \"failed\"g")
status = 1
break
if os.path.isfile(os.path.join(os.environ['NZBPP_DIRECTORY'], "_brokenlog.txt")) and not status == 1:
#Logger.warning("Post-Process: _brokenlog.txt exists, download is probably damaged, exiting")
status = 1
#if not status == 1:
#Logger.info("Neither archive- nor par2-files found, _brokenlog.txt doesn't exist, considering download successful")
# Check if destination directory exists (important for reprocessing of history items)
if not os.path.isdir(os.environ['NZBPP_DIRECTORY']):
#Logger.error("Post-Process: Nothing to post-process: destination directory %s doesn't exist", os.environ['NZBPP_DIRECTORY'])
status = 1
# All checks done, now launching the script.
ppstatus = status
if ppstatus.isdigit():
if int(ppstatus) >= 0 and int(ppstatus) <= 3:
sab = True
# create parser
parser = argparse.ArgumentParser(description='convert matroska (.mkv) video files audio portion from dts to ac3')
if os.environ.has_key('NZBOP_SCRIPTDIR'): # NZBGet
args = parser.parse_args()
args.fileordir = os.environ['NZBPP_DIRECTORY']
args.aac = os.environ['NZBPO_AAC']
args.aacstereo = os.environ['NZBPO_AACSTEREO']
args.aaccustom = os.environ['NZBPO_AACCUSTOM']
args.custom = os.environ['NZBPO_CUSTOM']
args.default = os.environ['NZBPO_DEFAULT']
args.sabdestdir = os.environ['NZBPO_NZBGETDESTDIR']
args.compress = os.environ['NZBPO_COMPRESS']
args.destdir = os.environ['NZBPO_DESTDIR']
args.ffmpegpath = os.environ['NZBPO_FFMPEGPATH']
args.mkvtoolnixpath = os.environ['NZBPO_MKVTOOLNIXPATH']
args.track = os.environ['NZBPO_TRACK']
args.wd = os.environ['NZBPO_WD']
args.verbose = os.environ['NZBPO_VERBOSE']
args.external = os.environ['NZBPO_EXTERNAL']
args.force = os.environ['NZBPO_FORCE']
args.initial = os.environ['NZBPO_INITIAL']
args.keepdts = os.environ['NZBPO_KEEPDTS']
args.mp4 = os.environ['NZBPO_MP4']
args.new = os.environ['NZBPO_NEW']
args.nodts = os.environ['NZBPO_NODTS']
args.no_subtitles = os.environ['NZBPO_NO_SUBTITLES']
args.overwrite = os.environ['NZBPO_OVERWRITE']
args.recursive = os.environ['NZBPO_RECURSIVE']
args.test = os.environ['NZBPO_TEST']
args.debug = os.environ['NZBPO_DEBUG']
if not args.verbose:
args.verbose = 0
else: # default handling
# set config file arguments
configFilename = os.path.join(os.path.dirname(sys.argv[0]), "mkvdts2ac3.cfg")
if os.path.isfile(configFilename):
config = ConfigParser.SafeConfigParser()
config.read(configFilename)
defaults = dict(config.items("mkvdts2ac3"))
for key in defaults:
if key == "verbose":
defaults["verbose"] = int(defaults["verbose"])
parser.set_defaults(**defaults)
parser.add_argument('fileordir', metavar='FileOrDirectory', nargs='+', help='a file or directory (wildcards may be used)')
parser.add_argument("--aac", help="Also add aac track", action="store_true")
parser.add_argument("--aacstereo", help="Make aac track stereo instead of 6 channel", action="store_true")
parser.add_argument("--aaccustom", metavar="TITLE", help="Custom AAC track title")
parser.add_argument("-c", "--custom", metavar="TITLE", help="Custom AC3 track title")
parser.add_argument("-d", "--default", help="Mark AC3 track as default", action="store_true")
parser.add_argument("--destdir", metavar="DIRECTORY", help="Destination Directory")
parser.add_argument("-e", "--external", action="store_true",
help="Leave AC3 track out of file. Does not modify the original matroska file. This overrides '-n' and '-d' arguments")
parser.add_argument("-f", "--force", help="Force processing when AC3 track is detected", action="store_true")
parser.add_argument("--ffmpegpath", metavar="DIRECTORY", help="Path of ffmpeg")
parser.add_argument("-i", "--initial", help="New AC3 track will be first in the file", action="store_true")
parser.add_argument("-k", "--keepdts", help="Keep external DTS track (implies '-n')", action="store_true")
parser.add_argument("--md5", help="check md5 of files before removing the original if destination directory is on a different device than the original file", action="store_true")
parser.add_argument("--mp4", help="create output in mp4 format", action="store_true")
parser.add_argument("--mkvtoolnixpath", metavar="DIRECTORY", help="Path of mkvextract, mkvinfo and mkvmerge")
parser.add_argument("-n", "--nodts", help="Do not retain the DTS track", action="store_true")
parser.add_argument("--new", help="Do not copy over original. Create new adjacent file", action="store_true")
parser.add_argument("--no-subtitles", help="Remove subtitles", action="store_true")
parser.add_argument("-o", "--overwrite", help="Overwrite file if already there. This only applies if destdir or sabdestdir is set", action="store_true")
parser.add_argument("-r", "--recursive", help="Recursively descend into directories", action="store_true")
parser.add_argument("-s", "--compress", metavar="MODE", help="Apply header compression to streams (See mkvmerge's --compression)", default='none')
parser.add_argument("--sabdestdir", metavar="DIRECTORY", help="SABnzbd Destination Directory")
parser.add_argument("--stereo", help="Make ac3 track stereo instead of 6 channel", action="store_true")
parser.add_argument("-t", "--track", metavar="TRACKID", help="Specify alternate DTS track. If it is not a DTS track it will default to the first DTS track found")
parser.add_argument("-w", "--wd", metavar="FOLDER", help="Specify alternate temporary working directory")
parser.add_argument("-v", "--verbose", help="Turn on verbose output. Use more v's for more verbosity. -v will output what it is doing. -vv will also output the command that it is running. -vvv will also output the command output", action="count")
parser.add_argument("-V", "--version", help="Print script version information", action='version', version='%(prog)s ' + version + ' by Drew Thomson')
parser.add_argument("--test", help="Print commands only, execute nothing", action="store_true")
parser.add_argument("--debug", help="Print commands and pause before executing each", action="store_true")
args = parser.parse_args()
if not args.verbose:
args.verbose = 0
def winexe(program):
if sys.platform == "win32" and not program.endswith(".exe"):
program += ".exe"
return program
# set ffmpeg and mkvtoolnix paths
if args.mkvtoolnixpath:
mkvinfo = os.path.join(args.mkvtoolnixpath, "mkvinfo")
mkvinfo = winexe(mkvinfo)
mkvmerge = os.path.join(args.mkvtoolnixpath, "mkvmerge")
mkvmerge = winexe(mkvmerge)
mkvextract = os.path.join(args.mkvtoolnixpath, "mkvextract")
mkvextract = winexe(mkvextract)
if not args.mkvtoolnixpath or not os.path.exists(mkvinfo):
mkvinfo = "mkvinfo"
if not args.mkvtoolnixpath or not os.path.exists(mkvmerge):
mkvmerge = "mkvmerge"
if not args.mkvtoolnixpath or not os.path.exists(mkvextract):
mkvextract = "mkvextract"
if args.ffmpegpath:
ffmpeg = os.path.join(args.ffmpegpath, "ffmpeg")
ffmpeg = winexe(ffmpeg)
if not args.ffmpegpath or not os.path.exists(ffmpeg):
ffmpeg = "ffmpeg"
# check paths
def which(program):
if sys.platform == "win32" and not program.endswith(".exe"):
program += ".exe"
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath = os.path.split(program)[0]
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
missingprereqs = False
missinglist = []
if not which(mkvextract):
missingprereqs = True
missinglist.append("mkvextract")
if not which(mkvinfo):
missingprereqs = True
missinglist.append("mkvinfo")
if not which(mkvmerge):
missingprereqs = True
missinglist.append("mkvmerge")
if not which(ffmpeg):
missingprereqs = True
missinglist.append("ffmpeg")
if missingprereqs:
sys.stdout.write("You are missing the following prerequisite tools: ")
for tool in missinglist:
sys.stdout.write(tool + " ")
if not args.mkvtoolnixpath and not args.ffmpegpath:
print "\nYou can use --mkvtoolnixpath and --ffmpegpath to specify the path"
else:
print
sys.exit(1)
if not args.verbose:
args.verbose = 0
if args.verbose < 2 and (args.test or args.debug):
args.verbose = 2
if sab:
args.fileordir = [args.fileordir[0]]
args.verbose = 3
if args.debug and args.verbose == 0:
args.verbose = 1
def doprint(mystr, v):
if args.verbose >= v:
sys.stdout.write(mystr)
def silentremove(filename):
try:
os.remove(filename)
except OSError, e:
if e.errno != errno.ENOENT: # errno.ENOENT = no such file or directory
raise # re-raise exception if a different error occured
def elapsedstr(starttime):
elapsed = (time.time() - starttime)
minutes = int(elapsed / 60)
mplural = 's'
if minutes == 1:
mplural = ''
seconds = int(elapsed) % 60
splural = 's'
if seconds == 1:
splural = ''
return str(minutes) + " minute" + mplural + " " + str(seconds) + " second" + splural
def getduration(time):
(hms, ms) = time.split('.')
(h, m, s) = hms.split(':')
totalms = int(ms) + (int(s) * 100) + (int(m) * 100 * 60) + (int(h) * 100 * 60 * 60)
return totalms
def runcommand(title, cmdlist):
if args.debug:
raw_input("Press Enter to continue...")
cmdstarttime = time.time()
if args.verbose >= 1:
sys.stdout.write(title)
if args.verbose >= 2:
cmdstr = ''
for e in cmdlist:
cmdstr += e + ' '
print
print " Running command:"
print textwrap.fill(cmdstr.rstrip(), initial_indent=' ', subsequent_indent=' ')
if not args.test:
if args.verbose >= 3:
subprocess.call(cmdlist)
elif args.verbose >= 1:
if "ffmpeg" in cmdlist[0]:
proc = subprocess.Popen(cmdlist, stderr=subprocess.PIPE)
line = ''
duration_regex = re.compile(" Duration: (\d+:\d\d:\d\d\.\d\d),")
progress_regex = re.compile("size= +\d+.*time=(\d+:\d\d:\d\d\.\d\d) bitrate=")
duration = False
while True:
if not duration:
durationline = proc.stderr.readline()
match = duration_regex.match(durationline)
if match:
duration = getduration(match.group(1))
else:
out = proc.stderr.read(1)
if out == '' and proc.poll() != None:
break
if out != '\r':
line += out
else:
if 'size= ' in line:
match = progress_regex.search(line)
if match:
percentage = int(float(getduration(match.group(1)) / float(duration)) * 100)
if percentage > 100:
percentage = 100
sys.stdout.write("\r" + title + str(percentage) + '%')
line = ''
sys.stdout.flush()
print "\r" + title + elapsedstr(cmdstarttime)
else:
proc = subprocess.Popen(cmdlist, stdout=subprocess.PIPE)
line = ''
progress_regex = re.compile("Progress: (\d+%)")
while True:
out = proc.stdout.read(1)
if out == '' and proc.poll() != None:
break
if out != '\r':
line += out
else:
if 'Progress: ' in line:
match = progress_regex.search(line)
if match:
percentage = match.group(1)
sys.stdout.write("\r" + title + percentage)
line = ''
sys.stdout.flush()
print "\r" + title + elapsedstr(cmdstarttime)
else:
subprocess.call(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def find_mount_point(path):
path = os.path.abspath(path)
while not os.path.ismount(path):
path = os.path.dirname(path)
return path
def getmd5(fname, block_size=2**12):
md5 = hashlib.md5()
with open(fname, 'rb') as f:
while True:
data = f.read(block_size)
if not data:
break
md5.update(data)
doprint(fname + ": " + md5.hexdigest() + "\n", 3)
return md5.hexdigest()
def check_md5tree(orig, dest):
rt = True
orig = os.path.abspath(orig)
dest = os.path.abspath(dest)
for ofile in os.listdir(orig):
if rt == True:
if os.path.isdir(os.path.join(orig, ofile)):
doprint("dir: " + os.path.join(orig, ofile) + "\n", 3)
odir = os.path.join(orig, ofile)
ddir = os.path.join(dest, ofile)
rt = check_md5tree(odir, ddir)
else:
doprint("file: " + os.path.join(orig, ofile) + "\n", 3)
if getmd5(os.path.join(orig, ofile)) != getmd5(os.path.join(dest, ofile)):
rt = False
return rt
def process(ford):
if os.path.isdir(ford):
doprint(" Processing dir: " + ford + "\n", 3)
if args.recursive:
for f in os.listdir(ford):
process(os.path.join(ford, f))
else:
doprint(" Processing file: " + ford + "\n", 3)
# check if file is an mkv file
child = subprocess.Popen([mkvmerge, "-i", ford], stdout=subprocess.PIPE)
child.communicate()[0]
if child.returncode == 0:
starttime = time.time()
# set up temp dir
tempdir = False
if args.wd:
tempdir = args.wd
if not os.path.exists(tempdir):
os.makedirs(tempdir)
else:
tempdir = tempfile.mkdtemp()
tempdir = os.path.join(tempdir, "mkvdts2ac3")
(dirName, fileName) = os.path.split(ford)
fileBaseName = os.path.splitext(fileName)[0]
doprint("filename: " + fileName + "\n", 1)
dtsfile = fileBaseName + '.dts'
tempdtsfile = os.path.join(tempdir, dtsfile)
ac3file = fileBaseName + '.ac3'
tempac3file = os.path.join(tempdir, ac3file)
aacfile = fileBaseName + '.aac'
tempaacfile = os.path.join(tempdir, aacfile)
tcfile = fileBaseName + '.tc'
temptcfile = os.path.join(tempdir, tcfile)
newmkvfile = fileBaseName + '.mkv'
tempnewmkvfile = os.path.join(tempdir, newmkvfile)
adjacentmkvfile = os.path.join(dirName, fileBaseName + '.new.mkv')
mp4file = os.path.join(dirName, fileBaseName + '.mp4')
fname = fileName
# get dts track id and video track id
output = subprocess.check_output([mkvmerge, "-i", ford])
lines = output.split("\n")
altdtstrackid = False
dtstrackid = False
videotrackid = False
alreadygotac3 = False
audiotracks = []
for line in lines:
linelist = line.split(' ')
trackid = False
if len(linelist) > 2:
trackid = linelist[2]
linelist = trackid.split(':')
trackid = linelist[0]
if 'audio (A_' in line:
audiotracks.append(trackid)
if ': audio (A_DTS)' in line:
dtstrackid = trackid
elif 'video (V_' in line:
videotrackid = trackid
elif ': audio (A_AC3)' in line:
alreadygotac3 = True
if args.track:
if "Track ID " + args.track + ": audio (A_DTS)" in line:
altdtstrackid = args.track
if altdtstrackid:
dtstrackid = altdtstrackid
if not dtstrackid:
doprint(" No DTS track found\n", 1)
elif alreadygotac3 and not args.force:
doprint(" Already has AC3 track\n", 1)
else:
# get dtstrack info
output = subprocess.check_output([mkvinfo, ford])
lines = output.split("\n")
dtstrackinfo = []
startcount = 0
for line in lines:
match = re.search(r'^\|( *)\+', line)
linespaces = startcount
if match:
linespaces = len(match.group(1))
if startcount == 0:
if "track ID for mkvmerge & mkvextract:" in line:
if "track ID for mkvmerge & mkvextract: " + dtstrackid in line:
startcount = linespaces
elif "+ Track number: " + dtstrackid in line:
startcount = linespaces
if linespaces < startcount:
break
if startcount != 0:
dtstrackinfo.append(line)
# get dts language
dtslang = "eng"
for line in dtstrackinfo:
if "Language" in line:
dtslang = line.split()[-1]
# get ac3 track name
ac3name = False
if args.custom:
ac3name = args.custom
else:
for line in dtstrackinfo:
if "+ Name: " in line:
ac3name = line.split("+ Name: ")[-1]
ac3name = ac3name.replace("DTS", "AC3")
ac3name = ac3name.replace("dts", "ac3")
if args.stereo:
ac3name = ac3name.replace("5.1", "Stereo")
# get aac track name
aacname = False
if args.aaccustom:
aacname = args.aaccustom
else:
for line in dtstrackinfo:
if "+ Name: " in line:
aacname = line.split("+ Name: ")[-1]
aacname = aacname.replace("DTS", "AAC")
aacname = aacname.replace("dts", "aac")
if args.aacstereo:
aacname = aacname.replace("5.1", "Stereo")
totaljobs = 4
jobnum = 1
if args.aac:
totaljobs += 1
if args.mp4:
totaljobs += 1
# extract timecodes
tctitle = " Extracting Timecodes [" + str(jobnum) + "/" + str(totaljobs) + "]..."
jobnum += 1
tccmd = [mkvextract, "timecodes_v2", ford, dtstrackid + ":" + temptcfile]
runcommand(tctitle, tccmd)
delay = False
if not args.test:
# get the delay if there is any
fp = open(temptcfile)
for i, line in enumerate(fp):
if i == 1:
delay = line
break
fp.close()
# extract dts track
extracttitle = " Extracting DTS track [" + str(jobnum) + "/" + str(totaljobs) + "]..."
jobnum += 1
extractcmd = [mkvextract, "tracks", ford, dtstrackid + ':' + tempdtsfile]
runcommand(extracttitle, extractcmd)
# convert DTS to AC3
converttitle = " Converting DTS to AC3 [" + str(jobnum) + "/" + str(totaljobs) + "]..."
jobnum += 1
audiochannels = 6
if args.stereo:
audiochannels = 2
convertcmd = [ffmpeg, "-y", "-i", tempdtsfile, "-acodec", "ac3", "-ac", str(audiochannels), "-ab", "448k", tempac3file]
runcommand(converttitle, convertcmd)
if args.aac:
converttitle = " Converting DTS to AAC [" + str(jobnum) + "/" + str(totaljobs) + "]..."
jobnum += 1
audiochannels = 6
if args.aacstereo:
audiochannels = 2
convertcmd = [ffmpeg, "-y", "-i", tempdtsfile, "-acodec", "libfaac", "-ac", str(audiochannels), "-ab", "448k", tempaacfile]
runcommand(converttitle, convertcmd)
if not os.path.isfile(tempaacfile) or os.path.getsize(tempaacfile) == 0:
convertcmd = [ffmpeg, "-y", "-i", tempdtsfile, "-acodec", "libvo_aacenc", "-ac", str(audiochannels), "-ab", "448k", tempaacfile]
runcommand(converttitle, convertcmd)
if not os.path.isfile(tempaacfile) or os.path.getsize(tempaacfile) == 0:
convertcmd = [ffmpeg, "-y", "-i", tempdtsfile, "-acodec", "aac", "-strict", "experimental", "-ac", str(audiochannels), "-ab", "448k", tempaacfile]
runcommand(converttitle, convertcmd)
if not os.path.isfile(tempaacfile) or os.path.getsize(tempaacfile) == 0:
args.aac = False
print "ERROR: ffmpeg can't use any aac codecs. Please try to get libfaac, libvo_aacenc, or a newer version of ffmpeg with the experimental aac codec installed"
if args.external:
if not args.test:
shutil.move(tempac3file, os.path.join(dirName, fileBaseName + '.ac3'))
fname = ac3file
else:
# remux
remuxtitle = " Remuxing AC3 into MKV [" + str(jobnum) + "/" + str(totaljobs) + "]..."
jobnum += 1
# Start to "build" command
remux = [mkvmerge]
# Remove subtitles
if args.no_subtitles:
remux.append("--no-subtitles")
# Puts the AC3 track as the second in the file if indicated as initial
if args.initial:
remux.append("--track-order")
if args.aac:
remux.append("1:0,2:0")
else:
remux.append("1:0")
# If user doesn't want the original DTS track drop it
comp = args.compress
if args.nodts or args.keepdts:
if len(audiotracks) == 1:
remux.append("-A")
else:
audiotracks = [audiotrack for audiotrack in audiotracks if audiotrack != dtstrackid]
remux.append("-a")
remux.append(",".join(audiotracks))
for tid in audiotracks:
remux.append("--compression")
remux.append(tid + ":" + comp)
# Add original MKV file, set header compression scheme
remux.append("--compression")
remux.append(videotrackid + ":" + comp)
remux.append(ford)
# If user wants new AC3 as default then add appropriate arguments to command
if args.default:
remux.append("--default-track")
remux.append("0:0")
# Set the language
remux.append("--language")
remux.append("0:" + dtslang)
# If the name was set for the original DTS track set it for the AC3
if ac3name:
remux.append("--track-name")
remux.append("0:\"" + ac3name.rstrip() + "\"")
# set delay if there is any
if delay:
remux.append("--sync")
remux.append("0:" + delay.rstrip())
# Set track compression scheme and append new AC3
remux.append("--compression")
remux.append("0:" + comp)
remux.append(tempac3file)
if args.aac:
# If the name was set for the original DTS track set it for the AAC
if aacname:
remux.append("--track-name")
remux.append("0:\"" + aacname.rstrip() + "\"")
# Set track compression scheme and append new AAC
remux.append("--compression")
remux.append("0:" + comp)
remux.append(tempaacfile)
# Declare output file
remux.append("-o")
remux.append(tempnewmkvfile)
runcommand(remuxtitle, remux)
if not args.test:
if args.mp4:
converttitle = " Converting MKV to MP4 [" + str(jobnum) + "/" + str(totaljobs) + "]..."
convertcmd = [ffmpeg, "-i", tempnewmkvfile, "-map", "0", "-vcodec", "copy", "-acodec", "copy", "-c:s", "mov_text", mp4file]
runcommand(converttitle, convertcmd)
silentremove(ford)
else:
#~ replace old mkv with new mkv
if args.new:
shutil.move(tempnewmkvfile, adjacentmkvfile)
else:
silentremove(ford)
shutil.move(tempnewmkvfile, ford)
if not args.test:
#~ clean up temp folder
if args.keepdts and not args.external:
shutil.move(tempdtsfile, os.path.join(dirName, fileBaseName + ".dts"))
fname = dtsfile
else:
silentremove(tempdtsfile)
if not args.external:
silentremove(tempac3file)
silentremove(tempaacfile)
silentremove(temptcfile)
if not os.listdir(tempdir):
os.rmdir(tempdir)
#~ print out time taken
elapsed = (time.time() - starttime)
minutes = int(elapsed / 60)
seconds = int(elapsed) % 60
doprint(" " + fileName + " finished in: " + str(minutes) + " minutes " + str(seconds) + " seconds\n", 1)
return fname
totalstime = time.time()
for a in args.fileordir:
for ford in glob.glob(a):
fname = False
if os.path.isdir(ford):
for f in os.listdir(ford):
process(os.path.join(ford, f))
else:
fname = process(ford)
destdir = False
if args.destdir:
destdir = args.destdir
if sab and args.sabdestdir:
destdir = args.sabdestdir
if destdir:
if fname:
(dirName, fileName) = os.path.split(ford)
destfile = os.path.join(destdir, fname)
origfile = os.path.join(dirName, fname)
if args.md5 and (find_mount_point(dirName) != find_mount_point(destdir)):
if os.path.exists(destfile):
if args.overwrite:
silentremove(destfile)
shutil.copyfile(origfile, destfile)
if getmd5(origfile) == getmd5(destfile):
silentremove(origfile)
else:
print "MD5's don't match."
else:
print "File " + destfile + " already exists"
else:
doprint("copying: " + origfile + " --> " + destfile + "\n", 3)
shutil.copyfile(origfile, destfile)
if getmd5(origfile) == getmd5(destfile):
silentremove(origfile)
else:
print "MD5's don't match."
else:
if os.path.exists(destfile):
if args.overwrite:
silentremove(destfile)
shutil.move(origfile, destfile)
else:
print "File " + destfile + " already exists"
else:
shutil.move(origfile, destfile)
else:
origpath = os.path.abspath(ford)
destpath = os.path.join(destdir, os.path.basename(os.path.normpath(ford)))
if args.md5 and (find_mount_point(origpath) != find_mount_point(destpath)):
if os.path.exists(destpath) and args.overwrite:
shutil.rmtree(destpath)
elif os.path.exists(destpath):
print "Directory " + destpath + " already exists"
else:
shutil.copytree(origpath, destpath)
if check_md5tree(origpath, destpath):
shutil.rmtree(origpath)
else:
print "MD5's don't match."
else:
shutil.move(origpath, destpath)
if sab:
sys.stdout.write("mkv dts -> ac3 conversion: " + elapsedstr(totalstime))
else:
doprint("Total Time: " + elapsedstr(totalstime), 1)
Code: Select all
error Wed Dec 04 2013 11:54:53 Post-process-script mkvdts2ac3/mkvdts2ac3.py for dts sample failed (terminated with unknown status)
info Wed Dec 04 2013 11:54:53 mkvdts2ac3: IndentationError: expected an indented block
info Wed Dec 04 2013 11:54:53 mkvdts2ac3: ^
info Wed Dec 04 2013 11:54:53 mkvdts2ac3: configFilename = os.path.join(os.path.dirname(sys.argv[0]), "mkvdts2ac3.cfg")
info Wed Dec 04 2013 11:54:53 mkvdts2ac3: File "/mnt/disk1/share/nzbget/ppscripts/mkvdts2ac3/mkvdts2ac3.py", line 264
info Wed Dec 04 2013 11:54:52 Executing post-process-script mkvdts2ac3/mkvdts2ac3.py for dts sample
Ah damn!!!hugbug wrote:Quotations marks are missing. It should be:Code: Select all
os.environ[NZBPO_AAC]
There are many more such lines in the script. Check all usages of "os.environ" and add quotations marks.Code: Select all
os.environ['NZBPO_AAC']
Code: Select all
#!/usr/bin/env python
#Copyright (C) 2012 Drew Thomson
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
##############################################################################
### NZBGET POST-PROCESSING SCRIPT ###
# mkvdts2ac3.py is a python script for linux, windows or os x which can be used
# for converting the DTS in Matroska (MKV) files to AC3. It provides you with a
# set of options for controlling the resulting file.
##############################################################################
### OPTIONS ###
## These options take an argument.
# NZBGetdestdir.
#
#nzbgetdestdir=
# aaccustom.
#
#aaccustom=
# custom.
#
#custom=
# compress.
#
#compress=
# destdir.
#
#destdir=
# ffmpegpath.
#
#ffmpegpath=
# mkvtoolnixpath
#
#mkvtoolnixpath=
# track.
#
#track=
# wd.
#
#wd=
# verbose.
#
#verbose=
## These options don't take any arguments, they are only True or False
# aac (False, True).
#
#aac=
# aacstereo (False, True).
#
#aacstereo=
# default (False, True).
#
#default=
# external (False, True).
#
#external=
# force (False, True).
#
#force=
# initial (False, True).
#
#initial=
# keepdts (False, True).
#
#keepdts=
# mp4 (False, True).
#
#mp4=
# new (False, True).
#
#new=
# nodts (False, True).
#
#nodts=
# no subtitles (False, True).
#
#no_subtitles=
# no overwrite (False, True).
#
#overwrite=
# recursive (False, True).
#
#recursive=
# test (False, True).
#
#test=
#debug (False, True).
#
#debug=
### NZBGET POST-PROCESSING SCRIPT ###
##############################################################################
import argparse
import os
import subprocess
import time
import glob
import re
import tempfile
import sys
import ConfigParser
import shutil
import hashlib
import textwrap
import errno
version = "1.0"
# check if called from sabnzbdplus
sab = False
if len(sys.argv) == 8:
nzbgroup = sys.argv[6]
ppstatus = sys.argv[7]
if ppstatus.isdigit():
if int(ppstatus) >= 0 and int(ppstatus) <= 3 and "." in nzbgroup:
sab = True
elif os.environ.has_key('NZBOP_SCRIPTDIR') and not os.environ['NZBOP_VERSION'][0:5] < '11.0':
#Logger.info("MAIN: Script triggered from NZBGet (11.0 or later).")
# NZBGet argv: all passed as environment variables.
# Exit codes used by NZBGet
POSTPROCESS_PARCHECK=92
POSTPROCESS_SUCCESS=93
POSTPROCESS_ERROR=94
POSTPROCESS_NONE=95
# Check nzbget.conf options
status = 0
if os.environ['NZBOP_UNPACK'] != 'yes':
#Logger.error("Please enable option \"Unpack\" in nzbget configuration file, exiting")
sys.exit(POSTPROCESS_ERROR)
# Check par status
if os.environ['NZBPP_PARSTATUS'] == '3':
#Logger.warning("Par-check successful, but Par-repair disabled, exiting")
sys.exit(POSTPROCESS_NONE)
if os.environ['NZBPP_PARSTATUS'] == '1':
#Logger.warning("Par-check failed, setting status \"failed\"")
status = 1
# Check unpack status
if os.environ['NZBPP_UNPACKSTATUS'] == '1':
#Logger.warning("Unpack failed, setting status \"failed\"")
status = 1
if os.environ['NZBPP_UNPACKSTATUS'] == '0' and os.environ['NZBPP_PARSTATUS'] != '2':
# Unpack is disabled or was skipped due to nzb-file properties or due to errors during par-check
for dirpath, dirnames, filenames in os.walk(os.environ['NZBPP_DIRECTORY']):
for file in filenames:
fileExtension = os.path.splitext(file)[1]
if fileExtension in ['.rar', '.7z'] or os.path.splitext(fileExtension)[1] in ['.rar', '.7z']:
#Logger.warning("Post-Process: Archive files exist but unpack skipped, setting status \"failed\"")
status = 1
break
if fileExtension in ['.par2']:
#Logger.warning("Post-Process: Unpack skipped and par-check skipped (although par2-files exist), setting status \"failed\"g")
status = 1
break
if os.path.isfile(os.path.join(os.environ['NZBPP_DIRECTORY'], "_brokenlog.txt")) and not status == 1:
#Logger.warning("Post-Process: _brokenlog.txt exists, download is probably damaged, exiting")
status = 1
#if not status == 1:
#Logger.info("Neither archive- nor par2-files found, _brokenlog.txt doesn't exist, considering download successful")
# Check if destination directory exists (important for reprocessing of history items)
if not os.path.isdir(os.environ['NZBPP_DIRECTORY']):
#Logger.error("Post-Process: Nothing to post-process: destination directory %s doesn't exist", os.environ['NZBPP_DIRECTORY'])
status = 1
# All checks done, now launching the script.
ppstatus = status
if ppstatus.isdigit():
if int(ppstatus) >= 0 and int(ppstatus) <= 3:
sab = True
# create parser
parser = argparse.ArgumentParser(description='convert matroska (.mkv) video files audio portion from dts to ac3')
if os.environ.has_key('NZBOP_SCRIPTDIR'): # NZBGet
args = parser.parse_args()
args.fileordir = os.environ['NZBPP_DIRECTORY']
args.aac = os.environ['NZBPO_AAC']
args.aacstereo = os.environ['NZBPO_AACSTEREO']
args.aaccustom = os.environ['NZBPO_AACCUSTOM']
args.custom = os.environ['NZBPO_CUSTOM']
args.default = os.environ['NZBPO_DEFAULT']
args.sabdestdir = os.environ['NZBPO_NZBGETDESTDIR']
args.compress = os.environ['NZBPO_COMPRESS']
args.destdir = os.environ['NZBPO_DESTDIR']
args.ffmpegpath = os.environ['NZBPO_FFMPEGPATH']
args.mkvtoolnixpath = os.environ['NZBPO_MKVTOOLNIXPATH']
args.track = os.environ['NZBPO_TRACK']
args.wd = os.environ['NZBPO_WD']
args.verbose = os.environ['NZBPO_VERBOSE']
args.external = os.environ['NZBPO_EXTERNAL']
args.force = os.environ['NZBPO_FORCE']
args.initial = os.environ['NZBPO_INITIAL']
args.keepdts = os.environ['NZBPO_KEEPDTS']
args.mp4 = os.environ['NZBPO_MP4']
args.new = os.environ['NZBPO_NEW']
args.nodts = os.environ['NZBPO_NODTS']
args.no_subtitles = os.environ['NZBPO_NO_SUBTITLES']
args.overwrite = os.environ['NZBPO_OVERWRITE']
args.recursive = os.environ['NZBPO_RECURSIVE']
args.test = os.environ['NZBPO_TEST']
args.debug = os.environ['NZBPO_DEBUG']
if not args.verbose:
args.verbose = 0
else: # default handling
# set config file arguments
configFilename = os.path.join(os.path.dirname(sys.argv[0]), "mkvdts2ac3.cfg")
if os.path.isfile(configFilename):
config = ConfigParser.SafeConfigParser()
config.read(configFilename)
defaults = dict(config.items("mkvdts2ac3"))
for key in defaults:
if key == "verbose":
defaults["verbose"] = int(defaults["verbose"])
parser.set_defaults(**defaults)
parser.add_argument('fileordir', metavar='FileOrDirectory', nargs='+', help='a file or directory (wildcards may be used)')
parser.add_argument("--aac", help="Also add aac track", action="store_true")
parser.add_argument("--aacstereo", help="Make aac track stereo instead of 6 channel", action="store_true")
parser.add_argument("--aaccustom", metavar="TITLE", help="Custom AAC track title")
parser.add_argument("-c", "--custom", metavar="TITLE", help="Custom AC3 track title")
parser.add_argument("-d", "--default", help="Mark AC3 track as default", action="store_true")
parser.add_argument("--destdir", metavar="DIRECTORY", help="Destination Directory")
parser.add_argument("-e", "--external", action="store_true",
help="Leave AC3 track out of file. Does not modify the original matroska file. This overrides '-n' and '-d' arguments")
parser.add_argument("-f", "--force", help="Force processing when AC3 track is detected", action="store_true")
parser.add_argument("--ffmpegpath", metavar="DIRECTORY", help="Path of ffmpeg")
parser.add_argument("-i", "--initial", help="New AC3 track will be first in the file", action="store_true")
parser.add_argument("-k", "--keepdts", help="Keep external DTS track (implies '-n')", action="store_true")
parser.add_argument("--md5", help="check md5 of files before removing the original if destination directory is on a different device than the original file", action="store_true")
parser.add_argument("--mp4", help="create output in mp4 format", action="store_true")
parser.add_argument("--mkvtoolnixpath", metavar="DIRECTORY", help="Path of mkvextract, mkvinfo and mkvmerge")
parser.add_argument("-n", "--nodts", help="Do not retain the DTS track", action="store_true")
parser.add_argument("--new", help="Do not copy over original. Create new adjacent file", action="store_true")
parser.add_argument("--no-subtitles", help="Remove subtitles", action="store_true")
parser.add_argument("-o", "--overwrite", help="Overwrite file if already there. This only applies if destdir or sabdestdir is set", action="store_true")
parser.add_argument("-r", "--recursive", help="Recursively descend into directories", action="store_true")
parser.add_argument("-s", "--compress", metavar="MODE", help="Apply header compression to streams (See mkvmerge's --compression)", default='none')
parser.add_argument("--sabdestdir", metavar="DIRECTORY", help="SABnzbd Destination Directory")
parser.add_argument("--stereo", help="Make ac3 track stereo instead of 6 channel", action="store_true")
parser.add_argument("-t", "--track", metavar="TRACKID", help="Specify alternate DTS track. If it is not a DTS track it will default to the first DTS track found")
parser.add_argument("-w", "--wd", metavar="FOLDER", help="Specify alternate temporary working directory")
parser.add_argument("-v", "--verbose", help="Turn on verbose output. Use more v's for more verbosity. -v will output what it is doing. -vv will also output the command that it is running. -vvv will also output the command output", action="count")
parser.add_argument("-V", "--version", help="Print script version information", action='version', version='%(prog)s ' + version + ' by Drew Thomson')
parser.add_argument("--test", help="Print commands only, execute nothing", action="store_true")
parser.add_argument("--debug", help="Print commands and pause before executing each", action="store_true")
args = parser.parse_args()
if not args.verbose:
args.verbose = 0
def winexe(program):
if sys.platform == "win32" and not program.endswith(".exe"):
program += ".exe"
return program
# set ffmpeg and mkvtoolnix paths
if args.mkvtoolnixpath:
mkvinfo = os.path.join(args.mkvtoolnixpath, "mkvinfo")
mkvinfo = winexe(mkvinfo)
mkvmerge = os.path.join(args.mkvtoolnixpath, "mkvmerge")
mkvmerge = winexe(mkvmerge)
mkvextract = os.path.join(args.mkvtoolnixpath, "mkvextract")
mkvextract = winexe(mkvextract)
if not args.mkvtoolnixpath or not os.path.exists(mkvinfo):
mkvinfo = "mkvinfo"
if not args.mkvtoolnixpath or not os.path.exists(mkvmerge):
mkvmerge = "mkvmerge"
if not args.mkvtoolnixpath or not os.path.exists(mkvextract):
mkvextract = "mkvextract"
if args.ffmpegpath:
ffmpeg = os.path.join(args.ffmpegpath, "ffmpeg")
ffmpeg = winexe(ffmpeg)
if not args.ffmpegpath or not os.path.exists(ffmpeg):
ffmpeg = "ffmpeg"
# check paths
def which(program):
if sys.platform == "win32" and not program.endswith(".exe"):
program += ".exe"
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath = os.path.split(program)[0]
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
missingprereqs = False
missinglist = []
if not which(mkvextract):
missingprereqs = True
missinglist.append("mkvextract")
if not which(mkvinfo):
missingprereqs = True
missinglist.append("mkvinfo")
if not which(mkvmerge):
missingprereqs = True
missinglist.append("mkvmerge")
if not which(ffmpeg):
missingprereqs = True
missinglist.append("ffmpeg")
if missingprereqs:
sys.stdout.write("You are missing the following prerequisite tools: ")
for tool in missinglist:
sys.stdout.write(tool + " ")
if not args.mkvtoolnixpath and not args.ffmpegpath:
print "\nYou can use --mkvtoolnixpath and --ffmpegpath to specify the path"
else:
print
sys.exit(1)
if not args.verbose:
args.verbose = 0
if args.verbose < 2 and (args.test or args.debug):
args.verbose = 2
if sab:
args.fileordir = [args.fileordir[0]]
args.verbose = 3
if args.debug and args.verbose == 0:
args.verbose = 1
def doprint(mystr, v):
if args.verbose >= v:
sys.stdout.write(mystr)
def silentremove(filename):
try:
os.remove(filename)
except OSError, e:
if e.errno != errno.ENOENT: # errno.ENOENT = no such file or directory
raise # re-raise exception if a different error occured
def elapsedstr(starttime):
elapsed = (time.time() - starttime)
minutes = int(elapsed / 60)
mplural = 's'
if minutes == 1:
mplural = ''
seconds = int(elapsed) % 60
splural = 's'
if seconds == 1:
splural = ''
return str(minutes) + " minute" + mplural + " " + str(seconds) + " second" + splural
def getduration(time):
(hms, ms) = time.split('.')
(h, m, s) = hms.split(':')
totalms = int(ms) + (int(s) * 100) + (int(m) * 100 * 60) + (int(h) * 100 * 60 * 60)
return totalms
def runcommand(title, cmdlist):
if args.debug:
raw_input("Press Enter to continue...")
cmdstarttime = time.time()
if args.verbose >= 1:
sys.stdout.write(title)
if args.verbose >= 2:
cmdstr = ''
for e in cmdlist:
cmdstr += e + ' '
print
print " Running command:"
print textwrap.fill(cmdstr.rstrip(), initial_indent=' ', subsequent_indent=' ')
if not args.test:
if args.verbose >= 3:
subprocess.call(cmdlist)
elif args.verbose >= 1:
if "ffmpeg" in cmdlist[0]:
proc = subprocess.Popen(cmdlist, stderr=subprocess.PIPE)
line = ''
duration_regex = re.compile(" Duration: (\d+:\d\d:\d\d\.\d\d),")
progress_regex = re.compile("size= +\d+.*time=(\d+:\d\d:\d\d\.\d\d) bitrate=")
duration = False
while True:
if not duration:
durationline = proc.stderr.readline()
match = duration_regex.match(durationline)
if match:
duration = getduration(match.group(1))
else:
out = proc.stderr.read(1)
if out == '' and proc.poll() != None:
break
if out != '\r':
line += out
else:
if 'size= ' in line:
match = progress_regex.search(line)
if match:
percentage = int(float(getduration(match.group(1)) / float(duration)) * 100)
if percentage > 100:
percentage = 100
sys.stdout.write("\r" + title + str(percentage) + '%')
line = ''
sys.stdout.flush()
print "\r" + title + elapsedstr(cmdstarttime)
else:
proc = subprocess.Popen(cmdlist, stdout=subprocess.PIPE)
line = ''
progress_regex = re.compile("Progress: (\d+%)")
while True:
out = proc.stdout.read(1)
if out == '' and proc.poll() != None:
break
if out != '\r':
line += out
else:
if 'Progress: ' in line:
match = progress_regex.search(line)
if match:
percentage = match.group(1)
sys.stdout.write("\r" + title + percentage)
line = ''
sys.stdout.flush()
print "\r" + title + elapsedstr(cmdstarttime)
else:
subprocess.call(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def find_mount_point(path):
path = os.path.abspath(path)
while not os.path.ismount(path):
path = os.path.dirname(path)
return path
def getmd5(fname, block_size=2**12):
md5 = hashlib.md5()
with open(fname, 'rb') as f:
while True:
data = f.read(block_size)
if not data:
break
md5.update(data)
doprint(fname + ": " + md5.hexdigest() + "\n", 3)
return md5.hexdigest()
def check_md5tree(orig, dest):
rt = True
orig = os.path.abspath(orig)
dest = os.path.abspath(dest)
for ofile in os.listdir(orig):
if rt == True:
if os.path.isdir(os.path.join(orig, ofile)):
doprint("dir: " + os.path.join(orig, ofile) + "\n", 3)
odir = os.path.join(orig, ofile)
ddir = os.path.join(dest, ofile)
rt = check_md5tree(odir, ddir)
else:
doprint("file: " + os.path.join(orig, ofile) + "\n", 3)
if getmd5(os.path.join(orig, ofile)) != getmd5(os.path.join(dest, ofile)):
rt = False
return rt
def process(ford):
if os.path.isdir(ford):
doprint(" Processing dir: " + ford + "\n", 3)
if args.recursive:
for f in os.listdir(ford):
process(os.path.join(ford, f))
else:
doprint(" Processing file: " + ford + "\n", 3)
# check if file is an mkv file
child = subprocess.Popen([mkvmerge, "-i", ford], stdout=subprocess.PIPE)
child.communicate()[0]
if child.returncode == 0:
starttime = time.time()
# set up temp dir
tempdir = False
if args.wd:
tempdir = args.wd
if not os.path.exists(tempdir):
os.makedirs(tempdir)
else:
tempdir = tempfile.mkdtemp()
tempdir = os.path.join(tempdir, "mkvdts2ac3")
(dirName, fileName) = os.path.split(ford)
fileBaseName = os.path.splitext(fileName)[0]
doprint("filename: " + fileName + "\n", 1)
dtsfile = fileBaseName + '.dts'
tempdtsfile = os.path.join(tempdir, dtsfile)
ac3file = fileBaseName + '.ac3'
tempac3file = os.path.join(tempdir, ac3file)
aacfile = fileBaseName + '.aac'
tempaacfile = os.path.join(tempdir, aacfile)
tcfile = fileBaseName + '.tc'
temptcfile = os.path.join(tempdir, tcfile)
newmkvfile = fileBaseName + '.mkv'
tempnewmkvfile = os.path.join(tempdir, newmkvfile)
adjacentmkvfile = os.path.join(dirName, fileBaseName + '.new.mkv')
mp4file = os.path.join(dirName, fileBaseName + '.mp4')
fname = fileName
# get dts track id and video track id
output = subprocess.check_output([mkvmerge, "-i", ford])
lines = output.split("\n")
altdtstrackid = False
dtstrackid = False
videotrackid = False
alreadygotac3 = False
audiotracks = []
for line in lines:
linelist = line.split(' ')
trackid = False
if len(linelist) > 2:
trackid = linelist[2]
linelist = trackid.split(':')
trackid = linelist[0]
if 'audio (A_' in line:
audiotracks.append(trackid)
if ': audio (A_DTS)' in line:
dtstrackid = trackid
elif 'video (V_' in line:
videotrackid = trackid
elif ': audio (A_AC3)' in line:
alreadygotac3 = True
if args.track:
if "Track ID " + args.track + ": audio (A_DTS)" in line:
altdtstrackid = args.track
if altdtstrackid:
dtstrackid = altdtstrackid
if not dtstrackid:
doprint(" No DTS track found\n", 1)
elif alreadygotac3 and not args.force:
doprint(" Already has AC3 track\n", 1)
else:
# get dtstrack info
output = subprocess.check_output([mkvinfo, ford])
lines = output.split("\n")
dtstrackinfo = []
startcount = 0
for line in lines:
match = re.search(r'^\|( *)\+', line)
linespaces = startcount
if match:
linespaces = len(match.group(1))
if startcount == 0:
if "track ID for mkvmerge & mkvextract:" in line:
if "track ID for mkvmerge & mkvextract: " + dtstrackid in line:
startcount = linespaces
elif "+ Track number: " + dtstrackid in line:
startcount = linespaces
if linespaces < startcount:
break
if startcount != 0:
dtstrackinfo.append(line)
# get dts language
dtslang = "eng"
for line in dtstrackinfo:
if "Language" in line:
dtslang = line.split()[-1]
# get ac3 track name
ac3name = False
if args.custom:
ac3name = args.custom
else:
for line in dtstrackinfo:
if "+ Name: " in line:
ac3name = line.split("+ Name: ")[-1]
ac3name = ac3name.replace("DTS", "AC3")
ac3name = ac3name.replace("dts", "ac3")
if args.stereo:
ac3name = ac3name.replace("5.1", "Stereo")
# get aac track name
aacname = False
if args.aaccustom:
aacname = args.aaccustom
else:
for line in dtstrackinfo:
if "+ Name: " in line:
aacname = line.split("+ Name: ")[-1]
aacname = aacname.replace("DTS", "AAC")
aacname = aacname.replace("dts", "aac")
if args.aacstereo:
aacname = aacname.replace("5.1", "Stereo")
totaljobs = 4
jobnum = 1
if args.aac:
totaljobs += 1
if args.mp4:
totaljobs += 1
# extract timecodes
tctitle = " Extracting Timecodes [" + str(jobnum) + "/" + str(totaljobs) + "]..."
jobnum += 1
tccmd = [mkvextract, "timecodes_v2", ford, dtstrackid + ":" + temptcfile]
runcommand(tctitle, tccmd)
delay = False
if not args.test:
# get the delay if there is any
fp = open(temptcfile)
for i, line in enumerate(fp):
if i == 1:
delay = line
break
fp.close()
# extract dts track
extracttitle = " Extracting DTS track [" + str(jobnum) + "/" + str(totaljobs) + "]..."
jobnum += 1
extractcmd = [mkvextract, "tracks", ford, dtstrackid + ':' + tempdtsfile]
runcommand(extracttitle, extractcmd)
# convert DTS to AC3
converttitle = " Converting DTS to AC3 [" + str(jobnum) + "/" + str(totaljobs) + "]..."
jobnum += 1
audiochannels = 6
if args.stereo:
audiochannels = 2
convertcmd = [ffmpeg, "-y", "-i", tempdtsfile, "-acodec", "ac3", "-ac", str(audiochannels), "-ab", "448k", tempac3file]
runcommand(converttitle, convertcmd)
if args.aac:
converttitle = " Converting DTS to AAC [" + str(jobnum) + "/" + str(totaljobs) + "]..."
jobnum += 1
audiochannels = 6
if args.aacstereo:
audiochannels = 2
convertcmd = [ffmpeg, "-y", "-i", tempdtsfile, "-acodec", "libfaac", "-ac", str(audiochannels), "-ab", "448k", tempaacfile]
runcommand(converttitle, convertcmd)
if not os.path.isfile(tempaacfile) or os.path.getsize(tempaacfile) == 0:
convertcmd = [ffmpeg, "-y", "-i", tempdtsfile, "-acodec", "libvo_aacenc", "-ac", str(audiochannels), "-ab", "448k", tempaacfile]
runcommand(converttitle, convertcmd)
if not os.path.isfile(tempaacfile) or os.path.getsize(tempaacfile) == 0:
convertcmd = [ffmpeg, "-y", "-i", tempdtsfile, "-acodec", "aac", "-strict", "experimental", "-ac", str(audiochannels), "-ab", "448k", tempaacfile]
runcommand(converttitle, convertcmd)
if not os.path.isfile(tempaacfile) or os.path.getsize(tempaacfile) == 0:
args.aac = False
print "ERROR: ffmpeg can't use any aac codecs. Please try to get libfaac, libvo_aacenc, or a newer version of ffmpeg with the experimental aac codec installed"
if args.external:
if not args.test:
shutil.move(tempac3file, os.path.join(dirName, fileBaseName + '.ac3'))
fname = ac3file
else:
# remux
remuxtitle = " Remuxing AC3 into MKV [" + str(jobnum) + "/" + str(totaljobs) + "]..."
jobnum += 1
# Start to "build" command
remux = [mkvmerge]
# Remove subtitles
if args.no_subtitles:
remux.append("--no-subtitles")
# Puts the AC3 track as the second in the file if indicated as initial
if args.initial:
remux.append("--track-order")
if args.aac:
remux.append("1:0,2:0")
else:
remux.append("1:0")
# If user doesn't want the original DTS track drop it
comp = args.compress
if args.nodts or args.keepdts:
if len(audiotracks) == 1:
remux.append("-A")
else:
audiotracks = [audiotrack for audiotrack in audiotracks if audiotrack != dtstrackid]
remux.append("-a")
remux.append(",".join(audiotracks))
for tid in audiotracks:
remux.append("--compression")
remux.append(tid + ":" + comp)
# Add original MKV file, set header compression scheme
remux.append("--compression")
remux.append(videotrackid + ":" + comp)
remux.append(ford)
# If user wants new AC3 as default then add appropriate arguments to command
if args.default:
remux.append("--default-track")
remux.append("0:0")
# Set the language
remux.append("--language")
remux.append("0:" + dtslang)
# If the name was set for the original DTS track set it for the AC3
if ac3name:
remux.append("--track-name")
remux.append("0:\"" + ac3name.rstrip() + "\"")
# set delay if there is any
if delay:
remux.append("--sync")
remux.append("0:" + delay.rstrip())
# Set track compression scheme and append new AC3
remux.append("--compression")
remux.append("0:" + comp)
remux.append(tempac3file)
if args.aac:
# If the name was set for the original DTS track set it for the AAC
if aacname:
remux.append("--track-name")
remux.append("0:\"" + aacname.rstrip() + "\"")
# Set track compression scheme and append new AAC
remux.append("--compression")
remux.append("0:" + comp)
remux.append(tempaacfile)
# Declare output file
remux.append("-o")
remux.append(tempnewmkvfile)
runcommand(remuxtitle, remux)
if not args.test:
if args.mp4:
converttitle = " Converting MKV to MP4 [" + str(jobnum) + "/" + str(totaljobs) + "]..."
convertcmd = [ffmpeg, "-i", tempnewmkvfile, "-map", "0", "-vcodec", "copy", "-acodec", "copy", "-c:s", "mov_text", mp4file]
runcommand(converttitle, convertcmd)
silentremove(ford)
else:
#~ replace old mkv with new mkv
if args.new:
shutil.move(tempnewmkvfile, adjacentmkvfile)
else:
silentremove(ford)
shutil.move(tempnewmkvfile, ford)
if not args.test:
#~ clean up temp folder
if args.keepdts and not args.external:
shutil.move(tempdtsfile, os.path.join(dirName, fileBaseName + ".dts"))
fname = dtsfile
else:
silentremove(tempdtsfile)
if not args.external:
silentremove(tempac3file)
silentremove(tempaacfile)
silentremove(temptcfile)
if not os.listdir(tempdir):
os.rmdir(tempdir)
#~ print out time taken
elapsed = (time.time() - starttime)
minutes = int(elapsed / 60)
seconds = int(elapsed) % 60
doprint(" " + fileName + " finished in: " + str(minutes) + " minutes " + str(seconds) + " seconds\n", 1)
return fname
totalstime = time.time()
for a in args.fileordir:
for ford in glob.glob(a):
fname = False
if os.path.isdir(ford):
for f in os.listdir(ford):
process(os.path.join(ford, f))
else:
fname = process(ford)
destdir = False
if args.destdir:
destdir = args.destdir
if sab and args.sabdestdir:
destdir = args.sabdestdir
if destdir:
if fname:
(dirName, fileName) = os.path.split(ford)
destfile = os.path.join(destdir, fname)
origfile = os.path.join(dirName, fname)
if args.md5 and (find_mount_point(dirName) != find_mount_point(destdir)):
if os.path.exists(destfile):
if args.overwrite:
silentremove(destfile)
shutil.copyfile(origfile, destfile)
if getmd5(origfile) == getmd5(destfile):
silentremove(origfile)
else:
print "MD5's don't match."
else:
print "File " + destfile + " already exists"
else:
doprint("copying: " + origfile + " --> " + destfile + "\n", 3)
shutil.copyfile(origfile, destfile)
if getmd5(origfile) == getmd5(destfile):
silentremove(origfile)
else:
print "MD5's don't match."
else:
if os.path.exists(destfile):
if args.overwrite:
silentremove(destfile)
shutil.move(origfile, destfile)
else:
print "File " + destfile + " already exists"
else:
shutil.move(origfile, destfile)
else:
origpath = os.path.abspath(ford)
destpath = os.path.join(destdir, os.path.basename(os.path.normpath(ford)))
if args.md5 and (find_mount_point(origpath) != find_mount_point(destpath)):
if os.path.exists(destpath) and args.overwrite:
shutil.rmtree(destpath)
elif os.path.exists(destpath):
print "Directory " + destpath + " already exists"
else:
shutil.copytree(origpath, destpath)
if check_md5tree(origpath, destpath):
shutil.rmtree(origpath)
else:
print "MD5's don't match."
else:
shutil.move(origpath, destpath)
if sab:
sys.stdout.write("mkv dts -> ac3 conversion: " + elapsedstr(totalstime))
else:
doprint("Total Time: " + elapsedstr(totalstime), 1)
Code: Select all
error Wed Dec 04 2013 13:19:57 Post-process-script mkvdts2ac3/mkvdts2ac3.py for dts sample failed (terminated with unknown status)
info Wed Dec 04 2013 13:19:57 mkvdts2ac3: ImportError: No module named argparse
info Wed Dec 04 2013 13:19:57 mkvdts2ac3: import argparse
info Wed Dec 04 2013 13:19:57 mkvdts2ac3: File "/mnt/disk1/share/nzbget/ppscripts/mkvdts2ac3/mkvdts2ac3.py", line 135, in <module>
info Wed Dec 04 2013 13:19:57 mkvdts2ac3: Traceback (most recent call last):
Users browsing this forum: No registered users and 36 guests