#!/usr/bin/env python3 # Copyright 2014 (C) Raster Software Vigo (Sergio Costas) # # This file is part of DeVeDe-NG # # DeVeDe-NG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3 # as published by the Free Software Foundation. # # DeVeDe-NG 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 import subprocess import os import devedeng.configuration_data import devedeng.avbase import devedeng.mux_dvd_menu import devedeng.project class avconv(devedeng.avbase.avbase): supports_analize = False supports_play = False supports_convert = True supports_menu = True supports_mkiso = False supports_burn = False display_name = "AVCONV" disc_types = [] @staticmethod def check_is_installed(): try: handle = subprocess.Popen( ["avconv", "-codecs"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = handle.communicate() if 0 == handle.wait(): mp2 = False mp3 = False ac3 = False mpeg1 = False mpeg2 = False divx = False h264 = False hevc = False for line in stdout.decode("latin-1").split("\n"): parts = line.strip().split(" ") if len(parts) < 2: continue if len(parts[0]) != 6: continue capabilities = parts[0] codec = parts[1] if capabilities[1] != 'E': continue if (codec == "mpeg1video"): mpeg1 = True continue if (codec == "mpeg2video"): mpeg2 = True continue if (codec == "mp2"): mp2 = True continue if (codec == "mp3"): mp3 = True continue if (codec == "ac3"): ac3 = True continue if (codec == "h264") or (codec == "H264"): h264 = True continue if (codec == "hevc") or (codec == "HEVC"): hevc = True continue if (codec == "mpeg4"): divx = True continue if (mpeg1 and mp2): devedeng.avconv.avconv.disc_types.append("vcd") if (mpeg2 and mp2): devedeng.avconv.avconv.disc_types.append("svcd") devedeng.avconv.avconv.disc_types.append("cvd") if (mpeg2 and mp2 and ac3): devedeng.avconv.avconv.disc_types.append("dvd") if (divx and mp3): devedeng.avconv.avconv.disc_types.append("divx") if (h264 and mp3): devedeng.avconv.avconv.disc_types.append("mkv") if (hevc and mp3): devedeng.ffmpeg.ffmpeg.disc_types.append("hevc") return True else: return False except (OSError, subprocess.SubprocessError): return False def __init__(self): devedeng.executor.executor.__init__(self) self.config = devedeng.configuration_data.configuration.get_config() self.check_version(["avconv", "-version"]) def convert_file(self, file_project, output_file, video_length, pass2=False, start_offset=0): self.start_offset = start_offset if file_project.two_pass_encoding: if pass2: self.text = _("Converting %(X)s (pass 2)") % { "X": file_project.title_name} else: self.text = _("Converting %(X)s (pass 1)") % { "X": file_project.title_name} # Prepare the converting process for the second pass tmp = devedeng.avconv.avconv() tmp.convert_file(file_project, output_file, video_length, True, start_offset) # it deppends of this process tmp.add_dependency(self) # add it as a child process of this one self.add_child_process(tmp) else: self.text = _("Converting %(X)s") % {"X": file_project.title_name} if (pass2 == False) and (file_project.two_pass_encoding == True): # this is the first pass in a 2-pass codification second_pass = False else: # second_pass is TRUE in the second pass of a 2-pass codification, and also when not doing 2-pass codification # It is used to remove unnecessary steps during the first pass, but # that are needed on the second pass, or when not using 2-pass # codification second_pass = True if (video_length == 0): self.final_length = file_project.original_length else: self.final_length = video_length self.command_var = [] # lower priority so the desktop stays responsive during the transcode self.command_var.append("nice") self.command_var.append("-n") self.command_var.append("10") self.command_var.append("avconv") if start_offset and start_offset > 0: self.command_var.append("-ss") self.command_var.append(str(start_offset)) self.command_var.append("-i") self.command_var.append(file_project.file_name) if (file_project.volume != 100) and second_pass: self.command_var.append("-vol") self.command_var.append( str(int((256 * file_project.volume) / 100))) if (file_project.audio_delay != 0.0) and (file_project.copy_sound == False) and (file_project.no_reencode_audio_video == False) and second_pass: self.command_var.append("-itsoffset") self.command_var.append(str(file_project.audio_delay)) if start_offset and start_offset > 0: self.command_var.append("-ss") self.command_var.append(str(start_offset)) self.command_var.append("-i") self.command_var.append(file_project.file_name) self.command_var.append("-map") self.command_var.append("1:" + str(file_project.video_list[0])) if (not file_project.copy_sound) and (not file_project.no_reencode_audio_video): for l in file_project.audio_list: self.command_var.append("-map") self.command_var.append("0:" + str(l)) if (file_project.no_reencode_audio_video == False) and second_pass: cmd_line = "" if file_project.deinterlace == "deinterlace_yadif": cmd_line += "yadif" vflip = False hflip = False if (file_project.prerotation == 90): if (cmd_line != ""): cmd_line += ",fifo," cmd_line += "transpose=1" elif (file_project.prerotation == -90) or (file_project.prerotation == 270): if (cmd_line != ""): cmd_line += ",fifo," cmd_line += "transpose=2" elif (file_project.prerotation == 180): if (cmd_line != ""): cmd_line += ",fifo," vflip = not vflip hflip = not hflip if (file_project.rotation == "rotation_90"): if (cmd_line != ""): cmd_line += ",fifo," cmd_line += "transpose=1" elif (file_project.rotation == "rotation_270"): if (cmd_line != ""): cmd_line += ",fifo," cmd_line += "transpose=2" elif (file_project.rotation == "rotation_180"): vflip = not vflip hflip = not hflip if (file_project.mirror_vertical): vflip = not vflip if (file_project.mirror_horizontal): hflip = not hflip if (vflip): if (cmd_line != ""): cmd_line += ",fifo," cmd_line += "vflip" if (hflip): if (cmd_line != ""): cmd_line += ",fifo," cmd_line += "hflip" if (file_project.width_midle != file_project.original_width) or (file_project.height_midle != file_project.original_height): if (cmd_line != ""): cmd_line += ",fifo," # MPEG-2 / yuv420p needs even dimensions; round to even so the # aspect math can't produce an odd pad/crop target (e.g. 1079). mid_w = file_project.width_midle - (file_project.width_midle % 2) mid_h = file_project.height_midle - (file_project.height_midle % 2) x = int((mid_w - file_project.original_width) / 2) y = int((mid_h - file_project.original_height) / 2) if (x > 0) or (y > 0): cmd_line += "pad=" + str(mid_w) + ":" + str( mid_h) + ":" + str(x) + ":" + str(y) + ":0x000000" else: # crop x/y are the (positive) top-left offset into the source cmd_line += "crop=" + str(mid_w) + ":" + str( mid_h) + ":" + str(abs(x)) + ":" + str(abs(y)) if (file_project.width_final != file_project.width_midle) or (file_project.height_final != file_project.height_midle): if (cmd_line != ""): cmd_line += ",fifo," if self.major_version < 11: cmd_line += "scale=" + \ str(file_project.width_final) + ":" + \ str(file_project.height_final) else: cmd_line += "scale=w=" + \ str(file_project.width_final) + ":h=" + \ str(file_project.height_final) # text subtitle burn-in (hardsub) as the last filter, after scaling. # Image-based subs need an overlay filtergraph and are only handled # by the ffmpeg backend; avconv users get text subs burned in. if second_pass: text_filter = self._avconv_text_subtitle_filter(file_project) if text_filter is not None: if (cmd_line != ""): cmd_line += ",fifo," cmd_line += text_filter if cmd_line != "": self.command_var.append("-vf") self.command_var.append(cmd_line) self.command_var.append("-y") vcd = False maxrate = int(file_project.video_rate_final * 1500) minrate = int(file_project.video_rate_final * 666.66) if (self.config.disc_type == "divx"): self.command_var.append("-vcodec") self.command_var.append("mpeg4") self.command_var.append("-acodec") self.command_var.append("libmp3lame") self.command_var.append("-f") self.command_var.append("avi") if not pass2: self.command_var.append("-maxrate:v") self.command_var.append(str(maxrate)) self.command_var.append("-minrate:v") if minrate < (devedeng.project.devede_project.divx_min_bps * 1000): minrate = devedeng.project.devede_project.divx_min_bps * 1000 self.command_var.append(str(minrate)) elif (self.config.disc_type == "mkv") or (self.config.disc_type == "hevc"): self.command_var.append("-vcodec") self.command_var.append("h264" if self.config.disc_type == "mkv" else "hevc") self.command_var.append("-acodec") self.command_var.append("libmp3lame") self.command_var.append("-f") self.command_var.append("matroska") if not pass2: self.command_var.append("-maxrate:v") self.command_var.append(str(maxrate)) self.command_var.append("-minrate:v") if minrate < (devedeng.project.devede_project.mkv_min_bps * 1000): minrate = devedeng.project.devede_project.mkv_min_bps * 1000 self.command_var.append(str(minrate)) elif (self.config.disc_type == "dvd"): if not file_project.no_reencode_audio_video: self.command_var.append("-c:v") self.command_var.append("mpeg2video") if not file_project.copy_sound: if file_project.sound5_1: self.command_var.append("-c:a") self.command_var.append("ac3") else: self.command_var.append("-c:a") if file_project.format_pal: self.command_var.append("ac3") else: self.command_var.append("mp2") self.command_var.append("-f") self.command_var.append("dvd") self.command_var.append("-r") if file_project.format_pal: self.command_var.append("25") else: if (file_project.original_fps == 24): self.command_var.append("24000/1001") else: self.command_var.append("30000/1001") self.command_var.append("-pix_fmt") self.command_var.append("yuv420p") self.command_var.append("-maxrate:v") if maxrate > (devedeng.project.devede_project.dvd_max_bps_base[0] * 1000): maxrate = devedeng.project.devede_project.dvd_max_bps_base[0] * 1000 self.command_var.append(str(maxrate)) if not pass2: self.command_var.append("-minrate:v") if minrate < (devedeng.project.devede_project.dvd_min_bps * 1000): minrate = devedeng.project.devede_project.dvd_min_bps * 1000 self.command_var.append(str(minrate)) self.command_var.append("-bufsize") self.command_var.append("1835008") self.command_var.append("-packetsize") self.command_var.append("2048") self.command_var.append("-muxrate") self.command_var.append("10080000") self.command_var.append("-ar") self.command_var.append("48000") elif (self.config.disc_type == "vcd"): vcd = True if not file_project.no_reencode_audio_video: self.command_var.append("-c:v") self.command_var.append("mpeg1video") if not file_project.copy_sound: self.command_var.append("-c:a") self.command_var.append("mp2") self.command_var.append("-b:v") self.command_var.append("1150000") self.command_var.append("-b:a") self.command_var.append("224000") self.command_var.append("-f") self.command_var.append("vcd") self.command_var.append("-r") if file_project.format_pal: self.command_var.append("25") else: if (file_project.original_fps == 24): self.command_var.append("24000/1001") else: self.command_var.append("30000/1001") self.command_var.append("-g") if file_project.format_pal: self.command_var.append("15") else: self.command_var.append("18") self.command_var.append("-s") if file_project.format_pal: self.command_var.append("352x288") else: self.command_var.append("352x240") self.command_var.append("-maxrate:v") self.command_var.append("1150000") self.command_var.append("-minrate:v") self.command_var.append("1150000") self.command_var.append("-bufsize") self.command_var.append("327680") self.command_var.append("-packetsize") self.command_var.append("2324") self.command_var.append("-muxrate") self.command_var.append("1411200") self.command_var.append("-ar") self.command_var.append("44100") elif (self.config.disc_type == "svcd") or (self.config.disc_type == "cvd"): if not file_project.no_reencode_audio_video: self.command_var.append("-c:v") self.command_var.append("mpeg2video") if not file_project.copy_sound: self.command_var.append("-c:a") self.command_var.append("mp2") self.command_var.append("-f") self.command_var.append("svcd") self.command_var.append("-r") if file_project.format_pal: self.command_var.append("25") else: if (file_project.original_fps == 24): self.command_var.append("24000/1001") else: self.command_var.append("30000/1001") self.command_var.append("-g") if file_project.format_pal: self.command_var.append("12") else: self.command_var.append("15") self.command_var.append("-s") if self.config.disc_type == "cvd": if file_project.format_pal: self.command_var.append("352x576") else: self.command_var.append("352x480") else: if file_project.format_pal: self.command_var.append("480x576") else: self.command_var.append("480x480") self.command_var.append("-pix_fmt") self.command_var.append("yuv420p") self.command_var.append("-maxrate:v") if maxrate > (devedeng.project.devede_project.svcd_max_bps_base[0] * 1000): maxrate = devedeng.project.devede_project.svcd_max_bps_base[0] * 1000 self.command_var.append(str(maxrate)) if not pass2: self.command_var.append("-minrate:v") if minrate < (devedeng.project.devede_project.svcd_min_bps * 1000): minrate = (devedeng.project.devede_project.svcd_min_bps * 1000) self.command_var.append(str(minrate)) self.command_var.append("-bufsize") self.command_var.append("1835008") self.command_var.append("-packetsize") self.command_var.append("2324") self.command_var.append("-ar") self.command_var.append("44100") self.command_var.append("-scan_offset") self.command_var.append("1") if (not file_project.no_reencode_audio_video): self.command_var.append("-sn") # no subtitles if file_project.copy_sound or file_project.no_reencode_audio_video: self.command_var.append("-acodec") self.command_var.append("copy") if file_project.no_reencode_audio_video: self.command_var.append("-vcodec") self.command_var.append("copy") if (not vcd): if not file_project.format_pal: if (file_project.original_fps == 24) and ((self.config.disc_type == "dvd")): keyintv = 15 else: keyintv = 18 else: keyintv = 15 if (not file_project.gop12) and (not(self.config.disc_type == "divx")) and (not(self.config.disc_type == "mkv") and (not(self.config.disc_type == "hevc"))): self.command_var.append("-g") self.command_var.append(str(keyintv)) if (self.config.disc_type == "divx") or (self.config.disc_type == "mkv") or (self.config.disc_type == "hevc"): self.command_var.append("-g") self.command_var.append("300") elif file_project.gop12 and (not file_project.no_reencode_audio_video): self.command_var.append("-g") self.command_var.append("12") self.command_var.append("-bf") self.command_var.append("2") self.command_var.append("-strict") self.command_var.append("1") if video_length != 0: self.command_var.append("-t") self.command_var.append(str(video_length)) self.command_var.append("-ac") if (file_project.sound5_1) and ((self.config.disc_type == "dvd") or (self.config.disc_type == "divx") or (self.config.disc_type == "mkv") or (self.config.disc_type == "hevc")): self.command_var.append("6") else: self.command_var.append("2") self.command_var.append("-aspect") self.command_var.append(str(file_project.aspect_ratio_final)) if self.config.disc_type == "divx": self.command_var.append("-vtag") self.command_var.append("DX50") if (file_project.deinterlace == "deinterlace_ffmpeg") and (file_project.no_reencode_audio_video == False) and second_pass: self.command_var.append("-deinterlace") if (file_project.no_reencode_audio_video == False) and (vcd == False) and second_pass: self.command_var.append("-s") self.command_var.append( str(file_project.width_final) + "x" + str(file_project.height_final)) if second_pass: self.command_var.append("-trellis") self.command_var.append("1") self.command_var.append("-mbd") self.command_var.append("2") else: self.command_var.append("-trellis") self.command_var.append("0") self.command_var.append("-mbd") self.command_var.append("0") if (vcd == False) and (file_project.no_reencode_audio_video == False): self.command_var.append("-b:a") self.command_var.append(str(self._adjust_audio_bitrate(file_project.audio_rate_final)*1000)) self.command_var.append("-b:v") self.command_var.append(str(file_project.video_rate_final*1000)) if file_project.two_pass_encoding == True: self.command_var.append("-passlogfile") self.command_var.append(output_file + ".passlog") self.command_var.append("-pass") if pass2: self.command_var.append("2") else: self.command_var.append("1") self.command_var.append(output_file) def _adjust_audio_bitrate(self, bitrate): # includes AC3 rates above the MP2 ceiling (384) so 448k AC3 isn't # silently clamped down br = [32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 640] for a in br: if a >= bitrate: return a return br[-1] def _avconv_text_subtitle_filter(self, file_project): """ Build a `subtitles=` filter to burn a text-based embedded subtitle track into the picture, or None. Image-based subs are not handled by the avconv backend (use the ffmpeg backend for those). """ if getattr(file_project, "hardsub_kind", "text") != "text": return None index = getattr(file_project, "hardsub_index", -1) if index is None or index < 0: return None subs = getattr(file_project, "embedded_subtitles", []) or [] rel = None for i, track in enumerate(subs): if track["index"] == index: rel = i break if rel is None: return None path = file_project.file_name.replace("\\", "\\\\").replace(":", "\\:") path = path.replace("'", "\\'") return "subtitles='%s':si=%d" % (path, rel) def create_menu_mpeg(self, n_page, background_music, sound_length, pal, video_rate, audio_rate, output_path, use_mp2, widescreen): self.n_page = n_page self.final_length = float(sound_length) self.text = _("Creating menu %(X)d") % {"X": self.n_page} self.command_var = [] self.command_var.append("avconv") self.command_var.append("-loop") self.command_var.append("1") self.command_var.append("-f") self.command_var.append("image2") self.command_var.append("-i") self.command_var.append(os.path.join( output_path, "menu_" + str(n_page) + "_bg.png")) self.command_var.append("-i") self.command_var.append(background_music) self.command_var.append("-y") self.command_var.append("-target") if pal: self.command_var.append("pal-dvd") else: self.command_var.append("ntsc-dvd") self.command_var.append("-acodec") if use_mp2: self.command_var.append("mp2") if (audio_rate > 384): audio_rate = 384 # max bitrate for mp2 else: self.command_var.append("ac3") self.command_var.append("-s") if pal: self.command_var.append("720x576") else: self.command_var.append("720x480") self.command_var.append("-g") self.command_var.append("12") self.command_var.append("-b:v") self.command_var.append(str(video_rate) + "k") self.command_var.append("-b:a") self.command_var.append(str(self._adjust_audio_bitrate(audio_rate)) + "k") self.command_var.append("-aspect") if widescreen: self.command_var.append("16:9") else: self.command_var.append("4:3") self.command_var.append("-t") self.command_var.append(str(1 + sound_length)) movie_path = os.path.join(output_path, "menu_" + str(n_page) + ".mpg") self.command_var.append(movie_path) muxer = devedeng.mux_dvd_menu.mux_dvd_menu() final_path = muxer.create_mpg(n_page, output_path, movie_path) # the muxer process depends of the converter process muxer.add_dependency(self) self.add_child_process(muxer) return (final_path) def process_stdout(self, data): return def process_stderr(self, data): pos = data[0].find("time=") if (pos != -1): pos += 5 pos2 = data[0].find(" ", pos) if (pos2 != -1): parts = data[0][pos:pos2].split(":") t = 0.0 for e in parts: try: v = float(e) except ValueError: continue t = t * 60.0 + v t /= self.final_length self.progress_bar[1].set_fraction(t) self.progress_bar[1].set_text("%.1f%%" % (100.0 * t))