beaTunes News

Tuesday, February 24, 2009

Just released: beaTunes 2.0.0b

beaTunes2 logo

It's been a busy day, here at tagtraum industries. I finally got around releasing beaTunes 2, and of course this kind of thing never goes smoothly. Even though there were so many Early Access releases and I got so much valuable feedback from early testers, some things still go wrong.

It wasn't anything major, but right after the initial release, I did some sanity testing to make 200% sure that things work the way they should... and they didn't. Minor things. And then one of those early adopters contacted me and made me aware of another issue. And then there was another issue. Anyhow. All those issues are now fixed and I uploaded version 2.0.0b to the server.

Now, because these were quick and easy fixes, the 2.0.0b release will still claim that it is a 2.0.0 release. And there is no way that you can tell the difference, except for the version number of the disk image file or the Windows installer executable. So if you just installed beaTunes 2, please make sure you have the latest version by checking the version number of your installation file.

To make folks aware of this, the built-in update checker will claim that there is a new version out there - regardless of whether you have the latest version or not. I know, this is a little misleading and looks like a bug, but I rather have people download the newest and shiniest beaTunes once too often, than have them stumbling over bugs that are already fixed. I hope you understand.

Now, that said: beaTunes 2 is a huge step forward.

The short list of what's new:

  • Completely rewritten internal database
  • Much better support for huge libraries
  • Ability to recognize multiple libraries
  • Analysis on Windows is much faster
  • Tonal/musical key detection
  • Second BPM detection algorithm
  • New inspections (compilation, duplicates, rare genres, different ratings, sort issues, Amazon album version)
  • More accurate typo inspections
  • Support for sort names
  • Ability to specify filters when creating Matchlists
  • Matchlists can now be refreshed
  • Ability to embed non-iTunes fields into audio files
  • Search by artist/album/etc.
  • German localization
  • Updated UI (on OS X, check out that Leopard theme!)
  • Overall usability improvements

This is the first update in 2.5 years that I am going to charge for. This means, that if you own a beaTunes 1.x license, you can purchase the upgrade for $11.95 (USD) [as of 2/24/2009].

If you have bought your beaTunes 1.x license after 2/1/2009, you should already have received a free upgrade license.

Thanks again to all the people who helped to make this happen for their continued support. This includes all the testers, customers, friends and open source software authors, without whom beaTunes would not be possible.

I hope you enjoy my software.

Labels: ,

Saturday, February 21, 2009

Creating your own Song Context Components

beaTunes2 logo

As a beaTunes user you know that the matching song table and the Amazon song info panel are integral parts of the application. Both are hosted in the same space, right below the main song table. beaTunes' new plugin API makes it simple to write additional, custom components for the same space. I'd like to show you with a simple example how it's done.

The idea of this plugin is to show the Wikipedia page corresponding to the selected song's artist. Because Java's JEditorPane has a rather poor HTML rendering performance and JWebPane is definitely not available on the Mac, we will use JDIC as browser component (this example will only run on OS X, the concept is the same though for Windows). Note, that the Mac version of JDIC is rather old and really only an alpha version. So expect unnecessary repaints and other visual artifacts. But this is not about the perfect embedded browser for Java, it's about custom SongContextComponents.

Subclassing SongContextComponent

Our Wikipedia component has to subclass com.tagtraum.beatunes.songtable.SongContextComponent. This superclass does most of the work for us. We only have to implement a couple of methods. Let's start with the simple stuff. Just like any other well behaved plugin, our component should have an id.

public String getId() {
return "songcontextcomponent.wikipedia";
}

Furthermore, it needs to specify a status label text, which for our purposes may always remain empty (future versions of the API won't require this anymore).

public String getStatusLabel() {
return null;
}

Setting up the UI

To set up the component itself, we have to declare a couple of fields and instantiate the UI component in the constructor. Also, as part of the interface, we have to implement the getComponent() method, which returns the JComponent that is going to be integrated into beaTunes' UI. Here's how it's done:

private static final Logger LOG = LogManager.getLogger(Wikipedia.class);

// the component we hand to beaTunes
private JPanel component;
// the actual browser component
private IWebBrowser webBrowser;
// we want to remember the last artist we've shown
// to avoid reloading the same page
private String lastArtist;
// page loading should be done in a separate thread, not the event thread
private ExecutorService executorService;
// loading will be done in a Future, which can be cancelled
private Future future;


public Wikipedia(final BeaTunes beaTunes) {
super(beaTunes);
// register the show hide action for this SongContextComponent
setShowHideActionId("wikipedia.show.hide");
this.executorService = Executors.newSingleThreadExecutor(
PriorityThreadFactory.getInstance(Thread.MIN_PRIORITY));
this.webBrowser = BrowserEngineManager.instance().getActiveEngine().getWebBrowser();
// webBrowser already has scrollbars, therefore a JPanel is good enough as component
this.component = new JPanel(new BorderLayout());
this.component.add((Component) webBrowser, BorderLayout.CENTER);
}

public JComponent getComponent() {
return component;
}

Reacting to song selections

Now that we have set up the webBrowser component, the only thing that's left to do, is to put some content into the browser window. To do so, we implement the update(Song) method. This method is called when the user selects a song in the main song table. To be precise, it is not called right away when the user selects a song, but with a little delay. This allows us to ignore the selection, should the user just scroll down a list of songs using the cursor keys.

public void update(final Song song) {
// sanity checks
if (song == null) return;
if (song.getArtist() == null || song.getArtist().length() == 0) return;
final String artist = song.getAlbumArtist() != null
&& song.getAlbumArtist().length() != 0
? song.getAlbumArtist() : song.getArtist();
// is this what we just showed?
if (artist.equals(lastArtist)) return;
lastArtist = artist;
showArtist(artist);
}

private void showArtist(final String artist) {
if (future != null) {
future.cancel(true);
}
future = executorService.submit(new Runnable() {
public void run() {
try {
// respect the user's language
final String language = getApplication().getLocale().getLanguage();
final URL url = new URL("http://" + language
+ ".wikipedia.org/wiki/Special:Search?search="
+ URLEncoder.encode(artist, "UTF-8") + "&go=Go");
webBrowser.setURL(url);
} catch (IOException e) {
LOG.error("Unable to open page for " + artist, e);
}
}
});
}

That's basically it.

Showing some Action

Well, almost... there are some things we still need to take care of. To start with, we of course have to provide a plugin.xml file to register the class. But more importantly, we have to provide some Action to show and hide our Wikipedia component. To do so, we subclass com.tagtraum.beatunes.action.standard.SongContextComponentShowHideAction. Here's the class:

public class ShowHideWikipediaAction extends SongContextComponentShowHideAction {

public ShowHideWikipediaAction(final BeaTunes beaTunes) {
super(beaTunes);
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_W,
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
| InputEvent.SHIFT_MASK));
}

public void init() {
// always call super.init() to make sure the action gets enabled/disabled properly
super.init();
putValue(Action.NAME, "Show Artist Info");
putValue(Action.SHORT_DESCRIPTION, "Shows artist info on Wikipedia");
putValue(Action.SMALL_ICON,
new ImageIcon(Wikipedia.class.getResource("wikipedia.png")));
putValue(BaseAction.SELECTED_ICON,
new ImageIcon(Wikipedia.class.getResource("wikipedia_selected.png")));
putValue(BaseAction.DISABLED_ICON,
new ImageIcon(Wikipedia.class.getResource("wikipedia_disabled.png")));
putValue(BaseAction.DISABLED_SELECTED_ICON,
new ImageIcon(Wikipedia.class.getResource("wikipedia_disabled.png")));
}

public String getId() {
// this is the id we reference in the Wikipedia class
return "wikipedia.show.hide";
}

public ActionLocation[] getActionLocations() {
return new ActionLocation[] {
// add to Edit menu
new RelativeActionLocation(BeaTunesUIRegion.EDIT_MENU,
RelativeActionLocation.RelativePosition.BEFORE, "tree.show.hide"),
// add as first (index 0) item to the lower right button panel
new AbsoluteActionLocation(BeaTunesUIRegion.LOWER_RIGHT_BUTTON_PANEL, 0)
};
}
}

And here is the very simple plugin.xml that needs to be placed in the META-INF directory of our jar.

<?xml version="1.0" encoding="UTF-8" ?>
<!-- Contains a list of plugins to be instantiated by beaTunes -->
<plugins>
<!-- SongContextComponent -->
<plugin class="com.beatunes.wikipedia.Wikipedia"/>
<!-- Actions -->
<plugin class="com.beatunes.wikipedia.ShowHideWikipediaAction"/>
</plugins>

Zip it!

Since the plugin uses third party libraries, we can't just put everything in one jar and place it in the plugin folder. Instead, we have to create a folder with a subfolder called lib. Into the lib folder we place the plugin's jar, jdic.jar and the native library libjdic.jnilib (both from JDIC). Then we zip the whole thing up.

In essence, the directory structure looks like this:

/lib/wikipedia-1.0.0.jar
/jdic.jar
/libjdic.jnilib

For beaTunes 2, the plugin classloader is configured to add all jars in the lib directory, and the classes subdirectory itself, should it exist. Note that plugins are not isolated from each other or from beaTunes, so library and/or class version conflicts are something to stay aware of.

You can download the full source for this example from here and the ready to go binary from here.

Labels: , , , ,

Wednesday, February 11, 2009

Deutsch!

beaTunes2 logo

So... all the little UI glitches in the last couple of releases hopefully paid off with this one, EA19. All strings are extracted and a German and English set are provided. That means, German users should now be able to enjoy beaTunes in their native tongue! Please let me know, if you find any messages or dialogs that I missed or that sound awkward.

Besides the German localization, this release features a new Compilation Inspector that finds albums that really shouldn't be marked as compilations, some UI fixes and some caching improvements.

And here's the obligatory warning: Before downloading and installing this, please make sure you understand what Early Access means:

  • Absolutely no warranty for whatever
  • Features may or may not work, appear, and disappear
  • It may not be possible to migrate data to future versions (even though I make a reasonable effort)
  • This version will cease to function 2 weeks after its release
  • You cannot buy this version

Just so that there is no doubt about it: EA19 isn't even a beta version (actually, we are getting really damn close to that, though).

And here are the download links:

Note for EA18 users

There should be no problems.

Labels: ,