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