=begin
= Synopsis

Read the tag from a file or create and Mp3Tag instance for saving tag to
mp3 file later:

   tag = Mp3Tag.new(filename)

Examining tags:

   tag.songname
   tag.artist
   tag.album
   tag.year
   tag.comment
   tag.tracknum
   tag.genre_id
   tag.genre

Setting tags:

   tag.songname = "My Song"
   tag.artist = "Me"
   tag.album = "My Album"
   tag.year = "2001"
   tag.comment = "No Comment"
   tag.tracknum = 3

   tag.genre_id = 23
   tag.genre = "Drum Solo"

genre_id's should exist in Mp3Tag::Genres. Elements in Mp3Tag::Genres
can be assigned using tag.genre= and the id will be looked up
automatically.

Saving tag to mp3:

   tag.commit

Checking if a file has a tag:

   Mp3Tag.hastag?(filename)

=end

# '

class Mp3Tag

  VERSION = '1.0'

  Genres = [ 'Blues', 'Classic Rock', 'Country', 'Dance',
    'Disco', 'Funk', 'Grunge', 'Hip-Hop', 'Jazz', 'Metal', 'New Age',
    'Oldies', 'Other', 'Pop', 'R&B', 'Rap', 'Reggae', 'Rock', 'Techno',
    'Industrial', 'Alternative', 'Ska', 'Death Metal', 'Pranks',
    'Soundtrack', 'Euro-Techno', 'Ambient', 'Trip-Hop', 'Vocal',
    'Jazz+Funk', 'Fusion', 'Trance', 'Classical', 'Instrumental',
    'Acid', 'House', 'Game', 'Sound Clip', 'Gospel', 'Noise',
    'Alternative Rock', 'Bass', 'Soul', 'Punk', 'Space', 'Meditative',
    'Instrumental Pop', 'Instrumental Rock', 'Ethnic', 'Gothic',
    'Darkwave', 'Techno-Industrial', 'Electronic', 'Pop-Folk',
    'Eurodance', 'Dream', 'Southern Rock', 'Comedy', 'Cult', 'Gangsta',
    'Top 40', 'Christian Rap', 'Pop/Funk', 'Jungle', 'Native US',
    'Cabaret', 'New Wave', 'Psychadelic', 'Rave', 'Showtunes',
    'Trailer', 'Lo-Fi', 'Tribal', 'Acid Punk', 'Acid Jazz', 'Polka',
    'Retro', 'Musical', 'Rock & Roll', 'Hard Rock', 'Folk', 'Folk-Rock',
    'National Folk', 'Swing', 'Fast Fusion', 'Bebob', 'Latin',
    'Revival', 'Celtic', 'Bluegrass', 'Avantgarde', 'Gothic Rock',
    'Progressive Rock', 'Psychedelic Rock', 'Symphonic Rock',
    'Slow Rock', 'Big Band', 'Chorus', 'Easy Listening', 'Acoustic', 'Humour',
    'Speech', 'Chanson', 'Opera', 'Chamber Music', 'Sonata', 'Symphony',
    'Booty Bass', 'Primus', 'Porn Groove', 'Satire', 'Slow Jam', 'Club',
    'Tango', 'Samba', 'Folklore', 'Ballad', 'Power Ballad', 'Rhytmic Soul',
    'Freestyle', 'Duet', 'Punk Rock', 'Drum Solo', 'Acapella',
    'Euro-House', 'Dance Hall', 'Goa', 'Drum & Bass', 'Club-House',
    'Hardcore', 'Terror', 'Indie', 'BritPop', 'Negerpunk', 'Polsk Punk',
    'Beat', 'Christian Gangsta Rap', 'Heavy Metal', 'Black Metal',
    'Crossover', 'Contemporary Christian', 'Christian Rock', 'Merengue',
    'Salsa', 'Trash Metal', 'Anime', 'Jpop', 'Synthpop' ]

  attr_accessor :genre_id, :songname, :artist, :album, :comment
  attr_reader :path, :year, :tracknum

  TagSize = 128
  TAGFORMAT_WRITE = 'Z30Z30Z30Z4Z28CCC'# write zero padded
  TAGFORMAT_READ = 'A30A30A30A4A30C'# accept zero/space padded input

=begin
= Class Methods
--- Mp3Tag.new(path)
      Creates a new Mp3Tag object for the file give by ((|path|)).
=end

  def initialize(path)
    @path = path

    @genre_id = 255
    @songname = ''
    @artist = ''
    @album = ''
    @comment = ''
    @tracknum = 0
    @year = 0

    readtag
  end

=begin
--- Mp3Tag.hastag?(filename)
      Tests if ((|filename|)) has a ID3V1.0 or ID3V1.1 tag. Returns a boolean values giving the result of the test.
=end

  def Mp3Tag.hastag?(file)
    hastag = nil
    File.open(file, 'r') { |f|
      f.seek(-TagSize, IO::SEEK_END)
      tag = f.read(3)
      hastag = (tag == 'TAG')
    }
    return hastag
  end

=begin
--- Mp3Tag.removetag(filename)
      Removes an ID3v1 tag from the MP3 file ((|filename|))
=end

  def Mp3Tag.removetag(filename)
    if Mp3Tag.hastag?(filename)
      newsize = File.size(filename) - TagSize
      File.open(filename, 'r+') {
	|f|
	f.truncate(newsize)
      }
    end
  end

=begin
= Instance Methods
--- songname
--- artist
--- album
--- comment
      Return the song name, artist, album, or comment from the tag as a String object. Will return empty strings if file did not have a tag.
--- tracknum
      Returns the track number from the tag. Will return 0 if the track number was not set in the tag when loaded, or if the file had no tag.
--- year
      Returns the year from the tag. Will return 0 if the file had no tag.
--- genre_id
      Return the id number of the genre from the tag. Will return 255 if the file had no tag.
--- genre
      Returns the genre name. Will return "Unknown" if the file had no tag or the genre id was not in Mp3Tag::Genres
--- path
      Returns the full path name of the MP3 file.
--- filename
      Returns the filename without the directory part of the MP3 File.
--- songname=(txt)
--- artist=(txt)
--- album=(txt)
--- comment=(txt)
      Sets the song name, atist, album, or comment for the tag to ((|txt|)). ((|txt|)) should be a String object.
--- tracknum=(num)
      Sets the track number for the tag. Only values in the range (0..255) are allowed.
--- year=(num)
      Sets the year for the tag. Should be a four digit number.
--- genre_id=(num)
      Sets the the genre id for the tag. Only values in the range (0..255) are allowed.
--- genre=(txt)
      Sets the genre id for the tag to the index of ((|txt|)) in Mp3Tag::Genres. The genre id will be set to 255 if ((|txt|)) is not found in the list.

=end

  def tracknum=(num)
    @tracknum = num.to_i
  end

  def year=(year)
    @year = year.to_i
  end

  def genre=(genre)
    @genre_id = Genres.index(genre) || 255
  end

  def genre_id=(id)
    @genre_id = id.to_i
  end
 
  def genre
    Genres[@genre_id] || 'Unknown'
  end

  def filename
    File.basename(@path)
  end

  def readtag
    return if File.size(@path) < TagSize
    File.open(@path, 'r') {
      |f|
      f.seek(-TagSize, IO::SEEK_END)
      tag = f.read(3)
      if (tag == 'TAG')
	rest = f.read(TagSize-3)
	# Read id3v1.0 tag
	@songname, @artist, @album, @year_s, @comment, @genre_id = rest.unpack TAGFORMAT_READ
	@year = @year_s.to_i
	# Handle id3v1.1 tracknums
	if @comment[28] == 0
	  @tracknum = @comment[29]
	  @comment.sub!(/\000+.$/, '')# remove nulls and tracknum from comment
	end
      end
    }
  end
  private :readtag

=begin
--- commit
      Saves the tag to the MP3 file. Will overwrite any existing tag.
=end

  def commit
    File.open(@path, 'r+') {
      |f|
      f.seek(-TagSize, IO::SEEK_END)
      tag = f.read(3)
      if tag != 'TAG'
	# Append new tag
	f.seek(0, IO::SEEK_END)
	f.write('TAG')
      end
      f.write([@songname,@artist,@album, ("%04d" % @year), @comment, 0, @tracknum, @genre_id].pack TAGFORMAT_WRITE)
    }
  end

end

