def get_norm_volume(self, filename): """ Gets the volume correction of a movie using ffmpeg and sox. Returns without error: norm_vol, None with error: 1.0, error_message """ try: process1 = subprocess.Popen([ path.get_tools_path('intern-ffmpeg'), '-loglevel', 'quiet', '-i', filename, '-f', 'sox', '-' ], stdout=subprocess.PIPE) except OSError: return "1.0", "FFMPEG wurde nicht gefunden!" try: process2 = subprocess.Popen([ path.get_tools_path('intern-sox'), '-p', '--null', 'stat', '-v' ], stdin=process1.stdout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) except OSError: return "1.0", "SOX wurde nicht gefunden!" log = process2.communicate()[0] for line in log.split('\n'): try: return line, None except: return "1.0", "Volume konnte nicht bestimmt werden " + line return None, "Volume konnte nicht bestimmt werden."
def get_norm_volume(self, filename): """ Gets the volume correction of a movie using ffmpeg and sox. Returns without error: norm_vol, None with error: 1.0, error_message """ try: process1 = subprocess.Popen( [path.get_tools_path("intern-ffmpeg"), "-loglevel", "quiet", "-i", filename, "-f", "sox", "-"], stdout=subprocess.PIPE, ) except OSError: return "1.0", "FFMPEG wurde nicht gefunden!" try: process2 = subprocess.Popen( [path.get_tools_path("intern-sox"), "-p", "--null", "stat", "-v"], stdin=process1.stdout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) except OSError: return "1.0", "SOX wurde nicht gefunden!" log = process2.communicate()[0] for line in log.split("\n"): try: return line, None except: return "1.0", "Volume konnte nicht bestimmt werden " + line return None, "Volume konnte nicht bestimmt werden."
def get_norm_volume(self, filename): """ Gets the volume correction of a movie using ffmpeg and sox. Returns without error: norm_vol, None with error: 1.0, error_message """ try: process1 = subprocess.Popen([path.get_tools_path('intern-ffmpeg'), '-loglevel', 'quiet', '-i', filename, '-f', 'sox', '-'], stdout=subprocess.PIPE) except OSError: return "1.0", "FFMPEG wurde nicht gefunden!" try: process2 = subprocess.Popen([path.get_tools_path('intern-sox'), '-p', '--null', 'stat', '-v'], stdin=process1.stdout,stdout=subprocess.PIPE, stderr=subprocess.STDOUT) except OSError: return "1.0", "SOX wurde nicht gefunden!" log = process2.communicate()[0] for line in log.split('\n'): try: return line, None except: return "1.0", "Volume konnte nicht bestimmt werden " + line return None, "Volume konnte nicht bestimmt werden."
def get_norm_volume(self, filename, stream): """ Gets the volume correction of a movie using ffprobe. Returns without error: norm_vol, None with error: 1.0, error_message """ global adjust self.gui.main_window.set_tasks_text('Berechne den Normalisierungswert') self.gui.main_window.set_tasks_progress(0) try: process1 = subprocess.Popen([ path.get_tools_path('intern-ffprobe'), '-v', 'error', '-of', 'compact=p=0:nk=1', '-drc_scale', '1.0', '-show_entries', 'frame_tags=lavfi.r128.I', '-f', 'lavfi', 'amovie=' + filename + ':si=' + stream + ',ebur128=metadata=1' ], stdout=subprocess.PIPE) except OSError: return "1.0", "FFMPEG wurde nicht gefunden!" log = process1.communicate()[0] loudness = ref = -23 for line in log.splitlines(): sline = line.rstrip() if sline: loudness = sline adjust = ref - float(loudness) self.gui.main_window.set_tasks_progress(100) if adjust: return str(adjust) + 'dB', None else: return "1.0", "Volume konnte nicht bestimmt werden."
def get_norm_volume(self, filename, stream): """ Gets the volume correction of a movie using ffprobe. Returns without error: norm_vol, None with error: 1.0, error_message """ self.gui.main_window.set_tasks_text('Berechne den Normalisierungswert') self.gui.main_window.set_tasks_progress(0) try: process1 = subprocess.Popen([path.get_tools_path('intern-ffprobe'), '-v', 'error','-of','compact=p=0:nk=1','-drc_scale','1.0','-show_entries','frame_tags=lavfi.r128.I','-f','lavfi','amovie='+filename+':si='+stream+',ebur128=metadata=1'], stdout=subprocess.PIPE) except OSError: return "1.0", "FFMPEG wurde nicht gefunden!" log = process1.communicate()[0] loudness = ref = -23 for line in log.splitlines(): sline = line.rstrip() if sline: loudness = sline adjust = ref - float(loudness) self.gui.main_window.set_tasks_progress(100) if adjust: return str(adjust)+'dB', None else: return "1.0", "Volume konnte nicht bestimmt werden."
def mp4(): # env my_env = os.environ.copy() my_env["LANG"] = "C" for count, filename in enumerate(filenames): # analyse file cutter = Cut(self.app, self.gui) fps, dar, sar, max_frames, ac3_stream, error = cutter.analyse_mediafile(filename) if fps == None: self.errors[filename] = error continue # mkvmerge pass yield 0, count self.progress = 0 if os.path.splitext(filename)[1] != ".mkv": mkvpass_file = fileoperations.make_unique_filename(os.path.splitext(filename)[0] + "_remux.mkv") try: p = subprocess.Popen( [ self.app.config.get_program("mkvmerge"), "--ui-language", "en_US", "-o", mkvpass_file, filename, ], stdout=subprocess.PIPE, env=my_env, ) except OSError: self.errors[filename] = "MKVmerge wurde nicht gefunden!" continue p.stdout.readline() line = "" while p.poll() == None: # read progress from stdout char = p.stdout.read(1) line += char progress = "" if char == ":": if "Error" in line or "Warning" in line: break while char != "%": char = p.stdout.read(1) progress += char try: self.progress = int(progress.strip(" %")) yield 4, self.progress except ValueError: pass exit_code = p.poll() if exit_code == 0 or exit_code == 1: pass else: error = p.stdout.readline() if os.path.exists(mkvpass_file): fileoperations.remove_file(mkvpass_file) try: error = error.split(":")[1] except IndexError: pass if "unknown type" in error: error = "Datei konnte nicht gelesen werden." self.errors[filename] = error continue else: mkvpass_file = filename # norm volume ausrechnen yield 5, count if self.Config["NormalizeAudio"] and self.Config["EncodeAudioToAAC"]: vol, error = self.get_norm_volume(filename) else: vol = 1.0 # ffmpeg pass yield 1, count self.progress = 0 ffmpegpass_file = fileoperations.make_unique_filename(os.path.splitext(filename)[0] + "_remux.mp4") if self.Config["EncodeAudioToAAC"]: if self.Config["EncodeOnlyFirstAudioToAAC"]: aacaudiostreams = "-c:a:0" else: aacaudiostreams = "-c:a" # convert first audio stream to aac ffmpeg = self.app.config.get_program("ffmpeg") if "nonfree" in ffmpeg: # nonfree ffmpeg version with fdk support available audiocodec = [ "-c:a", "copy", aacaudiostreams, "libfdk_aac", "-flags", "+qscale", "-profile:a:0", "aac_low", "-global_quality", "5", "-afterburner", "1", ] else: # only gpl version of ffmpeg available -> use standard aac codec audiocodec = [ "-c:a", "copy", aacaudiostreams, "aac", "-strict", "-2", "-profile:a:0", "aac_low", "-ab", "192k", "-cutoff", "18000", ] else: # only copy audio ffmpeg = path.get_tools_path("intern-ffmpeg") audiocodec = ["-c:a", "copy"] if self.Config["DownMixStereo"] and self.Config["EncodeAudioToAAC"]: audiocodec.extend(["-ac:0", "2"]) if ac3_stream == None: # no ac3 stream found - all streams are muxed map = ["-map", "0"] else: if self.Config["RemoveOtherAudioStreamsThanAC3"]: # mux only video and ac3 stream map = ["-map", "0:v", "-map", ac3_stream] else: map = ["-map", "0"] args = [ ffmpeg, "-loglevel", "info", "-y", "-drc_scale", "1.0", "-i", mkvpass_file, "-vcodec", "copy", "-af", "volume=volume=" + str(vol), "-vsync", "1", "-async", "1000", "-dts_delta_threshold", "100", "-vf", "fps=" + str(fps), ffmpegpass_file, ] map.extend(audiocodec) args[8:8] = map try: p = subprocess.Popen(args, stderr=subprocess.PIPE, universal_newlines=True) except OSError: self.errors[filename] = "FFMPEG (intern) wurde nicht gefunden!" if os.path.exists(mkvpass_file) and filename != mkvpass_file: fileoperations.remove_file(mkvpass_file) continue yield 4, 0 line = "" infos_match = re.compile(r"frame=\ {0,1}(\d{1,})") while p.poll() == None: line = p.stderr.readline() m = re.search(infos_match, line) if m and max_frames != 0: next = float(float(m.group(1)) / float(max_frames)) * 100 if next > self.progress: self.progress = next yield 4, self.progress else: pass exit_code = p.poll() if os.path.exists(mkvpass_file) and filename != mkvpass_file: fileoperations.remove_file(mkvpass_file) if exit_code == 0: if self.Config["DumpAVIs"]: yield 3, self.success new_filename = os.path.join( self.app.config.get("general", "folder_trash_avis"), os.path.basename(filename) ) if os.path.exists(new_filename): fileoperations.remove_file(new_filename) fileoperations.move_file(filename, self.app.config.get("general", "folder_trash_avis")) else: self.errors[filename] = "Fehler beim Erzeugen der MP4 Datei durch FFMPEG" if os.path.exists(ffmpegpass_file): fileoperations.remove_file(ffmpegpass_file) continue # mp4box - last turn self.progress = 0 mp4boxpass_file = fileoperations.make_unique_filename(os.path.splitext(filename)[0] + ".mp4") if self.Config["DontOptimizeMP4"]: os.rename(ffmpegpass_file, mp4boxpass_file) self.success += 1 continue yield 2, count try: p = subprocess.Popen( [ self.app.config.get_program("mp4box"), "-keep-all", "-new", "-packed", "-fps", str(fps), "-add", ffmpegpass_file, mp4boxpass_file, ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) except OSError: self.errors[filename] = "MP4Box (intern) wurde nicht gefunden!" if os.path.exists(ffmpegpass_file): fileoperations.remove_file(ffmpegpass_file) continue yield 4, 0 infos_match = re.compile(r".*\((\d{2,})\/\d{2,}\).*") while p.poll() == None: line = p.stdout.read(60) m = re.search(infos_match, line) if m: self.progress = int(m.group(1)) yield 4, self.progress if "Importing" in line: yield 2, count elif "Writing" in line: yield 6, count else: pass exit_code = p.poll() if os.path.exists(ffmpegpass_file): fileoperations.remove_file(ffmpegpass_file) if exit_code == 0: self.success += 1 else: self.errors[filename] = "Fehler beim Erzeugen der MP4 Datei durch MP4Box"
def cut_file_by_cutlist(self, filename, cutlist=None, program_config_value=None): """ Cuts a otr file with x264 and mkvmerge frame accurate. returns: name of cut video, error_message """ # configuration videolist = [] # result list for smart rendering simulation audio_import_files = [filename] # otr files which have audio streams and needs to be cutted (e.g. OTR avi and ac3) process_list = [] # list of started processes mkvmerge_list = [] # list of started mkvmerge processes video_splitframes = '' # mkvmerge split string for cutting the video at keyframes audio_timecodes = '' # mkvmerge split timecodes for cutting the audio ac3_file = None # AC3 source file mkvmerge = self.config.get_program('mkvmerge') x264 = self.config.get_program('x264') # env my_env = os.environ.copy() my_env["LANG"] = "C" my_env["LC_COLLATE"] = "C" # x264 option string format, ac3_file = self.get_format(filename) if format == Format.HQ: x264_opts, x264_core = self.complete_x264_opts(self.config.get('smartmkvmerge', 'x264_hq_string').split(' '), filename) elif format == Format.HD: x264_opts, x264_core = self.complete_x264_opts(self.config.get('smartmkvmerge', 'x264_hd_string').split(' '), filename) elif format == Format.MP4: x264_opts, x264_core = self.complete_x264_opts(self.config.get('smartmkvmerge', 'x264_mp4_string').split(' '), filename) else: return None, "Format nicht unterstützt (Nur MP4 H264, HQ H264 und HD H264 sind möglich)." logging.debug(x264_opts) if x264_core < 122: return None, "Alte HQ Kodierung entdeckt. Diese Datei bitte mit intern-Virtualdub und Codec ffdshow schneiden." # test workingdir if os.access(self.config.get('smartmkvmerge', 'workingdir').rstrip('/'), os.W_OK): self.workingdir = os.path.abspath(self.config.get('smartmkvmerge', 'workingdir')).rstrip('/') else: return None, "Ungültiges Temp Verzeichnis. Schreiben nicht möglich." # audio part 1 - cut audio if ac3_file: audio_import_files.append(ac3_file) audio_timecodes = (',+'.join([self.get_timecode(start) + '-' + self.get_timecode(start+duration) for start, duration in cutlist.cuts_seconds])) audio_timecodes = audio_timecodes.lstrip(',+') command = [mkvmerge, '--ui-language', 'en_US', '-D', '--split', 'parts:'+audio_timecodes, '-o', self.workingdir + '/audio_copy.mkv'] + audio_import_files logging.debug(command) try: blocking_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, env=my_env) except OSError as e: return None, e.strerror + ": " + mkvmerge mkvmerge_list.append(blocking_process) # video part 1 - read keyframes keyframes, error = self.get_keyframes_from_file(filename) if keyframes == None: return None, "Keyframes konnten nicht ausgelesen werden." logging.debug(keyframes) # video part 2 - simulate smart rendering process for frame_start, frames_duration in cutlist.cuts_frames: result = self.__simulate_smart_mkvmerge(int(frame_start), int(frames_duration), keyframes) if result != None: videolist += result else: return None, 'Cutlist oder zu schneidende Datei passen nicht zusammen oder sind fehlerhaft.' logging.debug(videolist) # video part 3 - encode small parts - smart rendering part (1/2) for encode, start, duration, video_part_filename in videolist: self.video_files.append('+'+ self.workingdir +'/' + video_part_filename) command = [x264] + x264_opts + ['--demuxer','ffms','--index', self.workingdir + '/x264.index','--seek', str(start),'--frames', str(duration), '--output', self.workingdir + '/' + video_part_filename, filename ] logging.debug(command) if encode: try: non_blocking_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) except OSError as e: return None, e.strerror + ": " + x264 process_list.append(non_blocking_process) else: video_splitframes += ','+str(start)+'-'+str(duration) self.video_files[0]=self.video_files[0].lstrip('+') video_splitframes = video_splitframes.lstrip(',') # video part 4 - cut the big parts out the file (keyframe accurate) - smart rendering part (2/2) command = [mkvmerge, '--ui-language', 'en_US','-A', '--split', 'parts-frames:'+video_splitframes, '-o', self.workingdir + '/video_copy.mkv', filename ] logging.debug(command) try: non_blocking_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, env=my_env) except OSError as e: return None, e.strerror + ": " + mkvmerge mkvmerge_list.append(non_blocking_process) # audio part 2 - encode audio to AAC if 'MP3 Spur kopieren' in self.config.get('smartmkvmerge', 'first_audio_stream') and 'AC3 Spur kopieren' in self.config.get('smartmkvmerge', 'second_audio_stream'): self.audio_files.append(self.workingdir + '/audio_copy.mkv') else: blocking_process.wait() self.show_progress(blocking_process) ffmpeginput_file = self.workingdir + '/audio_copy.mkv' ffmpegoutput_file = self.workingdir + '/audio_encode.mkv' audiofilter = ['-af', 'anull'] # convert first audio stream to aac if 'AAC' in self.config.get('smartmkvmerge', 'first_audio_stream') and 'AAC' in self.config.get('smartmkvmerge', 'second_audio_stream'): aacaudiostreams = '-c:a' if self.config.get('smartmkvmerge', 'normalize_audio'): vol0, error = self.get_norm_volume(ffmpeginput_file, '0') vol1, error = self.get_norm_volume(ffmpeginput_file, '1') audiofilter = ['-af:0', 'volume=volume=' + vol0, '-af:1', 'volume=volume=' + vol1] elif 'AAC' in self.config.get('smartmkvmerge', 'second_audio_stream') and 'MP3' in self.config.get('smartmkvmerge', 'first_audio_stream'): aacaudiostreams = '-c:a:1' if self.config.get('smartmkvmerge', 'normalize_audio'): vol, error = self.get_norm_volume(ffmpeginput_file, '1') audiofilter = ['-af:1', 'volume=volume=' + vol] elif 'AAC' in self.config.get('smartmkvmerge', 'first_audio_stream'): aacaudiostreams = '-c:a:0' if self.config.get('smartmkvmerge', 'normalize_audio'): vol, error = self.get_norm_volume(ffmpeginput_file, '0') audiofilter = ['-af:0', 'volume=volume=' + vol] else: aacaudiostreams = '-c:a:2' ffmpeg = self.config.get_program('ffmpeg') if 'nonfree' in ffmpeg: # nonfree ffmpeg version with fdk support available audiocodec = ['-c:a', 'copy', aacaudiostreams, 'libfdk_aac', '-flags', '+qscale', '-profile:a', 'aac_low', '-global_quality', '5' ,'-afterburner', '1'] else: # only gpl version of ffmpeg available -> use standard aac codec audiocodec = ['-c:a', 'copy', aacaudiostreams, 'aac', '-strict', '-2','-profile:a', 'aac_low', '-ab' ,'192k', '-cutoff', '18000'] if '2-Kanal' in self.config.get('smartmkvmerge', 'first_audio_stream'): audiocodec.extend(['-ac:0', '2']) if ac3_file == None: # no ac3 stream found - all streams are muxed map = ['-map', '0'] else: if 'AC3' in self.config.get('smartmkvmerge', 'first_audio_stream') : map = ['-map', '0:a:1'] else: map = ['-map', '0:a:0'] if not 'AC3 Spur entfernen' in self.config.get('smartmkvmerge', 'second_audio_stream') : map.extend(['-map', '0:a:1']) args = [ffmpeg, "-loglevel", "info", "-y", "-drc_scale", "1.0", "-i", ffmpeginput_file, "-vn", "-vsync", "1", '-async', '200000', "-dts_delta_threshold", "100", '-threads', '0', ffmpegoutput_file] map.extend(audiocodec) map.extend(audiofilter) args[8:8] = map logging.debug(args) try: non_blocking_process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) except OSError as e: return None, e.strerror + ": " + ffmpeg process_list.append(non_blocking_process) self.audio_files.append(self.workingdir + '/audio_encode.mkv') # wait until all threads are terminated for blocking_process in mkvmerge_list + process_list: self.show_progress(blocking_process) # check all processes for blocking_process in mkvmerge_list: returncode = blocking_process.wait() if returncode != 0 and returncode != 1: return None, 'beim Schneiden der Originaldatei...' for blocking_process in process_list: returncode = blocking_process.wait() if returncode != 0: return None, 'beim Kodieren ...' # clean up if os.path.isfile (self.workingdir + '/video_copy.mkv'): os.rename(self.workingdir + '/video_copy.mkv', self.workingdir + '/video_copy-001.mkv') if vars().has_key('ffmpeginput_file'): if os.path.isfile (ffmpeginput_file): os.remove(ffmpeginput_file) # mux all together if self.config.get('smartmkvmerge', 'remux_to_mp4'): cut_video = self.workingdir + '/' + os.path.basename(os.path.splitext(self.generate_filename((filename),1))[0] + ".mkv") else: cut_video = os.path.splitext(self.generate_filename(filename,1))[0] + ".mkv" command = [mkvmerge, '--engage', 'no_cue_duration', '--engage', 'no_cue_relative_position', '--ui-language', 'en_US', '-o', cut_video] + self.video_files + self.audio_files logging.debug(command) try: blocking_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, env=my_env) except OSError: return None, "MKVMerge konnte nicht aufgerufen werden oder zu alt (6.5.0 benötigt)" self.show_progress(blocking_process) returncode = blocking_process.wait() if returncode != 0 and returncode != 1: return None, 'beim Schreiben des geschnittenen MKVs...' # remove all temporary files for n in self.video_files + self.audio_files: if os.path.isfile(n.lstrip('+')): os.remove(n.lstrip('+')) # mux to mp4 if self.config.get('smartmkvmerge', 'remux_to_mp4'): # split files with eac3to with ChangeDir(self.workingdir): stdout_encoding = sys.stdout.encoding or sys.getfilesystemencoding() command = ['wine', path.get_tools_path('intern-eac3to/eac3to.exe'), os.path.basename(cut_video), '-demux', '-silence', '-keepDialnorm'] logging.debug(command) try: blocking_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) except OSError: return None, 'Eac3to konnte nicht aufgerufen werden' file_match = re.compile(r".*\"(.* - (\d{1,}) - .*)\".*") self.gui.main_window.set_tasks_text('Extrahiere Streams') self.gui.main_window.set_tasks_progress(50) while events_pending(): main_iteration(False) while blocking_process.poll() == None: line = blocking_process.stdout.readline().strip() if 'Creating file' in line: m = re.search(file_match,line) if m: self.rawstreams[m.group(2)] = m.group(1).decode("iso-8859-1").encode("utf-8") else: pass returncode = blocking_process.wait() if returncode != 0: if os.path.isfile(cut_video): os.remove(cut_video) return None, 'Fehler beim Extrahieren der Streams mit Eac3to' # remove mkv + log file if os.path.isfile(cut_video): os.remove(cut_video) if os.path.isfile(os.path.splitext(cut_video)[0]+ ' - Log.txt'): os.remove(os.path.splitext(cut_video)[0]+ ' - Log.txt') args = [self.config.get_program('mp4box'), '-new', '-keep-all'] for index in sorted(self.rawstreams.keys()): args.append('-add') args.append(self.rawstreams[index]) cut_video = os.path.splitext(self.generate_filename(filename,1))[0] + ".mp4" args.append(cut_video) # mux to mp4 (mp4box) logging.debug(args) try: blocking_process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) except OSError: return None, 'MP4Box konnte nicht aufgerufen werden' self.gui.main_window.set_tasks_text('Muxe MP4') self.show_progress(blocking_process) returncode = blocking_process.wait() if returncode != 0: return None, 'Fehler beim Erstellen der MP4' return cut_video, None
def mp4(): # env my_env = os.environ.copy() my_env["LANG"] = "C" for count, filename in enumerate(filenames): # analyse file cutter = Cut(self.app, self.gui) fps, dar, sar, max_frames, ac3_stream, error = cutter.analyse_mediafile( filename) if fps == None: self.errors[filename] = error continue # mkvmerge pass yield 0, count self.progress = 0 if os.path.splitext(filename)[1] != '.mkv': mkvpass_file = fileoperations.make_unique_filename( os.path.splitext(filename)[0] + "_remux.mkv") try: p = subprocess.Popen([ self.app.config.get_program('mkvmerge'), '--ui-language', 'en_US', "-o", mkvpass_file, filename ], stdout=subprocess.PIPE, env=my_env) except OSError: self.errors[ filename] = "MKVmerge wurde nicht gefunden!" continue p.stdout.readline() line = "" while p.poll() == None: # read progress from stdout char = p.stdout.read(1) line += char.decode('utf-8') progress = '' if char == ':': if "Error" in line or "Warning" in line: break while char != '%': char = p.stdout.read(1) progress += char try: self.progress = int(progress.strip(' %')) yield 4, self.progress except ValueError: pass exit_code = p.poll() if exit_code == 0 or exit_code == 1: pass else: error = p.stdout.readline() if os.path.exists(mkvpass_file): fileoperations.remove_file(mkvpass_file) try: error = error.split(":")[1] except IndexError: pass if "unknown type" in error: error = "Datei konnte nicht gelesen werden." self.errors[filename] = error continue else: mkvpass_file = filename # norm volume ausrechnen yield 5, count if self.Config['NormalizeAudio'] and self.Config[ 'EncodeAudioToAAC']: vol, error = self.get_norm_volume(filename) else: vol = 1.0 # ffmpeg pass yield 1, count self.progress = 0 ffmpegpass_file = fileoperations.make_unique_filename( os.path.splitext(filename)[0] + "_remux.mp4") if self.Config['EncodeAudioToAAC']: if self.Config['EncodeOnlyFirstAudioToAAC']: aacaudiostreams = '-c:a:0' else: aacaudiostreams = '-c:a' # convert first audio stream to aac ffmpeg = self.app.config.get_program('ffmpeg') if 'nonfree' in ffmpeg: # nonfree ffmpeg version with fdk support available audiocodec = [ '-c:a', 'copy', aacaudiostreams, 'libfdk_aac', '-flags', '+qscale', '-profile:a:0', 'aac_low', '-global_quality', '5', '-afterburner', '1' ] else: # only gpl version of ffmpeg available -> use standard aac codec audiocodec = [ '-c:a', 'copy', aacaudiostreams, 'aac', '-strict', '-2', '-profile:a:0', 'aac_low', '-ab', '192k', '-cutoff', '18000' ] else: # only copy audio ffmpeg = path.get_tools_path('intern-ffmpeg') audiocodec = ['-c:a', 'copy'] if self.Config['DownMixStereo'] and self.Config[ 'EncodeAudioToAAC']: audiocodec.extend(['-ac:0', '2']) if ac3_stream == None: # no ac3 stream found - all streams are muxed map = ['-map', '0'] else: if self.Config['RemoveOtherAudioStreamsThanAC3']: # mux only video and ac3 stream map = ['-map', '0:v', '-map', ac3_stream] else: map = ['-map', '0'] args = [ ffmpeg, "-loglevel", "info", "-y", "-drc_scale", "1.0", "-i", mkvpass_file, "-vcodec", "copy", '-af', 'volume=volume=' + str(vol), "-vsync", "1", '-async', '1000', "-dts_delta_threshold", "100", "-vf", "fps=" + str(fps), ffmpegpass_file ] map.extend(audiocodec) args[8:8] = map try: p = subprocess.Popen(args, stderr=subprocess.PIPE, universal_newlines=True) except OSError: self.errors[ filename] = "FFMPEG (intern) wurde nicht gefunden!" if os.path.exists( mkvpass_file) and filename != mkvpass_file: fileoperations.remove_file(mkvpass_file) continue yield 4, 0 line = "" infos_match = re.compile(r"frame=\ {0,1}(\d{1,})") while p.poll() == None: line = p.stderr.readline() m = re.search(infos_match, line) if m and max_frames != 0: next = float( float(m.group(1)) / float(max_frames)) * 100 if next > self.progress: self.progress = next yield 4, self.progress else: pass exit_code = p.poll() if os.path.exists(mkvpass_file) and filename != mkvpass_file: fileoperations.remove_file(mkvpass_file) if exit_code == 0: if self.Config['DumpAVIs']: yield 3, self.success new_filename = os.path.join( self.app.config.get('general', 'folder_trash_avis'), os.path.basename(filename)) if os.path.exists(new_filename): fileoperations.remove_file(new_filename) fileoperations.move_file( filename, self.app.config.get('general', 'folder_trash_avis')) else: self.errors[ filename] = "Fehler beim Erzeugen der MP4 Datei durch FFMPEG" if os.path.exists(ffmpegpass_file): fileoperations.remove_file(ffmpegpass_file) continue # mp4box - last turn self.progress = 0 mp4boxpass_file = fileoperations.make_unique_filename( os.path.splitext(filename)[0] + ".mp4") if self.Config['DontOptimizeMP4']: os.rename(ffmpegpass_file, mp4boxpass_file) self.success += 1 continue yield 2, count try: p = subprocess.Popen([ self.app.config.get_program('mp4box'), "-keep-all", "-new", "-packed", "-fps", str(fps), "-add", ffmpegpass_file, mp4boxpass_file ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) except OSError: self.errors[ filename] = "MP4Box (intern) wurde nicht gefunden!" if os.path.exists(ffmpegpass_file): fileoperations.remove_file(ffmpegpass_file) continue yield 4, 0 infos_match = re.compile(r".*\((\d{2,})\/\d{2,}\).*") while p.poll() == None: line = p.stdout.read(60) line = line.decode('utf-8') m = re.search(infos_match, line) if m: self.progress = int(m.group(1)) yield 4, self.progress if 'Importing' in line: yield 2, count elif 'Writing' in line: yield 6, count else: pass exit_code = p.poll() if os.path.exists(ffmpegpass_file): fileoperations.remove_file(ffmpegpass_file) if exit_code == 0: self.success += 1 else: self.errors[ filename] = "Fehler beim Erzeugen der MP4 Datei durch MP4Box"
def cut_file_by_cutlist(self, filename, cutlist=None, program_config_value=None): """ Cuts a otr file with x264 and mkvmerge frame accurate. returns: name of cut video, error_message """ # configuration videolist = [] # result list for smart rendering simulation audio_import_files = [ filename ] # otr files which have audio streams and needs to be cutted (e.g. OTR avi and ac3) process_list = [] # list of started processes mkvmerge_list = [] # list of started mkvmerge processes video_splitframes = '' # mkvmerge split string for cutting the video at keyframes audio_timecodes = '' # mkvmerge split timecodes for cutting the audio ac3_file = None # AC3 source file warning_msg = None mkvmerge = self.config.get_program('mkvmerge') x264 = self.config.get_program('x264') ffmpeg = self.config.get_program('ffmpeg') encoder_engine = self.config.get('smartmkvmerge', 'encoder_engine') # env my_env = os.environ.copy() my_env["LANG"] = "C" my_env["LC_COLLATE"] = "C" my_env["LC_ALL"] = "C" # analyse file fps, dar, sar, max_frames, ac3_stream, error = self.analyse_mediafile( filename) if error: return None, "Konnte FPS nicht bestimmen: " + error # codec configuration string format, ac3_file, bframe_delay = self.get_format(filename) if format == Format.HQ: if encoder_engine == 'x264': codec, codec_core = self.complete_x264_opts( self.config.get('smartmkvmerge', 'x264_hq_string').split(' '), filename) elif encoder_engine == 'ffmpeg': codec, codec_core = self.__ffmpeg_codec_options( self.config.get('smartmkvmerge', 'ffmpeg_hq_x264_options').split(' '), filename) elif format == Format.HD: if encoder_engine == 'x264': codec, codec_core = self.complete_x264_opts( self.config.get('smartmkvmerge', 'x264_hd_string').split(' '), filename) elif encoder_engine == 'ffmpeg': codec, codec_core = self.__ffmpeg_codec_options( self.config.get('smartmkvmerge', 'ffmpeg_hd_x264_options').split(' '), filename) elif format == Format.MP4: if encoder_engine == 'x264': codec, codec_core = self.complete_x264_opts( self.config.get('smartmkvmerge', 'x264_mp4_string').split(' '), filename) elif encoder_engine == 'ffmpeg': codec, codec_core = self.__ffmpeg_codec_options( self.config.get('smartmkvmerge', 'ffmpeg_mp4_x264_options').split(' '), filename) elif format == Format.AVI: encoder_engine = 'ffmpeg' codec = self.config.get('smartmkvmerge', 'ffmpeg_avi_mpeg4_options').split(' ') codec_core = 125 else: return None, "Format nicht unterstützt (Nur MP4 H264, HQ H264 und HD H264 sind möglich)." self.log.debug("Codec: {}".format(codec)) if codec_core != 125: warning_msg = "Unbekannte Kodierung entdeckt. Diese Datei genau prüfen und notfalls mit intern-Virtualdub und Codec ffdshow schneiden." return None, warning_msg # test workingdir if os.access( self.config.get('smartmkvmerge', 'workingdir').rstrip('/'), os.W_OK): self.workingdir = os.path.abspath( self.config.get('smartmkvmerge', 'workingdir')).rstrip('/') else: return None, "Ungültiges Temp Verzeichnis. Schreiben nicht möglich." # threads flag_singlethread = self.config.get('smartmkvmerge', 'single_threaded') if self.config.get('smartmkvmerge', 'single_threaded_automatic'): try: memory = self.meminfo() if self.available_cpu_count() > 1 and memory['MemFree'] > ( os.stat(filename).st_size / 1024): flag_singlethread = True else: flag_singlethread = False except Exception as e: flag_singlethread = self.config.get('smartmkvmerge', 'single_threaded') self.log.debug("flag_singlethread: {}".format(flag_singlethread)) # audio part 1 - cut audio if ac3_file: audio_import_files.append(ac3_file) audio_timecodes = (',+'.join([ self.get_timecode(start) + '-' + self.get_timecode(start + duration) for start, duration in cutlist.cuts_seconds ])) audio_timecodes = audio_timecodes.lstrip(',+') command = [ mkvmerge, '--ui-language', 'en_US', '-D', '--split', 'parts:' + audio_timecodes, '-o', self.workingdir + '/audio_copy.mkv' ] + audio_import_files self.log.debug("Command: {}".format(command)) try: blocking_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, env=my_env) except OSError as e: return None, e.strerror + ": " + mkvmerge mkvmerge_list.append(blocking_process) if flag_singlethread: self.show_progress(blocking_process) # video part 1 - read keyframes keyframes, error = self.get_keyframes_from_file(filename) if keyframes == None: return None, "Keyframes konnten nicht ausgelesen werden." # self.log.debug(keyframes) # video part 2 - simulate smart rendering process for frame_start, frames_duration in cutlist.cuts_frames: result = self.__simulate_smart_mkvmerge(int(frame_start), int(frames_duration), keyframes) if result != None: videolist += result else: return None, 'Cutlist oder zu schneidende Datei passen nicht zusammen oder sind fehlerhaft.' self.log.debug("Videolist: {}".format(videolist)) # video part 3 - encode small parts - smart rendering part (1/2) for encode, start, duration, video_part_filename in videolist: self.video_files.append('+' + self.workingdir + '/' + video_part_filename) if encoder_engine == 'x264': command = [x264] + codec + [ '--demuxer', 'ffms', '--index', self.workingdir + '/x264.index', '--seek', str(start), '--frames', str(duration), '--output', self.workingdir + '/' + video_part_filename, filename ] elif encoder_engine == 'ffmpeg': command = [ ffmpeg, '-ss', str(self.get_timecode((start + bframe_delay) / fps)), '-i', filename, '-vframes', str(duration), '-vf', 'setsar=' + str(sar), '-threads', '0', '-an', '-sn', '-dn', '-y', self.workingdir + '/' + video_part_filename ] command[5:5] = codec else: return None, "Keine unterstützte Render-Engine zum Kodieren eingestellt" self.log.debug("Command: {}".format(command)) if encode: try: non_blocking_process = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) except OSError as e: return None, e.strerror + ": " + 'Render Engine nicht vorhanden' process_list.append(non_blocking_process) if flag_singlethread: self.show_progress(non_blocking_process) else: video_splitframes += ',' + str(start) + '-' + str(duration) self.video_files[0] = self.video_files[0].lstrip('+') video_splitframes = video_splitframes.lstrip(',') # video part 4 - cut the big parts out the file (keyframe accurate) - smart rendering part (2/2) command = [ mkvmerge, '--ui-language', 'en_US', '-A', '--split', 'parts-frames:' + video_splitframes, '-o', self.workingdir + '/video_copy.mkv', filename ] self.log.debug("Command: {}".format(command)) try: non_blocking_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, env=my_env) except OSError as e: return None, e.strerror + ": " + mkvmerge mkvmerge_list.append(non_blocking_process) if flag_singlethread: self.show_progress(non_blocking_process) # audio part 2 - encode audio to AAC if 'MP3 Spur kopieren' in self.config.get( 'smartmkvmerge', 'first_audio_stream' ) and 'AC3 Spur kopieren' in self.config.get('smartmkvmerge', 'second_audio_stream'): self.audio_files.append(self.workingdir + '/audio_copy.mkv') else: self.show_progress(blocking_process) blocking_process.wait() ffmpeginput_file = self.workingdir + '/audio_copy.mkv' ffmpegoutput_file = self.workingdir + '/audio_encode.mkv' audiofilter = [] # convert first audio stream to aac if 'AAC' in self.config.get( 'smartmkvmerge', 'first_audio_stream') and 'AAC' in self.config.get( 'smartmkvmerge', 'second_audio_stream'): aacaudiostreams = '-c:a' if self.config.get('smartmkvmerge', 'normalize_audio'): vol0, error = self.get_norm_volume(ffmpeginput_file, '0') vol1, error = self.get_norm_volume(ffmpeginput_file, '1') audiofilter = [ '-af:0', 'volume=volume=' + vol0, '-af:1', 'volume=volume=' + vol1 ] elif 'AAC' in self.config.get( 'smartmkvmerge', 'second_audio_stream') and 'MP3' in self.config.get( 'smartmkvmerge', 'first_audio_stream'): aacaudiostreams = '-c:a:1' if self.config.get('smartmkvmerge', 'normalize_audio'): vol, error = self.get_norm_volume(ffmpeginput_file, '1') audiofilter = ['-af:1', 'volume=volume=' + vol] elif 'AAC' in self.config.get('smartmkvmerge', 'first_audio_stream'): aacaudiostreams = '-c:a:0' if self.config.get('smartmkvmerge', 'normalize_audio'): vol, error = self.get_norm_volume(ffmpeginput_file, '0') audiofilter = ['-af:0', 'volume=volume=' + vol] else: aacaudiostreams = '-c:a:2' if 'nonfree' in ffmpeg: # nonfree ffmpeg version with fdk support available audiocodec = [ '-c:a', 'copy', aacaudiostreams, 'libfdk_aac', '-flags', '+qscale', '-profile:a', 'aac_low', '-global_quality', '5', '-afterburner', '1' ] else: # only gpl version of ffmpeg available -> use standard aac codec audiocodec = [ '-c:a', 'copy', aacaudiostreams, 'aac', '-strict', '-2', '-profile:a', 'aac_low', '-ab', '192k', '-cutoff', '18000' ] if '2-Kanal' in self.config.get('smartmkvmerge', 'first_audio_stream'): audiocodec.extend(['-ac:0', '2']) if ac3_file == None: # no ac3 stream found - all streams are muxed map = ['-map', '0'] else: if 'AC3' in self.config.get('smartmkvmerge', 'first_audio_stream'): map = ['-map', '0:a:1'] else: map = ['-map', '0:a:0'] if not 'AC3 Spur entfernen' in self.config.get( 'smartmkvmerge', 'second_audio_stream'): map.extend(['-map', '0:a:1']) args = [ ffmpeg, "-loglevel", "info", "-y", "-drc_scale", "1.0", "-i", ffmpeginput_file, "-vn", "-vsync", "1", '-async', '200000', "-dts_delta_threshold", "100", '-threads', '0', ffmpegoutput_file ] map.extend(audiocodec) map.extend(audiofilter) args[8:8] = map self.log.debug("Args: {}".format(args)) try: non_blocking_process = subprocess.Popen( args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) except OSError as e: return None, e.strerror + ": " + ffmpeg process_list.append(non_blocking_process) self.audio_files.append(self.workingdir + '/audio_encode.mkv') if flag_singlethread: self.show_progress(non_blocking_process) # wait until all threads are terminated for blocking_process in mkvmerge_list + process_list: self.show_progress(blocking_process) # check all processes for blocking_process in mkvmerge_list: returncode = blocking_process.wait() if returncode != 0 and returncode != 1: return None, 'beim Schneiden der Originaldatei...' for blocking_process in process_list: returncode = blocking_process.wait() if returncode != 0: return None, 'beim Kodieren ...' # clean up if os.path.isfile(self.workingdir + '/video_copy.mkv'): os.rename(self.workingdir + '/video_copy.mkv', self.workingdir + '/video_copy-001.mkv') if 'ffmpeginput_file' in vars(): if os.path.isfile(ffmpeginput_file): os.remove(ffmpeginput_file) # mux all together if self.config.get('smartmkvmerge', 'remux_to_mp4'): cut_video = self.workingdir + '/' + os.path.basename( os.path.splitext(self.generate_filename((filename), 1))[0] + ".mkv") else: cut_video = os.path.splitext(self.generate_filename(filename, 1))[0] + ".mkv" command = [ mkvmerge, '--engage', 'no_cue_duration', '--engage', 'no_cue_relative_position', '--ui-language', 'en_US', '-o', cut_video ] + self.video_files + self.audio_files self.log.debug("Command: {}".format(command)) try: blocking_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, env=my_env) except OSError: return None, "MKVMerge konnte nicht aufgerufen werden oder zu alt (6.5.0 benötigt)" self.show_progress(blocking_process) returncode = blocking_process.wait() if returncode != 0 and returncode != 1: return None, 'beim Schreiben des geschnittenen MKVs...' # remove all temporary files for n in self.video_files + self.audio_files: if os.path.isfile(n.lstrip('+')): os.remove(n.lstrip('+')) # mux to mp4 if self.config.get('smartmkvmerge', 'remux_to_mp4'): # split files with eac3to with ChangeDir(self.workingdir): command = [ 'wine', path.get_tools_path('intern-eac3to/eac3to.exe'), os.path.basename(cut_video), '-demux', '-silence', '-keepDialnorm' ] self.log.debug("Command: {}".format(command)) try: blocking_process = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) except OSError: return None, 'Eac3to konnte nicht aufgerufen werden' file_match = re.compile(r".*\"(.* - (\d{1,}) - .*)\".*") self.gui.main_window.set_tasks_text('Extrahiere Streams') self.gui.main_window.set_tasks_progress(50) while Gtk.events_pending(): Gtk.main_iteration() while blocking_process.poll() == None: line = blocking_process.stdout.readline().strip() if 'Creating file' in line: m = re.search(file_match, line) if m: self.rawstreams[m.group(2)] = m.group(1).decode( "iso-8859-1").encode("utf-8") else: pass returncode = blocking_process.wait() if returncode != 0: if os.path.isfile(cut_video): os.remove(cut_video) return None, 'Fehler beim Extrahieren der Streams mit Eac3to' # remove mkv + log file if os.path.isfile(cut_video): os.remove(cut_video) if os.path.isfile( os.path.splitext(cut_video)[0] + ' - Log.txt'): os.remove(os.path.splitext(cut_video)[0] + ' - Log.txt') args = [ self.config.get_program('mp4box'), '-new', '-keep-all', '-isma', '-inter', '500' ] for index in sorted(self.rawstreams.keys()): args.append('-add') if '.dx50' in self.rawstreams[index]: (root_dx50, dx50) = os.path.splitext(self.rawstreams[index]) os.rename(self.rawstreams[index], root_dx50 + '.m4v') self.rawstreams[index] = root_dx50 + '.m4v' args.append(self.rawstreams[index]) cut_video = os.path.splitext( self.generate_filename(filename, 1))[0] + ".mp4" args.append(cut_video) # mux to mp4 (mp4box) self.log.debug("Args: {}".format(args)) try: blocking_process = subprocess.Popen( args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) except OSError: return None, 'MP4Box konnte nicht aufgerufen werden' self.gui.main_window.set_tasks_text('Muxe MP4') self.show_progress(blocking_process) returncode = blocking_process.wait() if returncode != 0: return None, 'Fehler beim Erstellen der MP4' return cut_video, warning_msg