コード例 #1
0
ファイル: cli.py プロジェクト: onlyquake/quake-cli-tools
def main():
    parser = Parser(
        prog='bsp2svg',
        description='Create an svg document from the given bsp file.',
        epilog='example: bsp2svg e1m1.bsp => creates the svg file e1m1.svg')

    parser.add_argument('file', metavar='file.bsp', action=ResolvePathAction)

    parser.add_argument('-d',
                        metavar='file.svg',
                        dest='dest',
                        default=os.getcwd(),
                        action=ResolvePathAction,
                        help='svg file to create')

    parser.add_argument('-i',
                        '--ignore',
                        dest='ignore',
                        metavar='name',
                        nargs='*',
                        default=[],
                        help='texture names to ignore')

    parser.add_argument('-q',
                        dest='quiet',
                        action='store_true',
                        help='quiet mode')

    parser.add_argument('-v',
                        '--version',
                        dest='version',
                        action='version',
                        help=argparse.SUPPRESS,
                        version=f'{parser.prog} version {qcli.__version__}')

    args = parser.parse_args()

    if not bsp.is_bspfile(args.file):
        print(f'{parser.prog}: cannot find or open {args.file}',
              file=sys.stderr)

    # Validate or create out file
    if args.dest == os.getcwd():
        svg_path = os.path.dirname(args.file)
        svg_name = f'{os.path.basename(args.file).split(".")[0]}.svg'
        args.dest = os.path.join(svg_path, svg_name)

    dest_dir = os.path.dirname(args.dest) or '.'
    if not os.path.exists(dest_dir):
        os.makedirs(dest_dir, exist_ok=True)

    converter.convert(args.file, args.dest, args)

    sys.exit(0)
コード例 #2
0
def main():
    parser = Parser(
        prog='pak',
        description='Default action is to add or replace pak files '
                    'entries from list.\nIf list is omitted, pak will '
                    'use stdin.',
        epilog='example: pak tex.pak image.png => adds image.png to tex.pak'
    )

    parser.add_argument(
        'file',
        metavar='file.pak',
        action=ResolvePathAction,
        help='pak file to create'
    )

    parser.add_argument(
        'list',
        nargs='*',
        action=ResolvePathAction,
        default=read_from_stdin()
    )

    parser.add_argument(
        '-q',
        dest='quiet',
        action='store_true',
        help='quiet mode'
    )

    parser.add_argument(
        '-v', '--version',
        dest='version',
        action='version',
        help=argparse.SUPPRESS,
        version='{} version {}'.format(parser.prog, qcli.__version__)
    )

    args = parser.parse_args()

    if not args.list:
        parser.error('the following arguments are required: list')

    dir = os.path.dirname(args.file) or '.'
    if not os.path.exists(dir):
        os.makedirs(dir)

    filemode = 'a'
    if not os.path.isfile(args.file):
        filemode = 'w'

    with pak.PakFile(args.file, filemode) as pak_file:
        if not args.quiet:
            print(f'Archive: {os.path.basename(args.file)}')

        # Process input files
        for file in args.list:
            # Walk directories
            if os.path.isdir(file):
                for root, dirs, files in os.walk(file):
                    for name in [f for f in files if not f.startswith('.')]:
                        fullpath = os.path.join(root, name)
                        relpath = os.path.relpath(fullpath, os.getcwd())

                        if not args.quiet:
                            print(f'  adding: {relpath}')

                        pak_file.write(relpath)

            else:
                relpath = os.path.relpath(file, os.getcwd())

                if not args.quiet:
                    print(f'  adding: {relpath}')

                pak_file.write(relpath)

    sys.exit(0)
コード例 #3
0
ファイル: cli.py プロジェクト: Sickelmo83/quake-cli-tools
def main():
    """CLI entrypoint"""

    parser = Parser(
        prog='unwad',
        description=
        'Default action is to convert files to png format and extract to xdir.',
        epilog='example: unwad gfx.wad -d ./out => extract all files to ./out')

    parser.add_argument('file', metavar='file.wad', action=ResolvePathAction)

    parser.add_argument('-l', '--list', action='store_true', help='list files')

    parser.add_argument('-d',
                        metavar='xdir',
                        default=os.getcwd(),
                        dest='dest',
                        action=ResolvePathAction,
                        help='extract files into xdir')

    parser.add_argument('-q',
                        dest='quiet',
                        action='store_true',
                        help='quiet mode')

    parser.add_argument('-f',
                        dest='format',
                        default='png',
                        choices=['bmp', 'gif', 'png', 'tga'],
                        help='image format to convert to')

    parser.add_argument(
        '-v',
        '--version',
        dest='version',
        action='version',
        help=argparse.SUPPRESS,
        version=f'{parser.prog} version {qcli.unwad.__version__}')

    args = parser.parse_args()

    archive_name = os.path.basename(args.file)

    if not wad.is_wadfile(args.file):
        print(f'{parser.prog}: cannot find or open {args.file}',
              file=sys.stderr)
        sys.exit(1)

    if args.list:
        with wad.WadFile(args.file) as wad_file:
            info_list = sorted(wad_file.infolist(), key=lambda i: i.filename)

            lump_types = {
                0: 'NONE',
                1: 'LABEL',
                64: 'LUMP',
                65: 'QTEX',
                66: 'QPIC',
                67: 'SOUND',
                68: 'MIPTEX'
            }

            def lump_type(num):
                if num in lump_types:
                    return lump_types[num]

                return num

            headers = ['Length', 'Type', 'Name']
            table = [[i.file_size, lump_type(i.type), i.filename]
                     for i in info_list]
            length = sum([i.file_size for i in info_list])
            count = len(info_list)
            table.append(
                [length, '', f'{count} file{"s" if count > 1 else ""}'])

            separator = []
            for i in range(len(headers)):
                t = max(len(str(length)), len(headers[i]) + 2)
                separator.append('-' * t)

            table.insert(-1, separator)

            print(f'Archive: {archive_name}')
            print(tabulate(table, headers=headers))

            sys.exit(0)

    if not os.path.exists(args.dest):
        os.makedirs(args.dest)

    with wad.WadFile(args.file) as wad_file:
        if not args.quiet:
            print(f'Archive: {archive_name}')

        # Flatten out palette
        palette = []
        for p in quake.palette:
            palette += p

        for item in wad_file.infolist():
            filename = item.filename
            fullpath = os.path.join(args.dest, filename)
            fullpath_ext = '{0}.{1}'.format(fullpath, args.format)

            data = None
            size = None

            # Pictures
            if item.type == wad.LumpType.QPIC:
                with wad_file.open(filename) as lmp_file:
                    lump = lmp.Lmp.open(lmp_file)
                    size = lump.width, lump.height
                    data = array.array('B', lump.pixels)

            # Special cases
            elif item.type == wad.LumpType.MIPTEX:
                # Console characters
                if item.file_size == 128 * 128:
                    size = 128, 128

                    with wad_file.open(filename) as lump:
                        data = lump.read(item.file_size)

                else:
                    # Miptextures
                    try:
                        with wad_file.open(filename) as mip_file:
                            mip = wad.Miptexture.read(mip_file)
                            data = mip.pixels[:mip.width * mip.height]
                            data = array.array('B', data)
                            size = mip.width, mip.height
                    except:
                        print(f' failed to extract resource: {item.filename}',
                              file=sys.stderr)
                        continue

            try:
                # Convert to image file
                if data is not None and size is not None:
                    img = Image.frombuffer('P', size, data, 'raw', 'P', 0, 1)
                    img.putpalette(palette)
                    img.save(fullpath_ext)

                    if not args.quiet:
                        print(f' extracting: {fullpath_ext}')

                # Extract as raw file
                else:
                    wad_file.extract(filename, args.dest)

                    if not args.quiet:
                        print(f' extracting: {fullpath}')
            except:
                print(f'{parser.prog}: error: {sys.exc_info()[1]}',
                      file=sys.stderr)

    sys.exit(0)
コード例 #4
0
ファイル: cli.py プロジェクト: Sickelmo83/quake-cli-tools
def main():
    parser = Parser(
        prog='bsp2wad',
        description='Default action is to create a wad archive from '
        'miptextures extracted from the given bsp file.'
        '\nIf list is omitted, pak will use stdin.',
        epilog='example: bsp2wad e1m1.bsp => creates the wad file e1m1.wad')

    parser.add_argument('list',
                        nargs='*',
                        action=ResolvePathAction,
                        default=read_from_stdin())

    parser.add_argument('-d',
                        metavar='file.wad',
                        dest='dest',
                        default=os.getcwd(),
                        action=ResolvePathAction,
                        help='wad file to create')

    parser.add_argument('-q',
                        dest='quiet',
                        action='store_true',
                        help='quiet mode')

    parser.add_argument(
        '-v',
        '--version',
        dest='version',
        action='version',
        help=argparse.SUPPRESS,
        version=f'{parser.prog} version {qcli.bsp2wad.__version__}')

    args = parser.parse_args()

    if not args.list:
        parser.error('the following arguments are required: list')

    miptextures = []

    for file in args.list:
        if not bsp.is_bspfile(file):
            print('{0}: cannot find or open {1}'.format(parser.prog, file),
                  file=sys.stderr)
            continue

        bsp_file = bsp.Bsp.open(file)
        miptextures += [
            mip for mip in bsp_file.miptextures
            if mip and mip.name not in [n.name for n in miptextures]
        ]

    if args.dest == os.getcwd():
        wad_path = os.path.dirname(file)

        if len(args.list) == 1:
            wad_name = f'{os.path.basename(file).split(".")[0]}.wad'

        else:
            wad_name = 'out.wad'

        args.dest = os.path.join(wad_path, wad_name)

    dir = os.path.dirname(args.dest) or '.'
    if not os.path.exists(dir):
        os.makedirs(dir)

    with wad.WadFile(args.dest, mode='w') as wad_file:
        if not args.quiet:
            print(f'Archive: {os.path.basename(args.dest)}')

        for miptex in miptextures:
            if not miptex:
                continue

            buff = io.BytesIO()
            wad.Miptexture.write(buff, miptex)
            buff.seek(0)

            info = wad.WadInfo(miptex.name)
            info.file_size = 40 + len(miptex.pixels)
            info.disk_size = info.file_size
            info.compression = wad.CompressionType.NONE
            info.type = wad.LumpType.MIPTEX

            if not args.quiet:
                print(f' adding: {info.filename}')

            wad_file.writestr(info, buff)

    sys.exit(0)
コード例 #5
0
ファイル: cli.py プロジェクト: onlyquake/quake-cli-tools
def main():
    parser = Parser(
        prog='image2spr',
        description='Default action is to convert an image file(s) to an '
        'spr.\nIf image file is omitted, image2spr will use stdin.',
        epilog='example: image2spr anim.spr anim.gif => converts anim.gif to '
        'anim.spr')

    parser.add_argument('dest_file',
                        metavar='file.spr',
                        action=ResolvePathAction,
                        help='spr file to create')

    parser.add_argument('source_files',
                        nargs='*',
                        metavar='file.gif',
                        action=ResolvePathAction,
                        default=read_from_stdin(),
                        help='image source file')

    parser.add_argument('-t',
                        dest='type',
                        default=0,
                        help='sprite orientation type')

    parser.add_argument('-q',
                        dest='quiet',
                        action='store_true',
                        help='quiet mode')

    parser.add_argument('-v',
                        '--version',
                        dest='version',
                        action='version',
                        help=argparse.SUPPRESS,
                        version=f'{parser.prog} version {qcli.__version__}')

    args = parser.parse_args()

    # Flatten out palette
    quake_palette = [channel for rgb in vgio.quake.palette for channel in rgb]

    # Create palette image for Image.quantize()
    quake_palette_image = Image.frombytes('P', (16, 16), bytes(quake_palette))
    quake_palette_image.putpalette(quake_palette)

    images = []

    # Build a list of source images
    for source_file in args.source_files:
        if not os.path.exists(source_file):
            print(f'{parser.prog}: cannot find or open {source_file}',
                  file=sys.stderr)
            continue

        # Open source image
        source_image = Image.open(source_file)
        size = source_image.size
        source_mode = source_image.mode
        global_transparency = source_image.info.get('transparency')

        # Decompose the source image frames into a sequence of images
        try:
            while True:
                if source_image.mode != 'P':
                    alpha = source_image.split()[-1]

                    # Set all alpha pixels to a known color
                    source_image = source_image.convert('RGB')

                    mask = Image.eval(alpha, lambda a: 255 if a <= 128 else 0)
                    transparent_color = tuple(quake_palette[-3:])
                    source_image.paste(transparent_color, mask)
                    source_image.info['transparency'] = 255

                    source_image = source_image.quantize(
                        palette=quake_palette_image)
                    source_image.putpalette(bytes(quake_palette))

                # Set the current palette's transparent color to Quake's
                local_transparency = source_image.info.get('transparency')
                source_palette = source_image.palette.palette
                source_palette = list(
                    struct.unpack(f'{len(source_palette)}B', source_palette))

                if local_transparency:
                    source_palette[local_transparency *
                                   3:local_transparency * 3 +
                                   3] = vgio.quake.palette[-1]

                if global_transparency and global_transparency != local_transparency:
                    source_palette[global_transparency *
                                   3:global_transparency * 3 +
                                   3] = vgio.quake.palette[-1]

                source_palette = bytes(source_palette)

                # Create new image from current frame
                data = source_image.tobytes()
                sub_image = Image.frombytes('P', size, data, 'raw', 'P', 0, 1)

                if local_transparency:
                    sub_image.info['transparency'] = local_transparency

                sub_image.putpalette(source_palette)

                # Convert from indexed color to RGB color then quantize to Quake's palette
                sub_image = sub_image.convert('RGB', dither=None)
                sub_image = sub_image.quantize(palette=quake_palette_image)
                sub_image.info['transparency'] = 255
                sub_image.putpalette(bytes(quake_palette))
                images.append(sub_image)
                source_image.seek(source_image.tell() + 1)

        except EOFError:
            pass

    if not images:
        print(f'{parser.prog}: no usable source images given', file=sys.stderr)
        sys.exit(1)

    # Normalize image sizes
    if len(images) > 1:
        sizes = [image.size for image in images]

        images_all_same_size = all([size[0] == size for size in sizes])

        if not images_all_same_size:
            resized_images = []
            max_width = max([size[0] for size in sizes])
            max_height = max([size[1] for size in sizes])

            for image in images:
                resized_image = Image.new('P', (max_width, max_height), 255)
                resized_image.putpalette(bytes(quake_palette))

                top = (max_height - image.size[1]) // 2
                left = (max_width - image.size[0]) // 2
                top_left = top, left
                resized_image.paste(image, box=top_left)
                resized_images.append(resized_image)

            images = resized_images

    # Build Quake sprite
    with spr.Spr.open(args.dest_file, 'w') as spr_file:
        spr_file.width, spr_file.height = size
        spr_file.number_of_frames = len(images)
        spr_file.type = int(args.type)

        origin = -size[0] // 2, size[1] // 2

        for image in images:
            frame = spr.SpriteFrame()
            frame.width, frame.height = size
            frame.origin = origin
            data = image.tobytes()
            data = struct.unpack(f'{frame.width * frame.height}B', data)
            frame.pixels = data
            spr_file.frames.append(frame)

    sys.exit(0)
コード例 #6
0
ファイル: cli.py プロジェクト: Sickelmo83/quake-cli-tools
def main():
    parser = Parser(
        prog='unpak',
        description='Default action is to extract files to xdir.',
        epilog='example: unpak PAK0.PAK -d ./out => extract all files to ./out'
    )

    parser.add_argument(
        'file',
        metavar='file.pak',
        action=ResolvePathAction
    )

    parser.add_argument(
        '-l', '--list',
        action='store_true',
        help='list files'
    )

    parser.add_argument(
        '-d',
        metavar='xdir',
        dest='dest',
        default=os.getcwd(),
        action=ResolvePathAction,
        help='extract files into xdir'
    )

    parser.add_argument(
        '-q',
        dest='quiet',
        action='store_true',
        help='quiet mode'
    )

    parser.add_argument(
        '-v', '--version',
        dest='version',
        action='version',
        help=argparse.SUPPRESS,
        version=f'{parser.prog} version {qcli.unpak.__version__}'
    )

    args = parser.parse_args()

    if not pak.is_pakfile(args.file):
        print(f'{parser.prog}: cannot find or open {args.file}', file=sys.stderr)
        sys.exit(1)

    if args.list:
        with pak.PakFile(args.file) as pak_file:
            info_list = sorted(pak_file.infolist(), key=lambda i: i.filename)

            headers = ['Length', 'Name']
            table = [[i.file_size, i.filename] for i in info_list]
            length = sum([i.file_size for i in info_list])
            count = len(info_list)
            table.append([length, f'{count} file{"s" if count == 1 else ""}'])

            separator = []
            for i in range(len(headers)):
                t = max(len(str(length)), len(headers[i]) + 2)
                separator.append('-' * t)

            table.insert(-1, separator)

            print(f'Archive: {os.path.basename(args.file)}')
            print(tabulate(table, headers=headers))

            sys.exit(0)

    with pak.PakFile(args.file) as pak_file:
        info_list = pak_file.infolist()
        for item in sorted(info_list, key=lambda i: i.filename):
            filename = item.filename
            fullpath = os.path.join(args.dest, filename)

            if not args.quiet:
                print(f' extracting: {fullpath}')

            try:
                pak_file.extract(filename, args.dest)

            except:
                print(f'{parser.prog}: error: {sys.exc_info()[0]}', file=sys.stderr)

    sys.exit(0)
コード例 #7
0
ファイル: cli.py プロジェクト: Sickelmo83/quake-cli-tools
def main():
    # Fix for frozen packages
    def handleSIGINT(signum, frame):
        raise KeyboardInterrupt

    signal.signal(signal.SIGINT, handleSIGINT)

    parser = Parser(
        prog='qmount',
        description=
        'Default action is to mount the given pak file as a logical volume.',
        epilog=
        'example: qmount TEST.PAK => mounts TEST.PAK as a logical volume.')

    parser.add_argument('file',
                        metavar='file.pak',
                        action=ResolvePathAction,
                        help='pak file to mount')

    parser.add_argument('-f',
                        '--file-browser',
                        dest='open_file_browser',
                        action='store_true',
                        help='opens a file browser once mounted')

    parser.add_argument('--verbose',
                        dest='verbose',
                        action='store_true',
                        help='verbose mode')

    parser.add_argument(
        '-v',
        '--version',
        dest='version',
        action='version',
        help=argparse.SUPPRESS,
        version=f'{parser.prog} version {qcli.qmount.__version__}')

    args = parser.parse_args()

    dir = os.path.dirname(args.file) or '.'
    if not os.path.exists(dir):
        os.makedirs(dir)

    archive_name = os.path.basename(args.file)
    context = {'dirty': False}
    files = {}

    # If the pak file exists put the contents into the file dictionary
    if os.path.exists(args.file):
        with pak.PakFile(args.file) as pak_file:
            for info in pak_file.infolist():
                name = info.filename
                files[name] = pak_file.read(name)

    else:
        context['dirty'] = True

    temp_directory = platforms.temp_volume(archive_name)

    # Copy pak file contents into the temporary directory
    for filename in files:
        abs_path = os.path.join(temp_directory, filename)
        dir = os.path.dirname(abs_path)

        if not os.path.exists(dir):
            os.makedirs(dir)

        with open(abs_path, 'wb') as out_file:
            out_file.write(files[filename])

    # Open a native file browser
    if args.open_file_browser:
        platforms.open_file_browser(temp_directory)

    # Start file watching
    observer = Observer()
    handler = TempPakFileHandler(
        context,
        temp_directory,
        files,
        args.verbose,
        ignore_patterns=['*/.DS_Store', '*/Thumbs.db'],
        ignore_directories=True)
    observer.schedule(handler, path=temp_directory, recursive=True)

    print('Press Ctrl+C to save and quit')

    observer.start()

    # Wait for user to terminate
    try:
        while True:
            time.sleep(1)

            # Detect the deletion of the watched directory.
            if not os.path.exists(temp_directory):
                raise KeyboardInterrupt

    except KeyboardInterrupt:
        print()
        try:
            observer.stop()

        except:
            """This is a temporary workaround. Watchdog will raise an exception
            if the watched media is ejected."""

    observer.join()

    # Write out updated files
    if context['dirty']:
        print(f'Updating changes to {archive_name}')

        with pak.PakFile(args.file, 'w') as pak_file:
            for filename in files:
                pak_file.writestr(filename, files[filename])

    else:
        print(f'No changes detected to {archive_name}')

    # Clean up temp directory
    platforms.unmount_temp_volume(temp_directory)

    sys.exit(0)
コード例 #8
0
ファイル: cli.py プロジェクト: onlyquake/quake-cli-tools
def main():
    parser = Parser(
        prog='spr2image',
        description='Default action is to convert a spr file to a gif.',
        epilog='example: spr2image bubble.spr => convert bubble.spr to bubble.gif'
    )

    parser.add_argument(
        'file',
        metavar='file.spr',
        action=ResolvePathAction
    )

    parser.add_argument(
        '-d',
        metavar='file.gif',
        dest='dest',
        default=os.getcwd(),
        action=ResolvePathAction,
        help='image file to create'
    )

    parser.add_argument(
        '-q',
        dest='quiet',
        action='store_true',
        help='quiet mode'
    )

    parser.add_argument(
        '-v', '--version',
        dest='version',
        action='version',
        help=argparse.SUPPRESS,
        version=f'{parser.prog} version {qcli.__version__}'
    )

    args = parser.parse_args()

    # Validate source file
    if not spr.is_sprfile(args.file):
        print(f'{parser.prog}: cannot find or open {args.file}', file=sys.stderr)
        sys.exit(1)

    # Validate or create out file
    if args.dest == os.getcwd():
        image_path = os.path.dirname(args.file)
        image_name = os.path.basename(args.file).split('.')[0] + '.gif'
        args.dest = os.path.join(image_path, image_name)

    dest_dir = os.path.dirname(args.dest) or '.'
    if not os.path.exists(dest_dir):
        os.makedirs(dest_dir)

    image_filename = os.path.basename(args.dest)
    image_extension = image_filename.split('.')[-1]

    with spr.Spr.open(args.file) as spr_file:
        if not args.quiet:
            print(f'Converting: {os.path.basename(args.file)}')

        # Flatten out palette
        palette = [channel for rgb in vgio.quake.palette for channel in rgb]

        # Default frame animation is 10 frames per second
        default_duration = 10 / 60 * 1000

        # Build a sequence of images from spr frames
        images = []
        for frame in spr_file.frames:
            if frame.type == spr.SINGLE:
                size = frame.width, frame.height
                data = array.array('B', frame.pixels)

                img = Image.frombuffer('P', size, data, 'raw', 'P', 0, 1)
                img.putpalette(palette)
                images.append(img)

            else:
                print(f'{parser.prog}: frame groups are not supported', file=sys.stderr)
                sys.exit(1)

    # Save as gif
    if image_extension.upper() == 'GIF':
        first_frame = images[0]
        first_frame.putpalette(palette)
        remaining_frames = images[1:]
        first_frame.save(
            args.dest,
            save_all=True,
            append_images=remaining_frames,
            duration=default_duration,
            loop=0,
            optimize=False,
            #transparency=255,
            palette=palette
        )

    else:
        image_directory = os.path.dirname(args.dest)
        image_name = image_filename.split('.')[0]
        for image_index, image in enumerate(images):
            filename = '{}_{}.{}'.format(image_name, image_index, image_extension)
            image.save(
                os.path.join(image_directory, filename),
                optimize=False,
                #transparency=255,
                palette=palette
            )

    sys.exit(0)
コード例 #9
0
ファイル: cli.py プロジェクト: onlyquake/quake-cli-tools
def main():
    """CLI entrypoint"""

    # Create and configure argument parser
    parser = Parser(
        prog='wad',
        description='Default action is to add or replace wad file entries from'
        ' list.\nIf list is omitted, wad will use stdin.',
        formatter_class=argparse.RawTextHelpFormatter,
        epilog='example:\n  wad tex.wad image.png => adds image.png to tex.wad'
    )

    parser.add_argument('file',
                        metavar='file.wad',
                        action=ResolvePathAction,
                        help='wad file to add entries to')

    parser.add_argument('list',
                        nargs='*',
                        action=ResolvePathAction,
                        default=read_from_stdin())

    parser.add_argument('-t',
                        dest='type',
                        default='MIPTEX',
                        choices=['LUMP', 'QPIC', 'MIPTEX'],
                        help='list data type [default: MIPTEX]')

    parser.add_argument('-q',
                        dest='quiet',
                        action='store_true',
                        help='quiet mode')

    parser.add_argument('-v',
                        '--version',
                        dest='version',
                        action='version',
                        version='{} version {}'.format(parser.prog,
                                                       qcli.__version__))

    # Parse the arguments
    args = parser.parse_args()

    if not args.list:
        parser.error('the following arguments are required: list')

    if args.quiet:

        def log(message):
            pass

    else:

        def log(message):
            print(message)

    # Ensure directory structure
    dir = os.path.dirname(args.file) or '.'
    os.makedirs(dir, exist_ok=True)

    filemode = 'a'
    if not os.path.isfile(args.file):
        filemode = 'w'

    with wad.WadFile(args.file, filemode) as wad_file:
        log(f'Archive: {os.path.basename(args.file)}')

        # Flatten out palette
        palette = []
        for p in quake.palette:
            palette += p

        # Create palette image for Image.quantize()
        palette_image = Image.frombytes('P', (16, 16), bytes(palette))
        palette_image.putpalette(palette)

        # Process input files
        for file in args.list:
            if args.type == 'LUMP':
                log(f'  adding: {file}')
                wad_file.write(file)

            elif args.type == 'QPIC':
                img = Image.open(file).convert(mode='RGB')
                img = img.quantize(palette=palette_image)
                pixels = img.tobytes()
                name = os.path.basename(file).split('.')[0]

                qpic = lmp.Lmp()
                qpic.width = img.width
                qpic.height = img.height
                qpic.pixels = pixels

                buff = io.BytesIO()
                lmp.Lmp.write(buff, qpic)
                file_size = buff.tell()
                buff.seek(0)

                info = wad.WadInfo(name)
                info.file_size = file_size
                info.disk_size = info.file_size
                info.compression = wad.CompressionType.NONE
                info.type = wad.LumpType.QPIC

                log(f'  adding: {file}')

                wad_file.writestr(info, buff)

            else:
                try:
                    img = Image.open(file).convert(mode='RGB')
                    img = img.quantize(palette=palette_image)

                    name = os.path.basename(file).split('.')[0]

                    mip = wad.Miptexture()
                    mip.name = name
                    mip.width = img.width
                    mip.height = img.height
                    mip.offsets = [40]
                    mip.pixels = []

                    # Build mip maps
                    for i in range(4):
                        resized_image = img.resize(
                            (img.width // pow(2, i), img.height // pow(2, i)))
                        data = resized_image.tobytes()
                        mip.pixels += struct.unpack(f'<{len(data)}B', data)
                        if i < 3:
                            mip.offsets += [mip.offsets[-1] + len(data)]

                    buff = io.BytesIO()
                    wad.Miptexture.write(buff, mip)
                    buff.seek(0)

                    info = wad.WadInfo(name)
                    info.file_size = 40 + len(mip.pixels)
                    info.disk_size = info.file_size
                    info.compression = wad.CompressionType.NONE
                    info.type = wad.LumpType.MIPTEX

                    log(f'  adding: {file}')

                    wad_file.writestr(info, buff)

                except:
                    parser.error(sys.exc_info()[1])

    sys.exit(0)