Class: AudioFile

Inherits:
Object
  • Object
show all
Defined in:
lib/id3/audiofile.rb

Overview

Class AudioFile may call this ID3File

reads and parses audio files for tags
writes audio files and attaches dumped tags to it..
revert feature would be nice to have..

If we query and AudioFile object, we query what's currently associated with it
e.g. we're not querying the file itself, but the Tag object which is perhaps modified.
To query the file itself, use the ID3 module functions

By default the audio portion of the file is not(!) read - to reduce memory footprint - the audioportion could be very long!

BUG: (1) : when a id3v2 frame is deleted from a tag, e.g. 'PICTURE', then the raw tag is not updated BUG: (2) : when a AudioFile is written to file, the raw tag is not updated to reflect the new raw tag value BUG: (3) : FIXED. when a AufioFile is written, the order of frames is not the same as in the original file.. fixed using OrderedHash BUG: (5) : when a FrameType is set for a ID3v2 tag, e.g. 'TITLE', the underlying attributes are not automatically pre-filled..

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filename) ⇒ AudioFile

this should take two parameters, either Filename or String, and an options hash, e.g. => false


35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/id3/audiofile.rb', line 35

def initialize( filename )
  @filename     = filename      # similar to path method from class File, which is a mis-nomer!
  @pwd          = ENV["PWD"]
  @dirname      = File.dirname( filename )
  @basename     = File.basename( filename )
  
  @tagID3v1     = nil
  @tagID3v2     = nil

  @audio        = nil           # this doesn't get initialized with the actual audio during new(), so we don't waste memory

  audioStartX   = 0
  audioEndX     = File.size(filename) - 1  # points to the last index

  if ID3.hasID3v1tag?(@filename)
    @tagID3v1 = ID3::Tag1.new
    @tagID3v1.read(@filename)

    audioEndX -= ID3::ID3v1tagSize
  end
  if ID3.hasID3v2tag?(@filename) 
    @tagID3v2 = ID3::Tag2.new
    @tagID3v2.read(@filename)

    audioStartX = @tagID3v2.raw.size
  end
  
  # audioStartX audioEndX indices into the file need to be set
  @audioStartX = audioStartX     # first byte of audio data
  @audioEndX   = audioEndX       # last byte of audio data
  
  # user may compute the MD5sum of the audio content later..
  # but we're only doing this if the user requests it..
  # because MD5sum computation takes a little bit time.

  @audioMD5sum = nil
  @audioSHA1sum = nil
end

Instance Attribute Details

#audioEndXObject (readonly)

begin and end indices of audio data in file


20
21
22
# File 'lib/id3/audiofile.rb', line 20

def audioEndX
  @audioEndX
end

#audioStartXObject (readonly)

begin and end indices of audio data in file


20
21
22
# File 'lib/id3/audiofile.rb', line 20

def audioStartX
  @audioStartX
end

#basenameObject (readonly)

absolute dirname and basename of the file (computed)


23
24
25
# File 'lib/id3/audiofile.rb', line 23

def basename
  @basename
end

#dirnameObject (readonly)

absolute dirname and basename of the file (computed)


23
24
25
# File 'lib/id3/audiofile.rb', line 23

def dirname
  @dirname
end

#filenameObject (readonly)

PWD and relative path/name how file was first referenced


22
23
24
# File 'lib/id3/audiofile.rb', line 22

def filename
  @filename
end

#pwdObject (readonly)

PWD and relative path/name how file was first referenced


22
23
24
# File 'lib/id3/audiofile.rb', line 22

def pwd
  @pwd
end

#tagID3v1Object

should make aliases id3v1_tag , id3v2_tag


25
26
27
# File 'lib/id3/audiofile.rb', line 25

def tagID3v1
  @tagID3v1
end

#tagID3v2Object

should make aliases id3v1_tag , id3v2_tag


25
26
27
# File 'lib/id3/audiofile.rb', line 25

def tagID3v2
  @tagID3v2
end

Instance Method Details

#audioObject


readAudion

read audio into @audio buffer either from String or from File

153
154
155
# File 'lib/id3/audiofile.rb', line 153

def audio
  @audio ||= readAudio    # read the audio portion of the file only once, the first time this is called.
end

#audioLengthObject



97
98
99
# File 'lib/id3/audiofile.rb', line 97

def audioLength
  @audioEndX - @audioStartX + 1
end

#audioMD5sumObject


audioMD5sum

if the user tries to access @audioMD5sum, it will be computed for him, 
unless it was previously computed. We try to calculate that only once 
and on demand, because it's a bit expensive to compute..

198
199
200
# File 'lib/id3/audiofile.rb', line 198

def audioMD5sum
  @audioMD5sum ||= MD5.hexdigest( audio )
end

#audioSHA1sumObject


202
203
204
# File 'lib/id3/audiofile.rb', line 202

def audioSHA1sum
  @audioSHA1sum ||= SHA1.hexdigest( audio )
end

#has_id3v1tag?Boolean



89
90
91
# File 'lib/id3/audiofile.rb', line 89

def has_id3v1tag?
  return @tagID3v1
end

#has_id3v2tag?Boolean



93
94
95
# File 'lib/id3/audiofile.rb', line 93

def has_id3v2tag?
  return @tagID3v2
end

#id3_versionsObject Also known as: versions, version


version aka versions

queries the tag objects and returns the version numbers of those tags
NOTE: this does not reflect what's currently in the file, but what's
      currently in the AudioFile object

80
81
82
83
84
85
# File 'lib/id3/audiofile.rb', line 80

def id3_versions       # returns Array of ID3 tag versions found
  a = Array.new
  a.push(@tagID3v1.version) if @tagID3v1
  a.push(@tagID3v2.version) if @tagID3v2
  return a
end

#readAudioObject


157
158
159
160
161
162
# File 'lib/id3/audiofile.rb', line 157

def readAudio
  File.open( File.join(@dirname, @basename) ) do |f|
    f.seek(@audioStartX)
    f.read(@audioEndX - @audioStartX + 1) 
  end
end

#verifyMD5sumObject


verifyMD5sum

compare the audioMD5sum against a previously stored md5sum file
and returns boolean value of comparison

If no md5sum file existed, we create one and return true.

computes the @audioMD5sum, if it wasn't previously computed..

238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/id3/audiofile.rb', line 238

def verifyMD5sum

  oldMD5sum = ''
  
  self.audioMD5sum if ! @audioMD5sum  # compute MD5sum if it's not computed yet

  base = @basename.sub( /(.)\.[^.]+$/ , '\1')   # remove suffix from audio-file
  base += '.md5'                                # add new suffix .md5
  md5name = File.join(@dirname,base)
  
  # if a MD5-file doesn't exist, we should create one and return TRUE ...
  if File.exists?(md5name)
    File.open( md5name ,"r") { |f| 
      oldname,oldMD5sum = f.readline.split  # read old MD5-sum
    }
  else
    oldMD5sum = self.writeMD5sum        # create MD5-file and return true..
  end
  @audioMD5sum == oldMD5sum
  
end

#write(*filename) ⇒ Object


write

write the AudioFile to file, including any ID3-tags
We keep backups if we write to a specific filename

105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/id3/audiofile.rb', line 105

def write(*filename)
  backups = false
  
  if filename.size == 0     # this is an Array!!
    filename = @filename
    backups  = true        # keep backups if we write to a specific filename
  else
    filename = filename[0]
    backups = false
  end
  
  tf = Tempfile.new( @basename )
  tmpname = tf.path
  
  # write ID3v2 tag:
  
  if @tagID3v2
    tf.write( @tagID3v2.dump )
  end
  
  # write Audio Data:
  
  tf.write( audio ) # reads audio from file if nil
  
  # write ID3v1 tag:
  
  if @tagID3v1
    tf.write( @tagID3v1.dump )
  end
  
  tf.close
  
  # now some logic about moving the tempfile and replacing the original

  bakname = filename + '.bak'
  move(filename, bakname) if backups && FileTest.exists?(filename) && ! FileTest.exists?(bakname)

  move(tmpname, filename)
  tf.close(true)
  
  # write md5sum sha1sum files:
  writeMD5sum if @audioMD5sum
  writeSHA1sum if @audioSHA1sum
end

#writeAudioOnlyObject


writeAudio

only for debugging, does not write any ID3-tags, but just the audio portion

167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/id3/audiofile.rb', line 167

def writeAudioOnly
  tf = Tempfile.new( @basename )
  
  File.open( @filename ) { |f|
    f.seek(@audioStartX)
    tf.write( audio )   # reads the audio from file if nil
  }
  tf.close
  path = tf.path
  
  tf.open
  tf.close(true)
end

#writeMD5sumObject


writeMD5sum

write the filename and MD5sum of the audio portion into an ascii file 
in the same location as the audio file, but with suffix .md5

computes the @audioMD5sum, if it wasn't previously computed..

212
213
214
215
216
217
218
219
# File 'lib/id3/audiofile.rb', line 212

def writeMD5sum
  base = @basename.sub( /(.)\.[^.]+$/ , '\1')
  base += '.md5'
  File.open( File.join(@dirname,base) ,"w") { |f| 
    f.printf("%s   %s\n",  File.join(@dirname,@basename), audioMD5sum ) # computes it if nil
  }
  @audioMD5sum
end

#writeSHA1sumObject


221
222
223
224
225
226
227
228
# File 'lib/id3/audiofile.rb', line 221

def writeSHA1sum
  base = @basename.sub( /(.)\.[^.]+$/ , '\1')
  base += '.sha1'
  File.open( File.join(@dirname,base) ,"w") { |f| 
    f.printf("%s   %s\n",  File.join(@dirname,@basename), audioSHA1sum ) # computes it if nil
  }
  @audioSHA1sum
end