Programming can be fun again
BY SHAWN GORDON, OLEKSANDR YAKOVLYEV

This article shows you how to develop a straightforward, multiplatform application for ripping CDs into the Ogg Vorbis format. Follow along to see how elegant and quick it is to develop using PyQt.

BlackAdder is an IDE for developing Python and PyQt based applications. Python is a very popular multiplatform, object-oriented scripting language that has been around for some time; you can download it for free from www.python.org. PyQt is the Qt language bindings for Python developed by Riverbank Computing some years ago; you can find out more at www.riverbankcomputing.co.uk. And if you aren’t familiar with Qt, it is a multiplatform windowing toolkit created by Trolltech (www.trolltech.com) and is the foundation of KDE (www.kde.org), a popular open source windowing desktop available on Linux.

BlackAdder brings all these elements together, including the Qt Designer for creating user interfaces, and allows you to create projects to organize your files, a syntax-highlighting text editor, a Python debugger, and many other useful features. BlackAdder runs on both Linux and Windows and allows you to create applications that will run anywhere that Python and PyQt run. You can even make Python applications that don’t require PyQt. BlackAdder comes in two flavors, the Personal Edition, which allows you to create GPL applications, and the Business Edition, which includes commercial licenses of Qt for PyQt, and PyQt, so you can create closed source, commercial applications.

This article shows how you can use BlackAdder to develop a straightforward, multiplatform application for ripping CDs into the popular open source Ogg Vorbis format (www.vorbis.com) (see Figure 1). What you’ll notice is how elegant and quick it is to develop using PyQt as opposed to C++.

This project assumes you are working on Linux; the entire project can be downloaded from www.smga3000.com/thekompany/BAdemo/tkcoggripper.tar.gz, and a demo of BlackAdder can be downloaded from www.smga3000.com/thekompany/BAdemo. In short, you’ll:

  • Build an application to rip CDs and encode it as an Ogg Vorbis file with ID tags
  • Query the CDDB server for information about the CD
  • Produce a clean and easy user interface

To do this you’ll need:

An attractive user interface is always important, so we’ve put together a collection of attractive icons, listed below. They’re included in the project archive.

  • disk.png
  • drive.png
  • eject.png
  • older.png
  • folder_home.png
  • folder_open.png
  • logo_home.png
  • oggripper16.png
  • oggripper32.png
  • options.png
  • query.png
  • select.png
  • select_folder.png
  • stop.png
  • track.png

Creating the user interface before the program logic is a good way to start – function follows form. We’ll use Qt Designer (BlackAdder’s form editor) to create the main window form. It helps if you have some experience with Qt Designer; however, it’s pretty straightforward. The main thing you need to know is that you’re grouping widgets in layouts so that your form will be resizable and will work with any font.

We have a group of action buttons on the top. Below, a tab widget contains a “Directories” tab so you can select a destination directory for the ripped files and a “CD” tab for working with the track information. Below that is the main button to stop the ripping process and progress bars that show information about the progress per track and per disk. Figures 2, 3, and 4 show the user interface in different stages of development.

You can quickly create this form for yourself (see Figure 5). First define slots that the form will use; these are the actions you’ll need:

  • query cddb server
  • eject
  • stop/quit
  • options
  • select tracks
  • select destination folder
  • rip

So you’ll create these slots:

  • query()
  • eject()
  • stop()
  • options()
  • selectFolder()
  • folderClicked(item) for QListView.clicked(QListViewItem)
  • ripTracks()

First look at init(), shown in Listing 1.

FolderItem is a subclass of QListViewItem for showing a directory tree. It’s easy to implement the class – just 70 lines. You can write it yourself or find an example on the Net from other projects.

The init() call sets column widths, the CD-ROM device name, and the tab to switch to the page with the directories.

For the call eject() you just run eject command:


    def eject(self, *args):
         self.listTracks.clear()
         self.labelHelp.setText("Ejecting...")
         os.system("eject")
         self.labelHelp.setText("Ready...")

Now look at query() call, shown in Listing 2. It is a bit complex.

You first query the CD device to get track information (DiscID), then fill the ListView with tracknames Track1… and query the CDDB server for more information about the disc. You then change track names to those names you retrieved. (We’ve omitted code for showing track length and left it as an exercise for the reader.)

Now it’s time to implement the ripping process. The call to ripTracks() just creates a list of tracks that will be ripped. If the user doesn’t select any tracks, then just rip them all. We then run ripTrack() to begin ripping the first track.

Look at ripTrack():. It is a long but simple function. It forms arguments for cdparanoia and for oggenc and then runs these two processes using QProcess() (see Listing 3).

You may be wondering how it works. You first start a system process for ripping and encoding. You use QProcess, so work with these processes is asynchronous; this is important so that multiple actions can take place at the same time, such as the actual ripping and showing the progress of the ripping. You can do this in one of two ways: using Qt’s QProcess or using Python’s Threads.

After you start to process your function’s self.ripStdout self.ripStderr, you’ll receive all the output of cdparanoia. (We passed the argument “-“, so all raw output will send to stdout.).Oggenc will wait for the raw input to stdin, so our function ripStdout() will be very short:


     def ripStdout(self, *args):
         self.penc.writeToStdin(self.pcdp.readStdout())

Notice that we’ve implemented a pipe between the two processes.

Next you need to grab the verbose output of cdparanoia from stderr and according to that information show the progress of the rip. CDparanoise will provide a string like “from sector NNN” and “to sector NNN” so you can parse it out pretty easily with regexps. Now you just grab the current sector and count progress as 100% * (sector – fromsector)/(tosector – fromsector).

Next you need to look at the encFinished function. It has code for disconnecting from the processes; set the progress and prepare to rip the next track if one exists.

We’ve only touched on core aspects of the application. We’ve created a pretty sophisticated application here, and it’s only about 400 lines of code plus the work of creating the form in the designer, which is trivial. We’ve left out the dialog that allows you to set ogg encoding options such as quality, bitrate, etc., but this is a good exercise for you to test your skills. This same application written in C++ had more than two times the amount of code. Another advantage is that Python is interpreted, so you can quickly prototype, test, and debug your application without having to wait for compiles, making the development process even quicker.

These powerful and easy-to-use tools can make you extremely productive, and dare I say it, can even make programming fun again.