import errno import os import platform import subprocess import urllib2 import traceback import nzbtomedia import json from subprocess import call from nzbtomedia import logger from nzbtomedia.nzbToMediaUtil import makeDir def isVideoGood(videofile): fileNameExt = os.path.basename(videofile) fileName, fileExt = os.path.splitext(fileNameExt) if fileExt not in nzbtomedia.MEDIACONTAINER: return True if platform.system() == 'Windows': bitbucket = open('NUL') else: bitbucket = open('/dev/null') if not nzbtomedia.FFPROBE: return True command = [nzbtomedia.FFPROBE, videofile] try: logger.info('Checking [%s] for corruption, please stand by ...' % (fileNameExt), 'TRANSCODER') result = call(command, stdout=bitbucket, stderr=bitbucket) except: logger.error("Checking [%s] for corruption has failed" % (fileNameExt), 'TRANSCODER') return False if result == 0: logger.info("SUCCESS: [%s] has no corruption." % (fileNameExt), 'TRANSCODER') return True else: logger.error("FAILED: [%s] is corrupted!" % (fileNameExt), 'TRANSCODER') return False def getVideoDetails(videofile): video_details = {} if not nzbtomedia.FFPROBE: return video_details command = [nzbtomedia.FFPROBE, '-v', 'quiet', '-print_format', 'json', '-show_format', '-show_streams', videofile] proc = subprocess.Popen(command, stdout=subprocess.PIPE) proc.wait() video_details = json.loads(proc.stdout.read()) return video_details def buildCommands(file, newDir): video_details = getVideoDetails(file) dir, name = os.path.split(file) name, ext = os.path.splitext(name) if ext == nzbtomedia.VEXTENSION and newDir == dir: # we need to change the name to prevent overwriting itself. nzbtomedia.VEXTENSION = '-transcoded' + nzbtomedia.VEXTENSION # adds '-transcoded.ext' newfilePath = os.path.normpath(os.path.join(newDir, name) + nzbtomedia.VEXTENSION) map_cmd = [] video_cmd = [] audio_cmd = [] sub_cmd = [] other_cmd = [] if not video_details: # we couldn't read streams with ffprobe. Set defaults to try transcoding. videoStreams = [] audioStreams = [] subStreams = [] map_cmd.extend(['-map', '0']) if nzbtomedia.VCODEC: video_cmd.extend(['-c:v', nzbtomedia.VCODEC]) if nzbtomedia.VCODEC == 'libx264' and nzbtomedia.VPRESET: video_cmd.extend(['-pre', nzbtomedia.VPRESET]) else: video_cmd.extend(['-c:v', 'copy']) if nzbtomedia.VFRAMERATE: video_cmd.extend(['-r', str(nzbtomedia.VFRAMERATE)]) if nzbtomedia.VBITRATE: video_cmd.extend(['-b:v', str(nzbtomedia.VBITRATE)]) if nzbtomedia.VRESOLUTION: video_cmd.extend(['-vf', 'scale=' + nzbtomedia.VRESOLUTION]) if nzbtomedia.ACODEC: audio_cmd.extend(['-c:a', nzbtomedia.ACODEC]) if nzbtomedia.ACODEC == 'aac': # Allow users to use the experimental AAC codec that's built into recent versions of ffmpeg audio_cmd.extend(['-strict', '-2']) else: audio_cmd.extend(['-c:a', 'copy']) if nzbtomedia.ABITRATE: audio_cmd.extend(['-b:a', str(nzbtomedia.ABITRATE)]) if nzbtomedia.OUTPUTQUALITYPERCENT: audio_cmd.extend(['-q:a', str(nzbtomedia.OUTPUTQUALITYPERCENT)]) if nzbtomedia.OUTPUTSUBTITLECODEC and nzbtomedia.ALLOWSUBS: sub_cmd.extend(['-c:s', nzbtomedia.SCODEC]) elif nzbtomedia.ALLOWSUBS: # Not every subtitle codec can be used for every video container format! sub_cmd.extend(['-c:s', 'copy']) else: # http://en.wikibooks.org/wiki/FFMPEG_An_Intermediate_Guide/subtitle_options sub_cmd.extend('-sn') # Don't copy the subtitles over if nzbtomedia.OUTPUTFASTSTART: other_cmd.extend(['-movflags', '+faststart']) else: videoStreams = [item for item in video_details["streams"] if item["codec_type"] == "video"] audioStreams = [item for item in video_details["streams"] if item["codec_type"] == "audio"] subStreams = [item for item in video_details["streams"] if item["codec_type"] == "subtitle"] for video in videoStreams: req_tc = 0 codec = video["codec_name"] fr = video["avg_frame_rate"] width = video["width"] height = video["height"] scale = nzbtomedia.VRESOLUTION framerate = float(fr.split('/')[0])/float(fr.split('/')[1]) vid_cmds = [] if codec in nzbtomedia.VCODEC_ALLOW: video_cmd.extend(['-c:v', 'copy']) else: video_cmd.extend(['-c:v', nzbtomedia.VCODEC]) if nzbtomedia.VFRAMERATE and not (nzbtomedia.VFRAMERATE * 0.999 <= fr <= nzbtomedia.VFRAMERATE * 1.001): video_cmd.extend(['-r', str(nzbtomedia.VFRAMERATE)]) if scale: w_scale = width/int(scale.split(':')[0]) h_scale = height/int(scale.split(':')[1]) if w_scale > h_scale: # widescreen, Scale by width only. scale = scale.split(':')[0] + ":-1" if w_scale != 1: video_cmd.extend(['-vf', 'scale=' + scale]) else: # lower or mathcing ratio, scale by height only. scale = "-1:" + scale.split(':')[1] if h_scale != 1: video_cmd.extend(['-vf', 'scale=' + scale]) map_cmd.extend(['-map', '0:' + str(video["index"])]) break # Only one video needed used_audio = 0 a_mapped = [] if audioStreams: audio1 = [ item for item in audioStreams if item["tags"]["language"] == nzbtomedia.ALANGUAGE ] audio2 = [ item for item in audio1 if item["codec_name"] in nzbtomedia.ACODEC_ALLOW ] audio3 = [ item for item in audioStreams if item["tags"]["language"] != nzbtomedia.ALANGUAGE ] if audio2: # right language and codec... map_cmd.extend(['-map', '0:' + str(audio2[0]["index"])]) a_mapped.extend([audio2[0]["index"]]) bitrate = int(audio2[0]["bit_rate"])/1000 audio_cmd.extend(['-c:a:' + str(used_audio), 'copy']) if nzbtomedia.ABITRATE and not (nzbtomedia.ABITRATE * 0.9 < bitrate < nzbtomedia.ABITRATE * 1.1): audio_cmd.extend(['-b:a:' + str(used_audio), str(nzbtomedia.ABITRATE)]) if nzbtomedia.OUTPUTQUALITYPERCENT: audio_cmd.extend(['-q:a:' + str(used_audio), str(nzbtomedia.OUTPUTQUALITYPERCENT)]) elif audio1: # right language wrong codec. map_cmd.extend(['-map', '0:' + str(audio1[0]["index"])]) a_mapped.extend([audio1[0]["index"]]) audio_cmd.extend(['-c:a:' + str(used_audio), nzbtomedia.ACODEC]) if nzbtomedia.ABITRATE: audio_cmd.extend(['-b:a:' + str(used_audio), str(nzbtomedia.ABITRATE)]) if nzbtomedia.OUTPUTQUALITYPERCENT: audio_cmd.extend(['-q:a:' + str(used_audio), str(nzbtomedia.OUTPUTQUALITYPERCENT)]) elif audio3: # just pick the default audio track map_cmd.extend(['-map', '0:' + str(audio3[0]["index"])]) a_mapped.extend([audio3[0]["index"]]) audio_cmd.extend(['-c:a:' + str(used_audio), nzbtomedia.ACODEC]) if nzbtomedia.ABITRATE: audio_cmd.extend(['-b:a:' + str(used_audio), str(nzbtomedia.ABITRATE)]) if nzbtomedia.OUTPUTQUALITYPERCENT: audio_cmd.extend(['-q:a:' + str(used_audio), str(nzbtomedia.OUTPUTQUALITYPERCENT)]) if nzbtomedia.ACODEC2_ALLOW: used_audio += 1 audio4 = [ item for item in audio1 if item["codec_name"] in nzbtomedia.ACODEC2_ALLOW ] if audio4: # right language and codec. map_cmd.extend(['-map', '0:' + str(audio4[0]["index"])]) a_mapped.extend([audio4[0]["index"]]) bitrate = int(audio3[0]["bit_rate"])/1000 audio_cmd.extend(['-c:a:' + str(used_audio), 'copy']) if nzbtomedia.ABITRATE2 and not (nzbtomedia.ABITRATE2 * 0.9 < bitrate < nzbtomedia.ABITRATE2 * 1.1): audio_cmd.extend(['-b:a' + str(used_audio), str(nzbtomedia.ABITRATE2)]) if nzbtomedia.OUTPUTQUALITYPERCENT: audio_cmd.extend(['-q:a' + str(used_audio), str(nzbtomedia.OUTPUTQUALITYPERCENT)]) elif audio1: # right language wrong codec. map_cmd.extend(['-map', '0:' + str(audio1[0]["index"])]) a_mapped.extend([audio1[0]["index"]]) audio_cmd.extend(['-c:a:' + str(used_audio), nzbtomedia.ACODEC2]) if nzbtomedia.ABITRATE2: audio_cmd.extend(['-b:a:' + str(used_audio), str(nzbtomedia.ABITRATE2)]) if nzbtomedia.OUTPUTQUALITYPERCENT: audio_cmd.extend(['-q:a:' + str(used_audio), str(nzbtomedia.OUTPUTQUALITYPERCENT)]) elif audio3: # just pick the default audio track map_cmd.extend(['-map', '0:' + str(audio3[0]["index"])]) a_mapped.extend([audio3[0]["index"]]) audio_cmd.extend(['-c:a:' + str(used_audio), nzbtomedia.ACODEC2]) if nzbtomedia.ABITRATE2: audio_cmd.extend(['-b:a:' + str(used_audio), str(nzbtomedia.ABITRATE2)]) if nzbtomedia.OUTPUTQUALITYPERCENT: audio_cmd.extend(['-q:a:' + str(used_audio), str(nzbtomedia.OUTPUTQUALITYPERCENT)]) if nzbtomedia.AINCLUDE and audio3: # just pick the default audio track for audio in audioStreams: if audio["index"] in a_mapped: continue used_audio += 1 map_cmd.extend(['-map', '0:' + str(audio["index"])]) codec = audio["codec_name"] bitrate = int(audio3[0]["bit_rate"])/1000 if audio["codec_name"] in nzbtomedia.ACODEC3_ALLOW: audio_cmd.extend(['-c:a:' + str(used_audio), 'copy']) if nzbtomedia.ABITRATE3 and not (nzbtomedia.ABITRATE3 * 0.9 < bitrate < nzbtomedia.ABITRATE3 * 1.1): audio_cmd.extend(['-b:a:' + str(used_audio), str(nzbtomedia.ABITRATE3)]) else: audio_cmd.extend(['-c:a:' + str(used_audio), nzbtomedia.ACODEC3_ALLOW]) if nzbtomedia.ABITRATE3: audio_cmd.extend(['-b:a:' + str(used_audio), str(nzbtomedia.ABITRATE3)]) if nzbtomedia.OUTPUTQUALITYPERCENT > 0: audio_cmd.extend(['-q:a:' + str(used_audio), str(nzbtomedia.OUTPUTQUALITYPERCENT)]) s_mapped = [] subs1 = [] burnt = 0 for lan in nzbtomedia.SLANGUAGES: subs1 = [ item for item in subStreams if item["tags"]["language"] == lan ] if nzbtomedia.BURN and not subs1 and not burnt: for subfile in get_subs(file): if lan in os.path.split(subfile)[1]: video_cmd.extend(['-vf', 'subtitles=' + subfile]) burnt = 1 for sub in subs1: if nzbtomedia.BURN and not burnt: subloc = 0 for index in range(len(subStreams)): if sunStreams[index]["index"] == sub["index"]: subloc = index break video_cmd.extend(['-vf', 'subtitles=' + file + ":si=" + subloc]) burnt = 1 if not nzbtomedia.ALLOWSUBS: break map_cmd.extend(['-map', '0:' + str(sub["index"])]) s_mapped.extend([sub["index"]]) if nzbtomedia.SINCLUDE: for sub in subStreams: if not nzbtomedia.ALLOWSUBS: break if sub["index"] in s_mapped: continue map_cmd.extend(['-map', '0:' + str(sub["index"])]) s_mapped.extend([sub["index"]]) if not nzbtomedia.ALLOWSUBS: sub_cmd.extend('-sn') else: if nzbtomedia.SCODEC: sub_cmd.extend(['-c:s', nzbtomedia.SCODEC]) else: sub_cmd.extend(['-c:s', 'copy']) if nzbtomedia.OUTPUTFASTSTART: other_cmd.extend(['-movflags', '+faststart']) command = [nzbtomedia.FFMPEG, '-loglevel', 'warning', '-i', file] if nzbtomedia.SEMBED: filenum = 1 for subfile in get_subs(file): command.extend(['-i', subfile]) map_cmd.extend(['-map', n]) n += 1 command.extend(map_cmd) command.extend(video_cmd) command.extend(audio_cmd) command.extend(sub_cmd) command.extend(other_cmd) command.append(newfilePath) if platform.system() != 'Windows': command = ['nice', '-%d' % nzbtomedia.NICENESS] + command return command def get_subs(file): filepaths = [] subExt = ['.srt', '.sub', '.idx'] name = os.path.splitext(os.path.split(file)[1])[0] for dirname, dirs, filenames in os.walk(dir): for filename in filenames: filepaths.extend([os.path.join(dirname, filename)]) subfiles = [ item for item in filepaths if os.path.splitext(item)[1] in subExt and name in item ] return subfiles def extract_subs(file, newfilePath, bitbucket): video_details = getVideoDetails(file) if not video_details: return subStreams = [item for item in video_details["streams"] if item["codec_type"] == "subtitle"] if nzbtomedia.SUBSDIR: subdir = nzbtomedia.SUBSDIR else: subdir = os.path.split(newfilePath)[0] name = os.path.splitext(os.path.split(newfilePath)[1])[0] for n in range(len(subStreams)): sub = subStreams[n] lan = sub["tags"]["language"] outputFile = os.path.join(subdir, "%s(%s).srt" %(name, lan)) if os.path.isfile(outputFile): outputFile = os.path.join(subdir, "%s(%s)%s.srt" %(name, n, lan)) command = [nzbtomedia.FFMPEG, '-loglevel', 'warning', '-i', sub, '-vn', '-an', '-codec:s:' + str(n), 'srt', outputFile] if platform.system() != 'Windows': command = ['nice', '-%d' % nzbtomedia.NICENESS] + command logger.info("Extracting %s Subtitle from: %s" % (lan, file)) cmd = "" for item in command: cmd = cmd + " " + item logger.debug("calling command:%s" % (cmd)) result = 1 # set result to failed in case call fails. try: result = call(command, stdout=bitbucket, stderr=bitbucket) except: logger.error("Extracting subtitles has failed") if result == 0: logger.info("Extracting %s Subtitle from %s has succeeded" % (lan, file)) else: logger.error("Extracting subtitles has failed") def Transcode_directory(dirName): if platform.system() == 'Windows': bitbucket = open('NUL') else: bitbucket = open('/dev/null') if not nzbtomedia.FFMPEG: return 1 logger.info("Checking for files to be transcoded") final_result = 0 # initialize as successful if nzbtomedia.OUTPUTVIDEOPATH: newDir = nzbtomedia.OUTPUTVIDEOPATH makeDir(newDir) else: newDir = dirName for file in nzbtomedia.listMediaFiles(dirName): if os.path.splitext(file)[1] in nzbtomedia.IGNOREEXTENSIONS: continue command = buildCommands(file, newDir) newfilePath = command[-1] try: # Try to remove the file that we're transcoding to just in case. (ffmpeg will return an error if it already exists for some reason) os.remove(newfilePath) except OSError, e: if e.errno != errno.ENOENT: # Ignore the error if it's just telling us that the file doesn't exist logger.debug("Error when removing transcoding target: %s" % (e)) except Exception, e: logger.debug("Error when removing transcoding target: %s" % (e)) logger.info("Transcoding video: %s" % (file)) cmd = "" for item in command: cmd = cmd + " " + item logger.debug("calling command:%s" % (cmd)) result = 1 # set result to failed in case call fails. try: result = call(command, stdout=bitbucket, stderr=bitbucket) except: logger.error("Transcoding of video %s has failed" % (file)) if result == 0: logger.info("Transcoding of video %s to %s succeeded" % (file, newfilePath)) if not nzbtomedia.DUPLICATE and os.path.isfile(newfilePath): # we get rid of the original file os.unlink(file) else: logger.error("Transcoding of video %s to %s failed" % (file, newfilePath)) # this will be 0 (successful) it all are successful, else will return a positive integer for failure. final_result = final_result + result if nzbtomedia.SEXTRACT: extract_subs(file, newfilePath, bitbucket) if nzbtomedia.SUBSDIR: for sub in get_subs(file): name = os.path.splitext(os.path.split(file)[1])[0] subname = os.path.split(sub)[1] newname = os.path.splitext(os.path.split(newfilePath)[1])[0] newpath = os.path.join(nzbtomedia.SUBSDIR, subname.replace(name, newname)) if not os.path.isfile(newpath): os.rename(sub, newpath) if not nzbtomedia.PROCESSOUTPUT and not nzbtomedia.DUPLICATE: # We postprocess the original files to CP/SB newDir = dirName return final_result, newDir