#!/usr/bin/env python3 """ xconv ffmpeg wrapper based on AdvancedAV ----------------------------------------------------------- AdvancedAV helps with constructing FFmpeg commandline arguments. It can automatically parse input files with the help of FFmpeg's ffprobe tool (WiP) and allows programatically mapping streams to output files and setting metadata on them. ----------------------------------------------------------- Copyright (c) 2015-2017 Taeyeon Mori 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 . """ from .profileman import load_profile from .cmdline import parse_args, version import advancedav from os.path import isdir, join as build_path, basename, dirname, splitext, exists, abspath from os import environ, makedirs, mkdir from shutil import copyfile from pathlib import Path # == Extend AAV == class OutputFile(advancedav.OutputFile): def change_format(self, format=None, ext=None): # Diverge from decorated format. # Watch out for args.genout!! if format: self.container = format if ext: self.name = splitext(self.name)[0] + "." + ext class SimpleTask(advancedav.SimpleTask): output_factory = OutputFile class AdvancedTask(advancedav.Task): output_factory = OutputFile def __init__(self, aav, basename): self.output_basename = basename super().__init__(aav) # == App == def make_basename(path, infile): return build_path(path, splitext(basename(infile))[0]) def make_outfile(path, infile, ext=None): name, oldext = splitext(basename(infile)) return build_path(path, ".".join((name, ext if ext else oldext))) def create_task(aav, profile, inputs, args, filename_from=None): is_advanced_task_profile = any(("advanced_task" in profile.features, "no_single_output" in profile.features)) filename_from = filename_from or inputs[0] if not is_advanced_task_profile: fmt = advancedav.DEFAULT_CONTAINER ext = None if "output" in profile.features: fmt, ext = profile.features["output"] outfile = args.output if args.output_filename else make_outfile(args.output_directory, filename_from, ext) task = SimpleTask(aav, outfile, fmt) else: basename = args.output if args.output_filename else make_basename(args.output_directory, filename_from) task = AdvancedTask(aav, basename) for input in inputs: task.add_input(input) return task def task_name(task): if hasattr(task, "name"): return basename(task.name) elif task.inputs: return "<%s" % task.inputs[0].name elif task.outputs: return ">%s" % task.outputs[0].name else: return "(anon task %p)" % id(task) def main(argv): import logging # Parse commandline args = parse_args(argv) logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) profile = load_profile(args.profile) print("\033[36mXConv %s (c) Taeyeon Mori\033[0m" % version) print("\033[34mProfile: %s\033[0m" % args.profile) if args.create_directory: makedirs(args.output_directory, exist_ok=True) if not args.output_filename and not isdir(args.output_directory): print("\033[31mOutput location '%s' is not a directory.\033[0m" % args.output_directory) return -1 # Initialize AAV aav = advancedav.SimpleAV(ffmpeg=args.ffmpeg, ffprobe=args.ffprobe) if args.quiet: aav.global_conv_args = "-loglevel", "warning" aav.global_args += "-hide_banner", # Collect Tasks tasks = [] print("\033[35mCollecting Tasks..\033[0m") if args.merge: tasks.append(create_task(aav, profile, args.inputs, args)) elif args.concat: import tempfile, os tmp = tempfile.NamedTemporaryFile(mode="w", delete=False) with tmp: tmp.write("ffconcat version 1.0\n") tmp.write("# XConv concat file\n") for f in map(abspath, args.inputs): print("\033[36m Concatenating %s\033[0m" % basename(f)) tmp.write("file '%s'\n" % f) task = create_task(aav, profile, (), args, filename_from=args.inputs[0]) task.add_input(tmp.name).set(f="concat", safe="0") tasks.append(task) else: for input in args.inputs: tasks.append(create_task(aav, profile, (input,), args)) print("\033[35mPreparing Tasks..\033[0m") # Prepare profile parameters pkw = {} if profile.defines: pkw["defines"] = args.define if profile.features: if "argshax" in profile.features: pkw["args"] = args # Apply profile for task in tasks: print("\033[32m Applying profile for '%s'\033[0m" % task_name(task), end="\033[K\r") res = profile(task, **pkw) if not res: print("\033[31m Failed to apply profile for '%s'\033[0m\033[K" % task_name(task)) return 1 if args.update: for task in tasks[:]: for output in [o for o in task.outputs if exists(o.name)]: print("\033[33m Skipping existing '%s' (--update)\033[0m\033[K" % basename(skip.name)) task.outputs.remove(skip) if not tasks.outputs: print("\033[33m Skipping task '%s' because no output files are left\033[0m\033[K" % task_name(task)) tasks.remove[task] print("\033[35mExecuting Tasks..\033[0m\033[K") # Commit for task in tasks: print("\033[32m Processing '%s'\033[0m" % task_name(task)) task.commit() # Clean up if args.concat: os.unlink(tmp.name) # Copy files if args.copy_files: print("\033[35mCopying Files..\033[0m\033[K") for file in args.copy_files: print("\033[32m Copying '%s'\033[0m\033[K" % basename(file)) copyfile(file, build_path(args.output_directory, basename(file))) print("\033[35mDone.\033[0m\033[K") return 0