parent
							
								
									04c609c152
								
							
						
					
					
						commit
						a1fe0e7401
					
				
				 11 changed files with 739 additions and 0 deletions
			
			
		@ -0,0 +1,26 @@ | 
				
			|||||||
 | 
					#!/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 <http://www.gnu.org/licenses/>. | 
				
			||||||
 | 
					""" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					version_info = 0, 2, 0 | 
				
			||||||
@ -0,0 +1,30 @@ | 
				
			|||||||
 | 
					#!/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 <http://www.gnu.org/licenses/>. | 
				
			||||||
 | 
					""" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .app import main | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from sys import argv, exit | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exit(main(argv)) | 
				
			||||||
@ -0,0 +1,171 @@ | 
				
			|||||||
 | 
					#!/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 <http://www.gnu.org/licenses/>. | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					""" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# == App == | 
				
			||||||
 | 
					def make_outfile(args, profile, infile): | 
				
			||||||
 | 
					    if not args.output_filename: | 
				
			||||||
 | 
					        if hasattr(profile, "ext"): | 
				
			||||||
 | 
					            return build_path(args.output_directory, ".".join((splitext(basename(infile))[0], profile.ext if profile.ext else "bin"))) | 
				
			||||||
 | 
					        else: | 
				
			||||||
 | 
					            return build_path(args.output_directory, basename(infile)) | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					        return args.output | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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: | 
				
			||||||
 | 
					        task = SimpleTask(aav, make_outfile(args, profile, args.inputs[0]), profile.container) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for input in args.inputs: | 
				
			||||||
 | 
					            task.add_input(input) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tasks.append(task) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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 = SimpleTask(aav, make_outfile(args, profile, args.inputs[0]), profile.container) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        options(task.add_input(tmp.name), | 
				
			||||||
 | 
					            f="concat", | 
				
			||||||
 | 
					            safe="0") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tasks.append(task) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					        for input in args.inputs: | 
				
			||||||
 | 
					            out = make_outfile(args, profile, input) | 
				
			||||||
 | 
					            if args.update and exists(out): | 
				
			||||||
 | 
					                continue | 
				
			||||||
 | 
					            task = SimpleTask(aav, out, profile.container) | 
				
			||||||
 | 
					            task.add_input(input) | 
				
			||||||
 | 
					            tasks.append(task) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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" % basename(task.name), end="\033[K\r") | 
				
			||||||
 | 
					        res = profile(task, **pkw) | 
				
			||||||
 | 
					        if not res: | 
				
			||||||
 | 
					            print("\033[31m  Failed to apply profile for '%s'\033[0m\033[K" % basename(task.name)) | 
				
			||||||
 | 
					            return 1 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print("\033[35mExecuting Tasks..\033[0m\033[K") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Commit | 
				
			||||||
 | 
					    for task in tasks: | 
				
			||||||
 | 
					        print("\033[32m  Processing '%s'\033[0m" % basename(task.name)) | 
				
			||||||
 | 
					        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 | 
				
			||||||
@ -0,0 +1,196 @@ | 
				
			|||||||
 | 
					#!/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 <http://www.gnu.org/licenses/>. | 
				
			||||||
 | 
					----------------------------------------------------------- | 
				
			||||||
 | 
					Commandline Parsing | 
				
			||||||
 | 
					""" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .profileman import load_all_profiles, load_profile | 
				
			||||||
 | 
					from . import version_info | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from advancedav import version_info as aav_version_info | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from argparse import ArgumentParser, Action | 
				
			||||||
 | 
					from pathlib import Path | 
				
			||||||
 | 
					from os.path import basename | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					version = "%s (AdvancedAV %s)" % (".".join(map(str, version_info)), ".".join(map(str, aav_version_info))) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# == Support code == | 
				
			||||||
 | 
					class TerminalAction(Action): | 
				
			||||||
 | 
					    def __init__(self, option_strings, dest, nargs=0, default=None, **kwargs): | 
				
			||||||
 | 
					        super().__init__(option_strings, dest, nargs=nargs, default=default or {}, **kwargs) | 
				
			||||||
 | 
					    def __call__(self, parser, namespace, values, option_string=None): | 
				
			||||||
 | 
					        self.run(parser, *values) | 
				
			||||||
 | 
					        parser.exit() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProfilesAction(TerminalAction): | 
				
			||||||
 | 
					    def run(self, parser): | 
				
			||||||
 | 
					        print("Available Profiles:") | 
				
			||||||
 | 
					        for name, profile in sorted(load_all_profiles().items()): | 
				
			||||||
 | 
					            print("  %-20s %s" % (name, profile.description if profile.description else "")) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProfileInfoAction(TerminalAction): | 
				
			||||||
 | 
					    def __init__(self, option_strings, dest, nargs=1, default=None, **kwargs): | 
				
			||||||
 | 
					        super().__init__(option_strings, dest, nargs=nargs, default=default or {}, **kwargs) | 
				
			||||||
 | 
					    def run(self, parser, profile_name): | 
				
			||||||
 | 
					        profile = load_profile(profile_name) | 
				
			||||||
 | 
					        print("Profile '%s':" % profile_name) | 
				
			||||||
 | 
					        if profile.description: | 
				
			||||||
 | 
					            print("  Description: %s" % profile.description) | 
				
			||||||
 | 
					        output_info = [] | 
				
			||||||
 | 
					        if profile.container: | 
				
			||||||
 | 
					            output_info.append("Format: %s" % profile.container) | 
				
			||||||
 | 
					        if profile.ext: | 
				
			||||||
 | 
					            output_info.append("File extension: %s" % profile.ext) | 
				
			||||||
 | 
					        if output_info: | 
				
			||||||
 | 
					            print("  Output: %s" % "; ".join(output_info)) | 
				
			||||||
 | 
					        if profile.features: | 
				
			||||||
 | 
					            print("  Flags: %s" % ", ".join("%s(%r)" % (k, v) if v is not None else k for k, v in profile.features.items())) | 
				
			||||||
 | 
					        if profile.defines: | 
				
			||||||
 | 
					            print("  Supported defines:") | 
				
			||||||
 | 
					            for define in sorted(profile.defines.items()): | 
				
			||||||
 | 
					                print("    %s: %s" % define) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DefineAction(Action): | 
				
			||||||
 | 
					    def __init__(self, option_strings, dest, nargs=1, default=None, **kwargs): | 
				
			||||||
 | 
					        super().__init__(option_strings, dest, nargs=nargs, default=default or {}, **kwargs) | 
				
			||||||
 | 
					    def __call__(self, parser, namespace, values, option_string=None): | 
				
			||||||
 | 
					        value = values[0] | 
				
			||||||
 | 
					        dest = getattr(namespace, self.dest) | 
				
			||||||
 | 
					        if "=" in value: | 
				
			||||||
 | 
					            k, v = value.split("=") | 
				
			||||||
 | 
					            dest[k] = v | 
				
			||||||
 | 
					        else: | 
				
			||||||
 | 
					            dest[value] = True | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ExtendAction(Action): | 
				
			||||||
 | 
					    def __init__(self, option_strings, dest, nargs="+", default=None, **kwargs): | 
				
			||||||
 | 
					        super().__init__(option_strings, dest, nargs=nargs, default=default, **kwargs) | 
				
			||||||
 | 
					    def __call__(self, parser, namespace, values, option_string=None): | 
				
			||||||
 | 
					        items = getattr(namespace, self.dest) or [] | 
				
			||||||
 | 
					        items.extend(values) | 
				
			||||||
 | 
					        setattr(namespace, self.dest, items) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_args(argv): | 
				
			||||||
 | 
					    prog = basename(argv[0]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if prog == "__main__.py": | 
				
			||||||
 | 
					        prog = "python -m xconv" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parser = ArgumentParser(prog=prog, | 
				
			||||||
 | 
					        usage="""%(prog)s [-h | -l | -i PROFILE] | 
				
			||||||
 | 
					       %(prog)s [option]... -p PROFILE [-DNAME[=VALUE]]... [-B] [-T] input output | 
				
			||||||
 | 
					       %(prog)s [option]... -p PROFILE [-DNAME[=VALUE]]...  -M  [-T] inputs... output | 
				
			||||||
 | 
					       %(prog)s [option]... -p PROFILE [-DNAME[=VALUE]]...  -C  [-T] inputs... output | 
				
			||||||
 | 
					       %(prog)s [option]... -p PROFILE [-DNAME[=VALUE]]... [-B] inputs... directory | 
				
			||||||
 | 
					       %(prog)s [option]... -p PROFILE [-DNAME[=VALUE]]... [-B] -t directory inputs...""", | 
				
			||||||
 | 
					        description="""FFmpeg wrapper based on AdvancedAV""") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parser.add_argument("-V", "--version",              help="Show version and quit",                                               action="version", | 
				
			||||||
 | 
					        version="""XConv %s""" % version) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Available Options | 
				
			||||||
 | 
					    parser.add_argument("-v", "--verbose",              help="Enable verbose output",                                               action="store_true") | 
				
			||||||
 | 
					    parser.add_argument("-q", "--quiet",                help="Be less verbose",                                                     action="store_true") | 
				
			||||||
 | 
					    profile = parser.add_argument_group("Profile") | 
				
			||||||
 | 
					    profile.add_argument("-l", "--list-profiles",       help="List profiles and quit",                                              action=ProfilesAction) | 
				
			||||||
 | 
					    profile.add_argument("-i", "--profile-info",        help="Give info about a profile and quit",          metavar="PROFILE",      action=ProfileInfoAction) | 
				
			||||||
 | 
					    profile.add_argument("-p", "--profile",             help="Specify the profile",                         metavar="PROFILE",      required=True) | 
				
			||||||
 | 
					    profile.add_argument("-D", "--define",              help="Define an option to be used by the profile",  metavar="NAME[=VALUE]", action=DefineAction) | 
				
			||||||
 | 
					    mode = parser.add_argument_group("Mode").add_mutually_exclusive_group() | 
				
			||||||
 | 
					    mode.add_argument("-B", "--batch",                  help="Batch process every input file into an output file (default)",        action="store_true") | 
				
			||||||
 | 
					    mode.add_argument("-M", "--merge",                  help="Merge streams from all inputs",                                       action="store_true") | 
				
			||||||
 | 
					    mode.add_argument("-C", "--concat",                 help="Concatenate streams from inputs",                                     action="store_true") | 
				
			||||||
 | 
					    files = parser.add_argument_group("Files") | 
				
			||||||
 | 
					    files.add_argument("inputs",                        help="The input file(s)",                                                   nargs="+") | 
				
			||||||
 | 
					    files.add_argument("output",                        help="The output filename or directory (unless -t is given)",               nargs="?") # always empty | 
				
			||||||
 | 
					    files.add_argument("-u", "--update",                help="Only work on files that don't already exist",                         action="store_true") | 
				
			||||||
 | 
					    files.add_argument("-c", "--create-directory",      help="Create directories if they don't exist",                              action="store_true") | 
				
			||||||
 | 
					    target = files.add_mutually_exclusive_group() | 
				
			||||||
 | 
					    target.add_argument("-t", "--target-directory",     help="Output into a directory",                     metavar="DIRECTORY",    type=Path) | 
				
			||||||
 | 
					    target.add_argument("-T", "--no-target-directory",  help="Treat output as a normal file",                                       action="store_true") | 
				
			||||||
 | 
					    files.add_argument("-S", "--subdirectory",          help="Work in a subdirectory of here and -t (use glob patterns for inputs)") | 
				
			||||||
 | 
					    files.add_argument("-K", "--copy-files",            help="Copy all following files unmodified",         metavar="FILE",         action=ExtendAction) | 
				
			||||||
 | 
					    progs = parser.add_argument_group("Programs") | 
				
			||||||
 | 
					    progs.add_argument("--ffmpeg",                      help="Path to the ffmpeg executable",                                       default="ffmpeg") | 
				
			||||||
 | 
					    progs.add_argument("--ffprobe",                     help="Path to the ffprobe executable",                                      default="ffprobe") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Parse arguments | 
				
			||||||
 | 
					    args = parser.parse_args(argv[1:]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Figure out output path | 
				
			||||||
 | 
					    # ---------------------- | 
				
			||||||
 | 
					    # Fill in args.output | 
				
			||||||
 | 
					    # args.output will never be filled in by argparse, since inputs consumes everything | 
				
			||||||
 | 
					    if args.target_directory: | 
				
			||||||
 | 
					        args.output = args.target_directory | 
				
			||||||
 | 
					    elif len(args.inputs) < 2: | 
				
			||||||
 | 
					        parser.error("Neither --target-directory nor output is given") | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					        args.output = Path(args.inputs.pop(-1)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if args.subdirectory: | 
				
			||||||
 | 
					        subdir = Path(args.subdirectory)#.resolve() | 
				
			||||||
 | 
					        outdir = Path(args.output, args.subdirectory)#.resolve() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if outdir.exists() and not outdir.is_dir(): | 
				
			||||||
 | 
					            parser.error("--subdirectory only works with output directories. '%s' exists and isn't a directory") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        inputs = args.inputs | 
				
			||||||
 | 
					        args.inputs = [] | 
				
			||||||
 | 
					        for pattern in inputs: | 
				
			||||||
 | 
					            args.inputs.extend(subdir.glob(pattern)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        files = args.copy_files | 
				
			||||||
 | 
					        args.copy_files = [] | 
				
			||||||
 | 
					        for pattern in files: | 
				
			||||||
 | 
					            args.copy_files.extend(subdir.glob(pattern)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        args.output_directory = args.output = outdir | 
				
			||||||
 | 
					        args.output_filename = None | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					        # Check if we're outputting to a directory | 
				
			||||||
 | 
					        multiple_outputs = args.copy_files or not (args.merge or args.concat) and len(args.inputs) > 1 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if args.target_directory or args.output.is_dir() or multiple_outputs: | 
				
			||||||
 | 
					            if args.no_target_directory: | 
				
			||||||
 | 
					                if multiple_outputs: | 
				
			||||||
 | 
					                    parser.error("Passed --no-target-directory, but operation would have multiple outputs. (See --merge or --concat)") | 
				
			||||||
 | 
					                else: | 
				
			||||||
 | 
					                    parser.error("Passed --no-target-directory, but '%s' is an existing directory." % args.output) | 
				
			||||||
 | 
					            args.output_filename = None | 
				
			||||||
 | 
					            args.output_directory = args.output | 
				
			||||||
 | 
					        else: | 
				
			||||||
 | 
					            args.output_filename = args.output.name | 
				
			||||||
 | 
					            args.output_directory = args.output.parent | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return args | 
				
			||||||
@ -0,0 +1,123 @@ | 
				
			|||||||
 | 
					#!/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 <http://www.gnu.org/licenses/>. | 
				
			||||||
 | 
					----------------------------------------------------------- | 
				
			||||||
 | 
					Decorators for defining xconv profiles | 
				
			||||||
 | 
					""" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .profileman import index | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from functools import wraps | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					__all__ = [ | 
				
			||||||
 | 
					    "profile", | 
				
			||||||
 | 
					    "description", | 
				
			||||||
 | 
					    "output", | 
				
			||||||
 | 
					    "defines", | 
				
			||||||
 | 
					    "features", | 
				
			||||||
 | 
					    "singleaudio" | 
				
			||||||
 | 
					] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# == Misc. helpers == | 
				
			||||||
 | 
					def __update(f, name, update): | 
				
			||||||
 | 
					    if hasattr(f, name): | 
				
			||||||
 | 
					        getattr(f, name).update(update); | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					        setattr(f, name, update) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def __defaults(obj, **defs): | 
				
			||||||
 | 
					    for k, v in defs.items(): | 
				
			||||||
 | 
					        if not hasattr(obj, k): | 
				
			||||||
 | 
					            setattr(obj, k, v) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# == Profile Decorators == | 
				
			||||||
 | 
					def profile(f): | 
				
			||||||
 | 
					    """ | 
				
			||||||
 | 
					    Define a XConv Profile | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Note: Should be outermost decorator | 
				
			||||||
 | 
					    """ | 
				
			||||||
 | 
					    __defaults(f, | 
				
			||||||
 | 
					        description=None, | 
				
			||||||
 | 
					        container=None, | 
				
			||||||
 | 
					        ext=None, | 
				
			||||||
 | 
					        defines={}, | 
				
			||||||
 | 
					        features={}) | 
				
			||||||
 | 
					    index[f.__name__] = f | 
				
			||||||
 | 
					    return f | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def description(desc): | 
				
			||||||
 | 
					    """ Add a profile description """ | 
				
			||||||
 | 
					    def apply(f): | 
				
			||||||
 | 
					        f.description = desc | 
				
			||||||
 | 
					        return f | 
				
			||||||
 | 
					    return apply | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def output(container=None, ext=None): | 
				
			||||||
 | 
					    """ Add output file information """ | 
				
			||||||
 | 
					    def apply(f): | 
				
			||||||
 | 
					        if container: | 
				
			||||||
 | 
					            f.container = container | 
				
			||||||
 | 
					            f.ext = "mkv" if container == "matroska" else container | 
				
			||||||
 | 
					        if ext: | 
				
			||||||
 | 
					            f.ext = ext | 
				
			||||||
 | 
					        return f | 
				
			||||||
 | 
					    return apply | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def defines(**defs): | 
				
			||||||
 | 
					    """ Document supported defines with description """ | 
				
			||||||
 | 
					    def apply(f): | 
				
			||||||
 | 
					        __update(f, "defines", defs) | 
				
			||||||
 | 
					        return f | 
				
			||||||
 | 
					    return apply | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def features(**features): | 
				
			||||||
 | 
					    """ Set opaque feature flags """ | 
				
			||||||
 | 
					    def apply(f): | 
				
			||||||
 | 
					        __update(f, "features", features) | 
				
			||||||
 | 
					        return f | 
				
			||||||
 | 
					    return apply | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def singleaudio(profile): | 
				
			||||||
 | 
					    """ | 
				
			||||||
 | 
					    Operate on a single audio stream (The first one found) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The stream will be passed to the decorated function in the "stream" keyword | 
				
			||||||
 | 
					    """ | 
				
			||||||
 | 
					    @wraps(profile) | 
				
			||||||
 | 
					    def wrapper(task, **kwds): | 
				
			||||||
 | 
					        try: | 
				
			||||||
 | 
					            audio_stream = next(task.iter_audio_streams()) | 
				
			||||||
 | 
					        except StopIteration: | 
				
			||||||
 | 
					            print("No audio track in '%s'" % "', '".join(map(lambda x: x.name, task.inputs))) | 
				
			||||||
 | 
					            return False | 
				
			||||||
 | 
					        return profile(task, stream=audio_stream, **kwds) | 
				
			||||||
 | 
					    __update(wrapper, "features", {"singleaudio": None}) | 
				
			||||||
 | 
					    return wrapper | 
				
			||||||
@ -0,0 +1,65 @@ | 
				
			|||||||
 | 
					#!/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 <http://www.gnu.org/licenses/>. | 
				
			||||||
 | 
					""" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .profiles import __path__ as profilepaths | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from importlib import import_module | 
				
			||||||
 | 
					from pathlib import Path | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# == Profile Index == | 
				
			||||||
 | 
					index = {} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def load_profile(name): | 
				
			||||||
 | 
					    if name in index: | 
				
			||||||
 | 
					        return index[name] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try: | 
				
			||||||
 | 
					        import_module(".profiles.%s" % name, __package__) | 
				
			||||||
 | 
					    except ImportError as e: | 
				
			||||||
 | 
					        print(e) | 
				
			||||||
 | 
					        pass | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if name in index: | 
				
			||||||
 | 
					        return index[name] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    load_all_profiles() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if name in index: | 
				
			||||||
 | 
					        return index[name] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    raise ImportError("Could not find XConv profile '%s'" % name) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def load_all_profiles(): | 
				
			||||||
 | 
					    for location in profilepaths: | 
				
			||||||
 | 
					        for mod in (x for x in Path(location).iterdir() if x.is_file() and x.suffix == ".py"): | 
				
			||||||
 | 
					            try: | 
				
			||||||
 | 
					                import_module(".profiles.%s" % mod.stem, __package__) | 
				
			||||||
 | 
					            except ImportError: | 
				
			||||||
 | 
					                pass | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return index | 
				
			||||||
@ -0,0 +1,55 @@ | 
				
			|||||||
 | 
					#!/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 <http://www.gnu.org/licenses/>. | 
				
			||||||
 | 
					----------------------------------------------------------- | 
				
			||||||
 | 
					Opus Audiobook profile | 
				
			||||||
 | 
					""" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ..profile import * | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@profile | 
				
			||||||
 | 
					@description("Encode Opus Audiobook") | 
				
			||||||
 | 
					@output(container="ogg", ext="ogg") | 
				
			||||||
 | 
					@defines(stereo="Use two channels", | 
				
			||||||
 | 
					         bitrate="Use custom target bitrate", | 
				
			||||||
 | 
					         voip="Use voice optimization", | 
				
			||||||
 | 
					         fancy="Use 48kbps stereo (For dramatic audiobooks with a lot of music and effects)") | 
				
			||||||
 | 
					@singleaudio | 
				
			||||||
 | 
					def audiobook(task, stream, defines): | 
				
			||||||
 | 
					    out = (task.map_stream(stream) | 
				
			||||||
 | 
					        .set(codec="libopus", | 
				
			||||||
 | 
					            vbr="on", | 
				
			||||||
 | 
					            b="32k", | 
				
			||||||
 | 
					            ac="1", | 
				
			||||||
 | 
					            application="voip")) | 
				
			||||||
 | 
					    if "stereo" in defines: | 
				
			||||||
 | 
					        out.set(ac="2", | 
				
			||||||
 | 
					                b="36k") | 
				
			||||||
 | 
					    if "fancy" in defines: | 
				
			||||||
 | 
					        out.set(ac="2", | 
				
			||||||
 | 
					                b="48k", | 
				
			||||||
 | 
					                application="audio") | 
				
			||||||
 | 
					    if "bitrate" in defines: | 
				
			||||||
 | 
					        out.set(b=defines["bitrate"]) | 
				
			||||||
 | 
					    return True | 
				
			||||||
@ -0,0 +1,14 @@ | 
				
			|||||||
 | 
					from ..profile import * | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@profile | 
				
			||||||
 | 
					@description("Save the first audio track as FLAC.") | 
				
			||||||
 | 
					@output(ext="flac") | 
				
			||||||
 | 
					@singleaudio | 
				
			||||||
 | 
					def flac(task, stream): | 
				
			||||||
 | 
					    if stream.codec == "ape": | 
				
			||||||
 | 
					        stream.file.set(max_samples="all") # Monkey's insane preset is insane. | 
				
			||||||
 | 
					    (task.map_stream(stream) | 
				
			||||||
 | 
					        .set(codec="flac", | 
				
			||||||
 | 
					            compression_level="10")) | 
				
			||||||
 | 
					    return True | 
				
			||||||
@ -0,0 +1,26 @@ | 
				
			|||||||
 | 
					from ..profile import * | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@profile | 
				
			||||||
 | 
					@description("First Video H.264 Main fastdecode animation, max 1280x800; Audio AAC; Keep subtitles") | 
				
			||||||
 | 
					@output(container="matroska", ext="mkv") | 
				
			||||||
 | 
					def laptop(task): | 
				
			||||||
 | 
					    # add first video stream | 
				
			||||||
 | 
					    for s in task.iter_video_streams(): | 
				
			||||||
 | 
					        (task.map_stream(s) | 
				
			||||||
 | 
					            .set(codec="libx264", | 
				
			||||||
 | 
					                tune=("fastdecode", "animation"), | 
				
			||||||
 | 
					                profile="main", | 
				
			||||||
 | 
					                preset="fast") | 
				
			||||||
 | 
					            .downscale(1280, 800)) | 
				
			||||||
 | 
					        break | 
				
			||||||
 | 
					    # Add all audio streams (reencode to aac if necessary) | 
				
			||||||
 | 
					    for s in task.iter_audio_streams(): | 
				
			||||||
 | 
					        os = task.map_stream(s) | 
				
			||||||
 | 
					        if s.codec != "aac": | 
				
			||||||
 | 
					            os.set(codec="aac") | 
				
			||||||
 | 
					    # add all subtitle and attachment streams | 
				
			||||||
 | 
					    for s in chain(task.iter_subtitle_streams(), task.iter_attachment_streams()): | 
				
			||||||
 | 
					        task.map_stream(s) | 
				
			||||||
 | 
					    # go | 
				
			||||||
 | 
					    return True | 
				
			||||||
@ -0,0 +1,20 @@ | 
				
			|||||||
 | 
					from ..profile import * | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@profile | 
				
			||||||
 | 
					@description("Encode Opus Audio") | 
				
			||||||
 | 
					@output(ext="mka", container="matroska") | 
				
			||||||
 | 
					@defines(ogg="Use Ogg/Opus output container", | 
				
			||||||
 | 
					    bitrate="Target bitrate (Default 96k)") | 
				
			||||||
 | 
					@features(argshax=None) | 
				
			||||||
 | 
					@singleaudio | 
				
			||||||
 | 
					def opus(task, stream, defines, args): | 
				
			||||||
 | 
					    os = (task.map_stream(stream) | 
				
			||||||
 | 
					        .set(codec="libopus", | 
				
			||||||
 | 
					            vbr="on") | 
				
			||||||
 | 
					    # Defines | 
				
			||||||
 | 
					        .apply(defines, bitrate="b")) | 
				
			||||||
 | 
					    # Output format | 
				
			||||||
 | 
					    if "ogg" in defines: | 
				
			||||||
 | 
					        task.change_format("ogg", "opus" if args.genout else None) | 
				
			||||||
 | 
					    return True | 
				
			||||||
@ -0,0 +1,13 @@ | 
				
			|||||||
 | 
					from ..profile import * | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@profile | 
				
			||||||
 | 
					@description("Copy all streams to a new container") | 
				
			||||||
 | 
					@defines(format="Container format", | 
				
			||||||
 | 
					    fext="File extension") | 
				
			||||||
 | 
					@features(argshax=None) | 
				
			||||||
 | 
					def remux(task, defines, args): | 
				
			||||||
 | 
					    task.change_format( | 
				
			||||||
 | 
					        defines["format"] if "format" in defines else None, | 
				
			||||||
 | 
					        defines["fext"] if "fext" in defines and args.genout else None) | 
				
			||||||
 | 
					    return all(task.map_stream(s) for s in task.iter_streams()) | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue