If you would like to discuss anything, please contact me via email: Howard.Bayliss@sequence.co.uk

Wednesday, June 27, 2012

SharePoint Index Music

I'm storing music tracks in a SharePoint Foundation 2010 library. I want users to be able to search for tracks using music file metadata e.g. Track Name. Artist, Album, Lyrics, etc.

This metadata is stored within popular music file types (e.g. mp3, wma, ogg, etc); though the format of the metadata varies. So how can SharePoint index these files and extract the metadata? You have a number of options:

  1. Install FAST Search Server 2010 and the "Advanced Filter Pack", which can index certain music file types.
  2. Install a 3rd party IFilter, which knows how to index your music files. You'll probably need one IFilter for each specific media file type.
  3. Develop your own music file IFilter. This should be written in C++ and it's not an easy exercise - believe me; I've tried.
  4. Expose the music file metadata through the standard SharePoint user interface.

I went for option (4), but it does require some development knowledge, and the use of a 3rd party .Net library called "taglib-sharp". In essence you create a SharePoint Event Receiver that fires whenever a music file is added to a library. The Receiver then uses "taglib-sharp" to extract metadata from the file and copy it to custom SharePoint metadata.

The picture below shows the custom SharePoint metadata, extracted from an .mp3 file:

The SharePoint indexer can now index the custom metadata, which you can then use in custom searches.

Two great things about taglib-sharp:

  1. It can be called from .Net and is easy to use - you just point it directly at a file in the SharePoint library.
  2. It supports the following music file formats: wma, aiff, asf, mp3, m4a, mpeg, ogg, riff, aac, ape, flac, Musepack and WavPack.

Thursday, June 21, 2012

TagLib Stream Buffer

Do you want to load TagLib mp3 properties from a buffer or stream?

As I write this, the latest version in the TagLib Master branch on GitHub supports this.

Here's some C++ code that I've hacked together to do it - thanks to Lukáš Lalinský for his help.

Note that I'm not a C++ Developer ;)

I need to use C++ in this instance as I'm working on a project that cannot use the .Net framework - more in a later post.


// Load a sample track into a stream. std::ifstream is("Track1.mp3");

if (!is.bad())
{
// Calculate the size of the stream.
long l = is.tellg();
is.seekg (0, ios::end);
long m = is.tellg();
is.seekg (0, ios::beg);

long diff = (m - l);

// Save the stream to a buffer.
char* buffer = new char[diff];
is.read(buffer, diff);

ByteVector v(buffer, diff);
TagLib::IOStream* stream = new TagLib::ByteVectorStream(v);

TagLib::ID3v2::FrameFactory *frameFactory = TagLib::ID3v2::FrameFactory::instance();
TagLib::MPEG::File* mpegFile =new TagLib::MPEG::File (stream,frameFactory,true,TagLib::AudioProperties::Accurate);

TagLib::FileRef* f = new TagLib::FileRef(mpegFile);

cout << f->tag()->title() << endl;
cout << mpegFile->ID3v1Tag()->artist() << endl;

is.close();
}

Monday, June 18, 2012

SharePoint check user in group

I created some code to check if the current user belonged to a specific SharePoint group, similar to this:

SPUser user = SPContext.Current.Web.CurrentUser;

foreach (SPGroup group in user.Groups)
{
  if (group.Name == [INSERT YOUR GROUP NAME HERE)
  {
  }
}

This worked when users were directly added to the SharePoint group. However, it did not work if they were part of an AD group, which was then added to the SharePoint group.

To fix the issue, I used code similar to this:

SPGroupCollection groups = SPContext.Current.Site.RootWeb.Groups;
bool inGroup = groups[INSERT YOUR GROUP NAME HERE].ContainsCurrentUser;

Thursday, June 14, 2012

SharePoint "Crawled Properties" Filename

I have a document library that contains audio files (mp3, wma, etc). When I search the site, the results contain links to the library's DispForm.aspx. This is expected as I haven't installed an iFilter for the audio file types. However, I wanted to be able to get at the Name property (highlighted in the image below), which is the filename of the media file:



To do this, I had to:
  1. Include values from the crawled property "ows_FileLeafRef" in the index.
  2. Create a mapping to a managed property
These are shown below:



The platform was a single-server farm, using SharePoint Foundation 2010 and Search Server Express 2010.

Friday, June 8, 2012

WMPLib Artist Blank

I'm using SharePoint to host music tracks, which I play with Windows Media Player (WMP).

The player is controlled via WMPLib - Microsoft's WMP API.

I found that I can add SharePoint music files to a WMP playlist via the API, and that the tracks are played. However, when I wanted to display information about the current track (track name, artist, duration, etc), I found that the artist property was empty.

I discovered the fix for this was to URL encode the URL of the track (as it exists in SharePoint) before adding it to the playlist. This was because the track URLs contained spaces (and potentially other odd characters).

Wednesday, June 6, 2012

SharePoint mp3 Headers

Music tracks include header information such as artist, album, duration, etc

In SharePoint Foundation 2010, I wanted to extract this header data and use it to populate library metadata.

To do this, I first downloaded TagLib.

The clever bit about TagLib is that you can create your own SharePoint-specific implementation of the TagLib.File.IFileAbstraction interface. I then used this in a SharePoint event receiver to extract the header data when a user uploads a music file.

Note that this works for other types of music tracks, not just mp3.

The following code shows the code I created for a class that implements IFileAbstraction:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.SharePoint;

namespace Redweb.Redio.Audio
{
public class SharePointMediaFile : TagLib.File.IFileAbstraction
{
private string _fileUrl = String.Empty;

public string FileUrl
{
get
{
return _fileUrl;
}
set
{
_fileUrl = value;
}
}

void TagLib.File.IFileAbstraction.CloseStream(System.IO.Stream stream)
{
if (stream == null)
throw new ArgumentNullException("stream");
stream.Close();
}

string TagLib.File.IFileAbstraction.Name
{
get { return _fileUrl; }
}

System.IO.Stream TagLib.File.IFileAbstraction.ReadStream
{
get
{
MemoryStream outputSteam = null;
using (SPSite site = new SPSite(_fileUrl))
{
using (SPWeb web = site.OpenWeb())
{
if ((web == null) || (web.Exists == false))
{
throw new Exception("No web exists.");
}
SPFile file = web.GetFile(_fileUrl);
if ((file == null) || (file.Exists == false))
{
throw new Exception("No file exists.");
}
byte[] bytes = file.OpenBinary();
outputSteam = new MemoryStream(bytes);
}
}

return outputSteam;
}
}

System.IO.Stream TagLib.File.IFileAbstraction.WriteStream
{
get { throw new NotImplementedException(); }
}
}
}