Base64 File Converter

base64fileconverter.png

Screenshot of the Tkinter GUI.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Base64 File Converter
=====================

This module offers a command line interface as well as a Tkinter-based
graphical interface to convert files to and from Base64 encoding.  It uses the
``base64`` module that is included in the Python distribution.  See
`RFC 1521`_ for more information on Base64 encoding.

.. _RFC 1521: http://www.faqs.org/rfcs/rfc1521.html

:Copyright: 2004-2008 Jochen Kupperschmidt
:Date: 19-Nov-2008 (previous release: 11-Oct-2004)
:License: GNU General Public License
"""

from __future__ import with_statement
import base64
import binascii
from optparse import OptionParser
import os.path
import sys
import Tkinter as tk
import tkFileDialog
import tkMessageBox


def convert(filename_in, filename_out, decode=False):
    """Read from ``filename_in`` and write to ``filename_out``.

    Encode the data if ``decode`` is false (the default), decode if it is true.
    """
    with open(filename_in, 'rb') as f_in:
        if filename_out is None:
            f_out = sys.stdout
        else:
            f_out = open(filename_out, 'wb')
        func = base64.decode if decode else base64.encode
        try:
            func(f_in, f_out)
        except binascii.Error, errstr:
            print >> sys.stderr, errstr
        if filename_out is not None:
            f_out.close()


class GUI(tk.Tk):
    """A Tkinter frontend."""

    def __init__(self):
        """Create and lay out widgets."""
        tk.Tk.__init__(self)
        self.title('Base64 File Converter')

        # Set up filename and mode variables.
        self.filename_in = tk.StringVar()
        self.filename_out = tk.StringVar()
        self.decode = tk.BooleanVar()
        self.decode.set(False)

        # Create filename widgets.
        for row, (label, var, cmd) in enumerate((
            ('Input', self.filename_in, self.get_in_filename),
            ('Output', self.filename_out, self.get_out_filename),
        )):
            tk.Label(self, text=label + ' File:') \
                .grid(row=row, column=0, sticky=tk.W)
            tk.Entry(self, textvariable=var) \
                .grid(row=row, column=1, sticky=tk.W + tk.E)
            tk.Button(self, text='Browse', command=cmd) \
                .grid(row=row, column=2)

        # Create mode selection and execution widgets.
        tk.Label(self, text='Mode:').grid(row=2, column=0, sticky=tk.W)
        modes = (('encode', False), ('decode', True))
        for row, (label, value) in enumerate(modes):
            tk.Radiobutton(self, text=label, variable=self.decode, value=value,
               anchor=tk.W).grid(row=(row + 2), column=1, sticky=tk.W)
        tk.Button(self, text='Convert', command=self.convert) \
            .grid(row=2, column=2, rowspan=2)

        self.columnconfigure(1, weight=1)
        self.rowconfigure(4, weight=1)

    def get_in_filename(self):
        """Ask user for the input filename."""
        self.filename_in.set(tkFileDialog.askopenfilename())

    def get_out_filename(self):
        """Ask user for the output filename."""
        self.filename_out.set(tkFileDialog.askopenfilename())

    def convert(self):
        """Execute the conversion."""
        if not self.filename_in.get():
            tkMessageBox.showerror('Error', 'No input filename specified.')
            return
        if not self.filename_out.get():
            tkMessageBox.showerror('Error', 'No output filename specified.')
            return

        print '%s from %s to %s ...' % (
            (self.decode.get() and 'Decoding' or 'Encoding'),
            os.path.basename(self.filename_in.get()),
            os.path.basename(self.filename_out.get())
        )
        try:
            convert(self.filename_in.get(), self.filename_out.get(),
                self.decode.get())
            tkMessageBox.showinfo('Info', 'Conversion complete.')
        except IOError, exc:
            tkMessageBox.showerror('Error', exc)


def main():
    parser = OptionParser()
    parser.add_option('-c', '--cli', dest='gui',
        action='store_false', help='use the command line interface (default)')
    parser.add_option('-d', '--decode', dest='decode',
        action='store_true', help='decode the data')
    parser.add_option('-e', '--encode', dest='decode',
        action='store_false', help='encode the data (default)')
    parser.add_option('-g', '--gui', dest='gui',
        action='store_true', help='use the user graphical interface')
    parser.add_option('-i', '--input', dest='input',
        metavar='FILE', help='input filename')
    parser.add_option('-o', '--output', dest='output',
        metavar='FILE', help='output filename')
    parser.set_defaults(decode=False, gui=False)

    opts, args = parser.parse_args()
    if opts.gui:
        # Use the graphical user interface.
        GUI().mainloop()
        return

    # Use the command line interface.

    if not opts.input:
        parser.error('Please specify an input filename.')
        parser.print_help()
        parser.exit()

    try:
        convert(opts.input, opts.output, opts.decode)
    except IOError, (errno, errstr):
        print >> sys.stderr, 'Error:', errstr

if __name__ == '__main__':
    main()