| Comments

I’m working on a little sample application for music management in Silverlight using WCF RIA Services and some other new Silverlight 4 features.  One thing that I wanted to add to the application was the ability to drag an audio file and either lookup the data and/or add a new album/artist/song to the library automatically.

Audio formats have a ‘tag’ format known as ID3.  It’s a format used for audio file metadata that is used in Windows Media Player, iTunes, and various hardware devices as well.  Over the years there has been an evolution of this format, with the older ID3v1 format basically taking up a header space with fixed character spaces for various things like Album, Artist, Title, Year, Track.  Over time though the ID3v2 format has been adapted more as it is more flexible for things like album art, and longer titles, etc.  There are various implementations of ID3 libraries for .NET that developers can choose from.  All of these implementations don’t take into account Silverlight unfortunately.

Silverlight can only reference Silverlight-compatible libraries.  Most of these libraries were targeted for the full .NET Framework and thus I can’t binary reference them.  Luckily most of them (except one) are Open Source so I could tinker.  I took the step of simply copying the files to a Silverlight project and recompiling.  This did not work 100% in a single task.  Most of the libraries had some form of Serialization attributes/constructors and almost all used some form of ASCII encoding for various string manipulation of byte arrays.

I settled on TagLib# as the library that was the easiest to modify for me.  I had to make the same changes I mentioned above to this library as well.  I created a new Silverlight 4 class library and compiled it as such.  One thing that TagLib# didn’t have was a stream input implementation.  Most of the libraries, in fact, assumed a local file path.  Luckily the library was written using a generic ‘File’ interface, so I just had to create my own StreamFileAbstraction.  I chose to do this within my project rather than the base library.  It was easy since the LocalFileAbstraction actually perfomed an Open on the file as it’s first task and set some public variables.  My abstraction basically just hands the stream already and ready to go.

Now, using the Silverlight 4 drop target feature, I created just a simple test harness to test my theory.  My XAML basically is this (pretty rudimentary just to test my theory):

   1: <Grid x:Name="LayoutRoot" Background="White">
   2:         <StackPanel>
   3:             <Border x:Name="DropZone" Width="700" Height="300" Background="Silver" CornerRadius="8" AllowDrop="True" Drop="DropZone_Drop">
   4:                 <TextBlock TextWrapping="Wrap" Text="drop here" FontSize="64" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="Gray"/>
   5:             </Border>
   6:             <Grid Height="255" Width="700">
   7:                 <Grid.ColumnDefinitions>
   8:                     <ColumnDefinition Width="111"/>
   9:                     <ColumnDefinition Width="*"/>
  10:                 </Grid.ColumnDefinitions>
  11:                 <Grid.RowDefinitions>
  12:                     <RowDefinition Height="Auto"/>
  13:                     <RowDefinition Height="Auto"/>
  14:                     <RowDefinition Height="Auto"/>
  15:                     <RowDefinition Height="Auto" />
  16:                     <RowDefinition Height="50*" />
  17:                 </Grid.RowDefinitions>
  18:                 <dataInput:Label Content="Artist" HorizontalAlignment="Right" VerticalAlignment="Top" FontWeight="Bold" Margin="4" />
  19:                 <dataInput:Label Content="Album" Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Top" FontWeight="Bold" Margin="4" />
  20:                 <dataInput:Label Content="Title" Grid.Row="2" HorizontalAlignment="Right" VerticalAlignment="Top" FontWeight="Bold" Margin="4" />
  21:                 <dataInput:Label Grid.Column="1" HorizontalAlignment="Left" Name="Artist" Margin="4" />
  22:                 <dataInput:Label Grid.Column="1" Grid.Row="1" HorizontalAlignment="Left" Name="Album" Margin="4" />
  23:                 <dataInput:Label Grid.Column="1" Grid.Row="2" HorizontalAlignment="Left" VerticalAlignment="Top" Name="Title" Margin="4" />
  24:                 <Image Grid.Column="1" Grid.Row="4" Height="118" HorizontalAlignment="Left" Margin="4,2,0,0" Name="AlbumArt" Stretch="Fill" VerticalAlignment="Top" Width="118" />
  25:             </Grid>
  26:         </StackPanel>
  27:     </Grid>

Notice on the Border the AllowDrop=”True” attribute.  This tells Silverlight that the element can be used as a drop target (for a file from the file system).  The rendered UI looks like this:

Sample MP3 test UI

You may also notice the Drop attribute on the Border element that maps to the event handler DropZone_Drop.  This event handler basically gives us an event argument that represents the dropped objects on the surface (yes you can drop more than one).  The initial stub of this function looks like this:

   1: private void DropZone_Drop(object sender, DragEventArgs e)
   2: {
   3:     IDataObject drop = e.Data as IDataObject;
   5:     object data = drop.GetData(DataFormats.FileDrop);
   7:     FileInfo[] files = data as FileInfo[];
   9:     FileInfo file = files[0];
  10: }

I’m being a little verbose in the code to show each of the steps.  As you can see you get a FileInfo array and can pull items out of that.  For my sample I’m just assuming one item was dropped.  In the next steps I just need to get the Stream from the file and use my library.  Here is the full function (with a quick check to make sure it is a supported audio file):

   1: private void DropZone_Drop(object sender, DragEventArgs e)
   2: {
   3:     IDataObject drop = e.Data as IDataObject;
   5:     object data = drop.GetData(DataFormats.FileDrop);
   7:     FileInfo[] files = data as FileInfo[];
   9:     FileInfo file = files[0];
  11:     if (file.Extension.ToLower() != ".mp3" && file.Extension.ToLower() != ".wma")
  12:     {
  13:         MessageBox.Show("Must be an MP3 file");
  14:     }
  15:     else
  16:     {
  17:         Stream fileStream = file.OpenRead();
  19:         TagLib.File.IFileAbstraction fileAbstraction = new StreamFileAbstraction(fileStream, file.Name);
  21:         TagLib.File tagFile = TagLib.File.Create(fileAbstraction);
  23:         if (tagFile.Tag.TagTypes.HasFlag(TagLib.TagTypes.Id3v2))
  24:         {
  25:             Artist.Content = tagFile.Tag.FirstAlbumArtist;
  26:             Album.Content = string.IsNullOrEmpty(tagFile.Tag.Album) ? "NO ALBUM NAME" : tagFile.Tag.Album;
  27:             Title.Content = tagFile.Tag.Title;
  28:             if (tagFile.Tag.Pictures.Length > 0)
  29:             {
  30:                 IPicture pic = tagFile.Tag.Pictures[0];
  31:                 MemoryStream img = new MemoryStream(pic.Data.Data);
  32:                 BitmapImage bmp = new BitmapImage();
  33:                 bmp.SetSource(img);
  35:                 AlbumArt.Source = bmp;
  36:             }
  37:         }
  38:         else
  39:         {
  40:             MessageBox.Show("no id3v2 tag");
  41:         }
  42:     }

Once all the pieces are together you drag an audio file on the drop surface and the items will populate.  Here’s a quick video showing how it all works together.

Get Microsoft Silverlight

So this is just a start – and I’ve got only the tag reading working…didn’t bother looking at the other parts of the library so I know it isn’t fully ported for Silverlight.

What do you think?  Found a better implementation of ID3 tag reading?

Please enjoy some of these other recent posts...