Flac Support for Beagle



Hi !

As promised I added flac files support in beagle.
I don't know how to transmit it to the developers, but here are the two files: FilterFlac goes into the beagle/Filters dir, and FlacReader.cs goes into beagle/Utils directory.

The makefiles have to be updated also, just adding the two names..

Now I'm wondering if a common class for all music metadata can be created, like AudioTag.cs, so there is no duplication between all the audio formats's own way of representing the metadata ?
If yes, can I modify the Id3 reading accordingly ?

I'll also add mpc and ape support when I have some time...

Raf
//
// FilterFlac.cs
//
// Copyright (C) Raphaël Slinckx
//

//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//

using System;
using System.IO;

using BUF = Beagle.Util.FlacReader;

namespace Beagle.Filters {

	public class FilterFlac : Beagle.Daemon.Filter {
		
		private BUF.FlacTagReader reader = new BUF.FlacTagReader ();
		
		public FilterFlac ()
		{
			AddSupportedMimeType ("audio/x-flac");
		}

		protected override void DoPullProperties ()
		{
			BUF.Tag tag = null;
			try {
				tag = reader.read (Stream);
			} catch (Exception e) {
				Finished();
				return;
			}
			
			//FIXME: Do we need to check for non-null empty values ?
			AddProperty (Beagle.Property.New ("fixme:artist",  tag.Artist));
			AddProperty (Beagle.Property.New ("fixme:album",   tag.Album));
			AddProperty (Beagle.Property.New ("fixme:song",    tag.Title));
			AddProperty (Beagle.Property.New ("fixme:comment", tag.Comment));
			AddProperty (Beagle.Property.NewKeyword ("fixme:track", tag.Track));
			AddProperty (Beagle.Property.NewKeyword ("fixme:year", tag.Year));
			AddProperty (Beagle.Property.NewKeyword ("fixme:genre", tag.Genre));
			
			Finished ();
		}
	}
}
//
// FlacReader.cs
//
// Copyright (C) Raphaël Slinckx
//

//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//

using System;
using System.IO;
using System.Text;
using System.Collections;

namespace Beagle.Util.FlacReader {

	public class Tag {
		protected Hashtable fields;

		public Tag() {
			fields = new Hashtable();
			fields["TITLE"] = "";
			fields["ALBUM"] = "";
			fields["ARTIST"] = "";
			fields["GENRE"] = "";
			fields["TRACK"] = "";
			fields["YEAR"] = "";
			fields["COMMENT"] = "";
			fields["VENDOR"] = "";
		}
		
		public string Title {
			get {
				return (string) fields["TITLE"];
			}
			set {
				if(value == null)
					fields["TITLE"] = "";
				fields["TITLE"] = value;
			}
		}
		
		public string Album {
			get {
				return (string) fields["ALBUM"];
			}
			set {
				if(value == null)
					fields["ALBUM"] = "";
				fields["ALBUM"] = value;
			}
			
		}
		
		public string Artist {
			get {
				return (string) fields["ARTIST"];
			}
			set {
				if(value == null)
					fields["ARTIST"] = "";
				fields["ARTIST"] = value;
			}
			
		}
		
		public string Genre {
			get {
				return (string) fields["GENRE"];
			}
			set {
				if(value == null)
					fields["GENRE"] = "";
				fields["GENRE"] = value;
			}
		}
		
		public string Track {
			get {
				return (string) fields["TRACK"];
			}
			set {
				if(value == null)
					fields["TRACK"] = "";
				fields["TRACK"] = value;
			}
		}
		
		public string Year {
			get {
				return (string) fields["YEAR"];
			}
			set {
				if(value == null)
					fields["YEAR"] = "";
				fields["YEAR"] = value;
			}
		}
		
		public string Comment {
			get {
				return (string) fields["COMMENT"];
			}
			set {
				if(value == null)
					fields["COMMENT"] = "";
				fields["COMMENT"] = value;
			}
		}
		
		public string Vendor {
			get {
				return (string) fields["VENDOR"];
			}
			set {
				if(value == null)
					fields["VENDOR"] = "";
				fields["VENDOR"] = value;
			}
		}
	}

	public class MetadataBlockHeader {
		
		public const int STREAMINFO=0,PADDING=1,APPLICATION=2,SEEKTABLE=3,VORBIS_COMMENT=4,CUESHEET=5,UNKNOWN=6;
		private int blockType, dataLength;
		private bool lastBlock;
		private byte[] data;
		private byte[] bytes;
		
		public MetadataBlockHeader( byte[] b ) {
			this.bytes = b;
			
			lastBlock = ( (b[0] & 0x80)>>7 )==1;
			
			int type = b[0] & 0x7F;
			switch(type) {
				case 0: blockType = STREAMINFO; break;
				case 1: blockType = PADDING; break;
				case 2: blockType = APPLICATION; break;
				case 3: blockType = SEEKTABLE; break;
				case 4: blockType = VORBIS_COMMENT; break;
				case 5: blockType = CUESHEET; break;
				default: blockType = UNKNOWN; break;
			}
			
			dataLength = (u(b[1])<<16) + (u(b[2])<<8) + (u(b[3]));
			
			data = new byte[4];
			data[0] = (byte) (data[0] & 0x7F);
			for(int i = 1; i< 4; i++) {
				data[i] = b[i];
			}
		}
	
		public int DataLength {
			get {
				return dataLength;
			}
		}
		
		public int BlockType {
			get {
				return blockType;
			}
		}
		
		public string BlockTypeString {
			get {
				switch(blockType) {
					case 0: return "STREAMINFO";
					case 1: return "PADDING";
					case 2: return "APPLICATION";
					case 3: return "SEEKTABLE";
					case 4: return "VORBIS_COMMENT";
					case 5: return "CUESHEET";
					default: return "UNKNOWN-RESERVED";
				}
			}
		}
		
		public bool IsLastBlock {
			get {
				return lastBlock;
			}
		}
		
		public byte[] Data {
			get {
				return data;
			}
		}
		
		private int u(int i) {
			return i & 0xFF;
		}
	}


	public class FlacTagReader {
		
		private ASCIIEncoding ascii = new ASCIIEncoding();
		private UTF8Encoding utf = new UTF8Encoding();
		
		public Tag read( Stream fs ) {
			//Begins tag parsing-------------------------------------
			if ( fs.Length < 4 ) {
				//Empty File
				throw new Exception("Error: File empty");
			}
			fs.Seek( 0, SeekOrigin.Begin );

			//FLAC Header string
			byte[] b = new byte[4];
			fs.Read(b, 0, 4);
			string flac = ascii.GetString(b, 0, 4);
			if(flac != "fLaC")
				throw new Exception("fLaC Header not found, not a flac file");
			
			Tag tag = null;
			
			//Seems like we hava a valid stream
			bool isLastBlock = false;
			while(!isLastBlock) {
				b = new byte[4];
				fs.Read(b, 0, 4);
				MetadataBlockHeader mbh = new MetadataBlockHeader(b);
			
				switch(mbh.BlockType) {
					//We got a vorbis comment block, parse it
					case MetadataBlockHeader.VORBIS_COMMENT : 	return handleVorbisComment(mbh, fs);
																//We have it, so no need to go further
					
					//This is not a vorbis comment block, we skip to next block
					default : 	fs.Seek(mbh.DataLength, SeekOrigin.Current); break;
				}

				isLastBlock = mbh.IsLastBlock;
				mbh = null;
			}
			//FLAC not found...
			throw new Exception("FLAC Tag could not be found or read..");
		}
		
		private Tag handleVorbisComment(MetadataBlockHeader mbh, Stream fs) {
			Tag tag = new Tag();
			byte[] b = new byte[mbh.DataLength];
			fs.Read(b, 0, b.Length);
			
			int pos = 0;
			int vendorstringLength = getNumber(b, 0,3);
			pos += 4;
			
			string vendorstring = utf.GetString(b, 4, vendorstringLength);
			pos += vendorstringLength;
		
			int userComments = getNumber(b, pos,pos+3);
			pos += 4;
			
			Hashtable ht = new Hashtable(10);
		
			for ( int i = 0; i < userComments; i++ ) {
				int commentLength = getNumber(b, pos,pos+3);
				pos += 4;
				
				string comment = utf.GetString( b, pos, commentLength );
				pos += commentLength;
				
				string[] splitComment = comment.Split( new char[] {'='} );
				if(splitComment.Length>1)
					ht[splitComment[0]] = splitComment[1];
			}
			
			foreach (DictionaryEntry en in ht) {
				string key = ((string) en.Key).ToUpper();
				string val = (string) en.Value;
				
				if( val.Trim() != "" ) {
					if(key == "TITLE")
						tag.Title = val;
					else if (key == "ARTIST")
						tag.Artist = val;
					else if (key == "ALBUM")
						tag.Album = val;
					else if (key == "DATE")
						tag.Year = val;
					else if (key == "COMMENT" || key == "DESCRIPTION")
						tag.Comment = val;
					else if (key == "TRACK" || key == "TRACKNUMBER")
						tag.Track = val;
					else if (key == "GENRE")
						tag.Genre = val;
					else {
						Console.Error.Write("FlacTagReader: Warning: Unknown field: "+key+" | "+val);
					}
				}
			}
				
			tag.Vendor = vendorstring;
				
			return tag;
		}
		
		//Computes a (end-start) bytes long number 
		private int getNumber( byte[] b, int start, int end) {
			int number = 0;
			for(int i = 0; i<(end-start+1); i++) {
				number += ((b[start+i]&0xFF) << i*8);
			}
			
			return number;
		}
	}
}

Attachment: signature.asc
Description: OpenPGP digital signature



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]