flacinfo-rb 0.2 → 0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. data/flacinfo.rb +309 -65
  2. metadata +2 -2
data/flacinfo.rb CHANGED
@@ -3,22 +3,21 @@
3
3
  # flacinfo-rb gives you access to low level information on Flac files.
4
4
  # * It parses stream information (METADATA_BLOCK_STREAMINFO).
5
5
  # * It parses Vorbis comments (METADATA_BLOCK_VORBIS_COMMENT).
6
+ # * It allows you to add/delete/edit Vorbis comments and write them to the Flac file.
6
7
  # * It parses the seek table (METADATA_BLOCK_SEEKTABLE).
7
8
  # * It parses the 'application metadata block' (METADATA_BLOCK_APPLICATION).
8
- # * If application is ID 0x41544348 (Flac File)
9
- # then we can parse that too.
9
+ # * If application is ID 0x41544348 (Flac File) then we can parse that too.
10
10
  # * It recognizes (but does not yet parse) the cue sheet (METADATA_BLOCK_CUESHEET).
11
11
  # * It parses zero or more picture blocks (METADATA_BLOCK_PICTURE)
12
- # * It allows you to write embedded images to a file.
12
+ # * It allows you to write the embedded images to a file.
13
13
  #
14
- # My plans are to eventually have this library write as well as read data to/from Flac files,
15
- # so in the future it should become quite a nice native Ruby library interface which will allow
14
+ # My goals are to create a nice native Ruby library interface which will allow
16
15
  # the user to mimic most functionality of the 'metaflac' binary programmatically.
17
16
  #
18
17
  # = Copyright and Disclaimer
19
18
  #
20
- # Copyright:: (c) 2006 Darren Kirby
21
- # FlacInfo is free software.
19
+ # Copyright:: (c) 2006, 2007 Darren Kirby
20
+ # FlacInfo is free software.
22
21
  # No warranty is provided and the author cannot accept responsibility
23
22
  # for lost or damaged files.
24
23
  # License:: Ruby
@@ -35,14 +34,33 @@
35
34
  # http://www.xiph.org/vorbis/doc/v-comment.html
36
35
 
37
36
 
38
- # FlacInfoError is raised when an error occurs parsing the Flac file.
39
- # It will print an additional error string stating where the error occured.
37
+ # FlacInfoError is raised for general user errors.
38
+ # It will print a string that describes the problem.
40
39
  class FlacInfoError < StandardError
41
40
  end
42
41
 
42
+ # FlacInfoReadError is raised when an error occurs parsing the Flac file.
43
+ # It will print a string that describes in which block the error occured.
44
+ class FlacInfoReadError < StandardError
45
+ end
46
+
47
+ # FlacInfoWriteError is raised when an error occurs writing the Flac file.
48
+ # It will print a string that describes where the error occured.
49
+ class FlacInfoWriteError < StandardError
50
+ end
51
+
43
52
  # Note: STREAMINFO is the only block guaranteed to be present in the Flac file.
44
- # All attributes will be present, but empty if the associated block is not present in the Flac file.
53
+ # All attributes will be present but empty if the associated block is not present in the Flac file,
54
+ # except for 'picture' which will have the key 'n' with the value '0'.
55
+ # All 'offset' and 'block_size' values do not include the block header. All block headers are 4 bytes
56
+ # no matter the type, so if you need the offset including the header, subtract 4. If you need the size
57
+ # including the header, add 4.
45
58
  class FlacInfo
59
+ # A list of 'standard field names' according to the Vorbis Comment specification. It is certainly
60
+ # possible to use a non-standard name, but the spec recommends against it.
61
+ # See: http://www.xiph.org/vorbis/doc/v-comment.html
62
+ STANDARD_FIELD_NAMES= %w/TITLE VERSION ALBUM TRACKNUMBER ARTIST PERFORMER COPYRIGHT LICENSE
63
+ ORGANIZATION DESCRIPTION GENRE DATE LOCATION CONTACT ISRC/
46
64
 
47
65
  # Hash of values extracted from the STREAMINFO block. Keys are:
48
66
  # 'offset':: The STREAMINFO block's offset from the beginning of the file (not including the block header).
@@ -70,7 +88,8 @@ class FlacInfo
70
88
  attr_reader :seektable
71
89
 
72
90
  # Array of "name=value" strings extracted from the VORBIS_COMMENT block. This is just the contents, metadata is in 'tags'.
73
- attr_reader :comment
91
+ # You should not normally operate on this array directly. Rather, use the comment_add and comment_del methods to make changes.
92
+ attr_accessor :comment
74
93
 
75
94
  # Hash of the 'comment' values separated into "key => value" pairs as well as the keys:
76
95
  # 'offset':: The VORBIS_COMMENT block's offset from the beginning of the file (not including the block header).
@@ -138,12 +157,17 @@ class FlacInfo
138
157
  @tags["#{tag}"] ? true : false
139
158
  end
140
159
 
141
- # Pretty print comment hash.
160
+ # Pretty print tags hash.
142
161
  #
143
162
  # :call-seq:
144
163
  # FlacInfo.print_tags -> nil
145
164
  #
165
+ # Raises FlacInfoError if METADATA_BLOCK_VORBIS_COMMENT is not present.
166
+ #
146
167
  def print_tags
168
+ if @tags == {}
169
+ raise FlacInfoError, "METADATA_BLOCK_VORBIS_COMMENT not present"
170
+ end
147
171
  @tags.each_pair { |key,val| puts "#{key}: #{val}" }
148
172
  nil
149
173
  end
@@ -154,6 +178,7 @@ class FlacInfo
154
178
  # FlacInfo.print_streaminfo -> nil
155
179
  #
156
180
  def print_streaminfo
181
+ # No test: METADATA_BLOCK_STREAMINFO must be present in valid Flac file
157
182
  @streaminfo.each_pair { |key,val| puts "#{key}: #{val}" }
158
183
  nil
159
184
  end
@@ -163,7 +188,12 @@ class FlacInfo
163
188
  # :call-seq:
164
189
  # FlacInfo.print_seektable -> nil
165
190
  #
191
+ # Raises FlacInfoError if METADATA_BLOCK_SEEKTABLE is not present.
192
+ #
166
193
  def print_seektable
194
+ if @seektable == {}
195
+ raise FlacInfoError, "METADATA_BLOCK_SEEKTABLE not present"
196
+ end
167
197
  puts " seek points: #{@seektable['seek_points']}"
168
198
  n = 0
169
199
  @seektable['seek_points'].times do
@@ -178,7 +208,7 @@ class FlacInfo
178
208
  # This method produces output similar to 'metaflac --list'.
179
209
  #
180
210
  # :call-seq:
181
- # FlacInfo.meta_flac -> nil
211
+ # FlacInfo.meta_flac -> nil
182
212
  #
183
213
  def meta_flac
184
214
  n = 0
@@ -216,7 +246,8 @@ class FlacInfo
216
246
  # FlacInfo.raw_data_dump(outfile) -> nil
217
247
  #
218
248
  # If passed with 'outfile', the data will be written to a file with that name
219
- # otherwise it is written to the console (even if binary!).
249
+ # otherwise it is written to the console (even if binary!). Raises FlacInfoError
250
+ # if there is no Flac File data present.
220
251
  #
221
252
  def raw_data_dump(outfile = nil)
222
253
  if @flac_file == {}
@@ -240,10 +271,10 @@ class FlacInfo
240
271
  # Writes embedded images to a file
241
272
  #
242
273
  # :call-seq:
243
- # FlacInfo.write_picture() -> nil
244
- # FlacInfo.write_picture(:outfile=>"str") -> nil
245
- # FlacInfo.write_picture(:n=>int) -> nil
246
- # FlacInfo.write_picture(:outfile=>"str", :n=>int) -> nil
274
+ # FlacInfo.write_picture() -> nil
275
+ # FlacInfo.write_picture(:outfile=>"str") -> nil
276
+ # FlacInfo.write_picture(:n=>int) -> nil
277
+ # FlacInfo.write_picture(:outfile=>"str", :n=>int) -> nil
247
278
  #
248
279
  # If passed with ':outfile', the image will be written to a file with that name
249
280
  # otherwise it is written to the value of the 'album' tag if it exists, otherwise it
@@ -261,13 +292,14 @@ class FlacInfo
261
292
  n = 1
262
293
  end
263
294
 
264
- # "image/jpeg" => "jpeg"
295
+ # "image/jpeg" => "jpeg"
265
296
  extension = @picture[n]["mime_type"].split("/")[1]
266
297
 
267
298
  if not args.has_key?(:outfile)
268
299
  if @tags["album"] == nil or @tags["album"] == ""
269
300
  outfile = "flacimage#{n}.#{extension}"
270
301
  else
302
+ # Try to use contents of "album" tag for the filename
271
303
  outfile = "#{@tags["album"]}#{n}.#{extension}"
272
304
  end
273
305
  else
@@ -277,7 +309,7 @@ class FlacInfo
277
309
  in_p = File.new(@filename, "rb")
278
310
  out_p = File.new(outfile, "wb")
279
311
 
280
- out_p.binmode
312
+ out_p.binmode # For Windows folks...
281
313
 
282
314
  in_p.seek(@picture[n]['raw_data_offset'], IO::SEEK_CUR)
283
315
  raw_data = in_p.read(@picture[n]['raw_data_length'])
@@ -289,7 +321,82 @@ class FlacInfo
289
321
  nil
290
322
  end
291
323
 
324
+ # Writes changes to disk
325
+ #
326
+ # :call-seq:
327
+ # FlacInfo.update -> bool
328
+ #
329
+ # Returns true if write was successful, false otherwise.
330
+ #
331
+ def update
332
+ if write_to_disk
333
+ return true
334
+ else
335
+ return false
336
+ end
337
+ end
338
+
339
+ # Adds a new comment to the comment array
340
+ #
341
+ # :call-seq:
342
+ # FlacInfo.comment_add(str) -> bool
343
+ #
344
+ # 'str' must be in the form 'name=value', or 'name=' if you want to
345
+ # set an empty value for a particular tag.
346
+ # Returns 'true' if successful, false otherwise.
347
+ #
348
+ def comment_add(name)
349
+ if name !~ /\w=/ # We accept 'name=' in case you want to leave the value empty
350
+ raise FlacInfoError, "comments must be in the form 'name=value'"
351
+ end
352
+ begin
353
+ @comment[@comment.length] = name
354
+ @comments_changed = 1
355
+ rescue
356
+ return false
357
+ end
358
+ return true
359
+ end
360
+
361
+ # Deletes a comment from the comment array
362
+ #
363
+ # :call-seq:
364
+ # FlacInfo.comment_del(str) -> bool
365
+ #
366
+ # If 'str' is in the form 'name=value' only exact matches
367
+ # will be deleted. If 'str' is in the form 'name' any and all
368
+ # comments named 'name' will be deleted. Returns 'true' if a
369
+ # comment was deleted, false otherwise.
370
+ #
371
+ def comment_del(name)
372
+ bc = Array.new(@comment) # We need a copy
373
+ if name.include? "="
374
+ nc = @comment.delete_if { |x| x == name }
375
+ else
376
+ nc = @comment.delete_if { |x| x.split("=")[0] == name }
377
+ end
378
+
379
+ if nc == bc
380
+ return false
381
+ else
382
+ @comments_changed = 1
383
+ return true
384
+ end
385
+ end
386
+
387
+ #--
388
+ # This cleans up the output when using FlacInfo in irb
389
+ def inspect #:nodoc:
390
+ s = "#<#{self.class}:0x#{(self.object_id*2).to_s(16)} "
391
+ @metadata_blocks.each do |blk|
392
+ s += "(#{blk[0].upcase} size=#{blk[4]} offset=#{blk[3]}) "
393
+ end
394
+ s += "\b>"
395
+ end
396
+ #++
397
+
292
398
  private
399
+
293
400
  # The following six methods are just helpers for meta_flac
294
401
  def meta_stream
295
402
  puts " length: #{@streaminfo['block_size']}"
@@ -361,16 +468,20 @@ class FlacInfo
361
468
  end
362
469
 
363
470
 
364
- # This is where the 'real' parsing starts.
471
+ # This is where the 'real' parsing starts
365
472
  def parse_flac_meta_blocks
366
- @fp = File.new(@filename, "rb")
367
- @streaminfo = {}
368
- @comment = {}
369
- @seektable = {}
370
- @padding = {}
473
+ @fp = File.new(@filename, "rb") # Our file pointer
474
+ @comments_changed = nil # Do we need to write a new VORBIS_BLOCK?
475
+
476
+ # These next 8 lines initialize our public data structures.
477
+ @streaminfo = {}
478
+ @comment = []
479
+ @tags = {}
480
+ @seektable = {}
481
+ @padding = {}
371
482
  @application = {}
372
- @cuesheet = {}
373
- @picture = {"n" => 0}
483
+ @cuesheet = {}
484
+ @picture = {"n" => 0}
374
485
 
375
486
  header = @fp.read(4)
376
487
  # First 4 bytes must be 0x66, 0x4C, 0x61, and 0x43
@@ -384,22 +495,22 @@ class FlacInfo
384
495
 
385
496
  @metadata_blocks = []
386
497
  lastheader = 0
387
- n = 0
498
+ pos = 1
388
499
 
389
500
  until lastheader == 1
390
- # first bit = Last-metadata-block flag
391
- # bits 2-7 = BLOCK_TYPE. See typetable above
501
+ # first bit = Last-metadata-block flag
502
+ # bits 2-8 = BLOCK_TYPE. See typetable above
392
503
  block_header = @fp.read(1).unpack("B*")[0]
393
504
  lastheader = block_header[0].to_i & 1
394
505
  type = sprintf("%u", "0b#{block_header[1..7]}").to_i
395
- @metadata_blocks[n] = ["#{typetable[type]}", type, lastheader]
506
+ @metadata_blocks << [typetable[type], type, lastheader]
396
507
 
397
508
  if type >= typetable.size
398
509
  raise FlacInfoError, "Invalid block header type"
399
510
  end
400
511
 
512
+ pos += 1
401
513
  self.send "parse_#{typetable[type]}"
402
- n += 1
403
514
  end
404
515
 
405
516
  @fp.close
@@ -407,9 +518,13 @@ class FlacInfo
407
518
 
408
519
  def parse_seektable
409
520
  begin
410
- @seektable['block_size'] = @fp.read(3).reverse.unpack("v*")[0]
411
- @seektable['offset'] = @fp.tell
521
+ @seektable['block_size'] = @fp.read(3).unpack("B*")[0].to_i(2)
522
+ @seektable['offset'] = @fp.tell
412
523
  @seektable['seek_points'] = @seektable['block_size'] / 18
524
+
525
+ @metadata_blocks[-1] << @seektable['offset']
526
+ @metadata_blocks[-1] << @seektable['block_size']
527
+
413
528
  n = 0
414
529
  @seektable['points'] = {}
415
530
 
@@ -430,8 +545,12 @@ class FlacInfo
430
545
  # Not parsed yet, I have no flacs with a cuesheet!
431
546
  def parse_cuesheet
432
547
  begin
433
- @cuesheet['block_size'] = @fp.read(3).reverse.unpack("v*")[0]
434
- @cuesheet['offset'] = @fp.tell
548
+ @cuesheet['block_size'] = @fp.read(3).unpack("B*")[0].to_i(2)
549
+ @cuesheet['offset'] = @fp.tell
550
+
551
+ @metadata_blocks[-1] << @cuesheet['offset']
552
+ @metadata_blocks[-1] << @cuesheet['block_size']
553
+
435
554
  @fp.seek(@cuesheet['block_size'], IO::SEEK_CUR)
436
555
  rescue
437
556
  raise FlacInfoError, "Could not parse METADATA_BLOCK_CUESHEET"
@@ -441,7 +560,7 @@ class FlacInfo
441
560
  def parse_picture
442
561
  n = @picture["n"] + 1
443
562
  @picture["n"] = n
444
- @picture[n] = {}
563
+ @picture[n] = {}
445
564
 
446
565
  picture_type = ["Other", "32x32 pixels file icon", "Other file icon", "Cover (front)", "Cover (back)",
447
566
  "Leaflet page", "Media", "Lead artist/lead performer/soloist", "Artist/performer",
@@ -450,20 +569,26 @@ class FlacInfo
450
569
  coloured fish", "Illustration", "Band/artist logotype", "Publisher/Studio logotype"]
451
570
 
452
571
  begin
453
- @picture[n]['block_size'] = @fp.read(3).reverse.unpack("v*")[0]
454
- @picture[n]['offset'] = @fp.tell
455
- @picture[n]['type_int'] = @fp.read(4).reverse.unpack("v*")[0]
456
- @picture[n]['type_string'] = picture_type[@picture[n]['type_int']]
457
- mime_length = @fp.read(4).reverse.unpack("v*")[0]
458
- @picture[n]['mime_type'] = @fp.read(mime_length).unpack("a*")[0]
459
- description_length = @fp.read(4).reverse.unpack("v*")[0]
572
+ @picture[n]['block_size'] = @fp.read(3).unpack("B*")[0].to_i(2)
573
+ @picture[n]['offset'] = @fp.tell
574
+
575
+ @metadata_blocks[-1] << @picture[n]['offset']
576
+
577
+ @picture[n]['type_int'] = @fp.read(4).reverse.unpack("v*")[0]
578
+ @picture[n]['type_string'] = picture_type[@picture[n]['type_int']]
579
+ mime_length = @fp.read(4).reverse.unpack("v*")[0]
580
+ @picture[n]['mime_type'] = @fp.read(mime_length).unpack("a*")[0]
581
+ description_length = @fp.read(4).reverse.unpack("v*")[0]
460
582
  @picture[n]['description_string'] = @fp.read(description_length).unpack("M*")[0]
461
- @picture[n]['width'] = @fp.read(4).reverse.unpack("v*")[0]
462
- @picture[n]['height'] = @fp.read(4).reverse.unpack("v*")[0]
463
- @picture[n]['colour_depth'] = @fp.read(4).reverse.unpack("v*")[0]
464
- @picture[n]['n_colours'] = @fp.read(4).reverse.unpack("v*")[0]
465
- @picture[n]['raw_data_length'] = @fp.read(4).reverse.unpack("V*")[0]
466
- @picture[n]['raw_data_offset'] = @fp.tell
583
+ @picture[n]['width'] = @fp.read(4).reverse.unpack("v*")[0]
584
+ @picture[n]['height'] = @fp.read(4).reverse.unpack("v*")[0]
585
+ @picture[n]['colour_depth'] = @fp.read(4).reverse.unpack("v*")[0]
586
+ @picture[n]['n_colours'] = @fp.read(4).reverse.unpack("v*")[0]
587
+ @picture[n]['raw_data_length'] = @fp.read(4).reverse.unpack("V*")[0]
588
+ @picture[n]['raw_data_offset'] = @fp.tell
589
+
590
+ @metadata_blocks[-1] << @picture[n]['block_size']
591
+
467
592
  @fp.seek((@picture[n]['raw_data_length']), IO::SEEK_CUR)
468
593
  rescue
469
594
  raise FlacInfoError, "Could not parse METADATA_BLOCK_PICTURE"
@@ -472,8 +597,12 @@ class FlacInfo
472
597
 
473
598
  def parse_application
474
599
  begin
475
- @application['block_size'] = @fp.read(3).reverse.unpack("v*")[0]
476
- @application['offset'] = @fp.tell
600
+ @application['block_size'] = @fp.read(3).unpack("B*")[0].to_i(2)
601
+ @application['offset'] = @fp.tell
602
+
603
+ @metadata_blocks[-1] << @application['offset']
604
+ @metadata_blocks[-1] << @application['block_size']
605
+
477
606
  @application['ID'] = @fp.read(4).unpack("H*")[0]
478
607
 
479
608
  app_id = {"41544348" => "Flac File", "43756573" => "GoldWave Cue Points",
@@ -503,17 +632,20 @@ class FlacInfo
503
632
  # separated into key=value pairs
504
633
  def parse_vorbis_comment
505
634
  begin
506
- @tags = {}
507
- @tags['block_size'] = @fp.read(3).reverse.unpack("v*")[0]
508
- @tags['offset'] = @fp.tell
509
- vendor_length = @fp.read(4).unpack("V")[0]
510
- @tags['vendor_tag'] = @fp.read(vendor_length)
511
- user_comment_list_length = @fp.read(4).unpack("V")[0]
512
- @comment = []
513
- n = 0
635
+ @tags['block_size'] = @fp.read(3).unpack("B*")[0].to_i(2)
636
+ @tags['offset'] = @fp.tell
637
+
638
+ @metadata_blocks[-1] << @tags['offset']
639
+ @metadata_blocks[-1] << @tags['block_size']
514
640
 
641
+ vendor_length = @fp.read(4).reverse.unpack("B*")[0].to_i(2)
642
+
643
+ @tags['vendor_tag'] = @fp.read(vendor_length)
644
+ user_comment_list_length = @fp.read(4).reverse.unpack("B*")[0].to_i(2)
645
+
646
+ n = 0
515
647
  user_comment_list_length.times do
516
- length = @fp.read(4).unpack("V")[0]
648
+ length = @fp.read(4).reverse.unpack("B*")[0].to_i(2)
517
649
  @comment[n] = @fp.read(length)
518
650
  n += 1
519
651
  end
@@ -539,8 +671,12 @@ class FlacInfo
539
671
  # padding is just a bunch of '0' bytes
540
672
  def parse_padding
541
673
  begin
542
- @padding['block_size'] = @fp.read(3).reverse.unpack("v*")[0]
543
- @padding['offset'] = @fp.tell
674
+ @padding['block_size'] = @fp.read(3).unpack("B*")[0].to_i(2)
675
+ @padding['offset'] = @fp.tell
676
+
677
+ @metadata_blocks[-1] << @padding['offset']
678
+ @metadata_blocks[-1] << @padding['block_size']
679
+
544
680
  @fp.seek(@padding['block_size'], IO::SEEK_CUR)
545
681
  rescue
546
682
  raise FlacInfoError, "Could not parse METADATA_BLOCK_PADDING"
@@ -549,8 +685,12 @@ class FlacInfo
549
685
 
550
686
  def parse_streaminfo
551
687
  begin
552
- @streaminfo['block_size'] = @fp.read(3).reverse.unpack("v*")[0]
688
+ @streaminfo['block_size'] = @fp.read(3).unpack("B*")[0].to_i(2)
553
689
  @streaminfo['offset'] = @fp.tell
690
+
691
+ @metadata_blocks[-1] << @streaminfo['offset']
692
+ @metadata_blocks[-1] << @streaminfo['block_size']
693
+
554
694
  @streaminfo['minimum_block'] = @fp.read(2).reverse.unpack("v*")[0]
555
695
  @streaminfo['maximum_block'] = @fp.read(2).reverse.unpack("v*")[0]
556
696
  @streaminfo['minimum_frame'] = @fp.read(3).reverse.unpack("v*")[0]
@@ -588,4 +728,108 @@ class FlacInfo
588
728
  raise FlacInfoError, "Could not parse Flac File data"
589
729
  end
590
730
  end
731
+
732
+ # Here we begin the FlacInfo write methods
733
+
734
+
735
+ # Build a block header given a type, a size, and whether it is last
736
+ def build_block_header(type, size, last)
737
+ begin
738
+ bit_string = sprintf("%b%7b", last, type).gsub(" ","0")
739
+ block_header_s = [bit_string].pack("B*")
740
+ block_header_s += [size].pack("VX").reverse # size is 3 bytes
741
+ rescue
742
+ raise FlacInfoWriteError, "error building block header"
743
+ end
744
+ end
745
+
746
+ # Build a string of packed data for the Vorbis comments
747
+ def build_vorbis_comment_block
748
+ begin
749
+ vorbis_comm_s = [@tags["vendor_tag"].length].pack("V")
750
+ vorbis_comm_s += [@tags["vendor_tag"]].pack("A*")
751
+ vorbis_comm_s += [@comment.length].pack("V")
752
+ @comment.each do |c|
753
+ vorbis_comm_s += [c.length].pack("V")
754
+ vorbis_comm_s += [c].pack("A*")
755
+ end
756
+ vorbis_comm_s
757
+ rescue
758
+ raise FlacInfoWriteError, "error building vorbis comment block"
759
+ end
760
+ end
761
+
762
+ def write_to_disk
763
+ if @comments_changed == nil
764
+ raise FlacInfoWriteError, "No changes to write"
765
+ else
766
+ vcd = build_vorbis_comment_block # Build the VORBIS_COMMENT data
767
+ vch = build_block_header(4, vcd.length, 0) # Build the VORBIS_COMMENT header
768
+ end
769
+
770
+ # Determine if we can shuffle the data or if a rewrite is necessary
771
+ begin
772
+ if not @padding.has_key?("block_size") or vcd.length > @padding['block_size']
773
+ rewrite(vcd, vch) # Rewriting is simpler but more expensive
774
+ else
775
+ shuffle(vcd, vch) # Shuffling is more complicated but cheaper
776
+ end
777
+ parse_flac_meta_blocks # Parse the file again to update new values
778
+ return true
779
+ rescue
780
+ raise FlacInfoWriteError, "error writing new data to #{@filename}"
781
+ end
782
+ end
783
+
784
+ # Shuffle the data and update the PADDING block
785
+ def shuffle(vcd, vch)
786
+ flac = File.new(@filename, "r+b")
787
+ flac.binmode # For Windows folks...
788
+
789
+ # Position ourselves at end of current Vorbis block
790
+ flac.seek((@tags['offset'] + @tags['block_size']), IO::SEEK_CUR)
791
+ # The data we need to shuffle starts at current position and ends at
792
+ # the beginning of the padding block, so the size we need to read is:
793
+ #
794
+ # (offset of padding minus 4 bytes for the padding header) minus our current position
795
+ #
796
+ size_to_read = (@padding['offset'] - 4) - flac.tell
797
+ data_to_shuffle = flac.read(size_to_read)
798
+
799
+ flac.seek((@tags['offset'] - 4), IO::SEEK_SET)
800
+ flac.write(vch) # Write the VORBIS_COMMENT header
801
+ flac.write(vcd) # Write the VORBIS_COMMENT data
802
+ flac.write(data_to_shuffle) # Write the shuffled data
803
+
804
+ new_padding_size = @padding['block_size'] - (vcd.length - @tags['block_size'])
805
+ ph = build_block_header(1, new_padding_size, 1) # Build the new PADDING header
806
+
807
+ flac.write(ph) # Write the new PADDING header
808
+ flac.close # ...and we're done
809
+ end
810
+
811
+ # Rewrite the entire file
812
+ def rewrite(vcd, vch)
813
+ flac = File.new(@filename, "r+b")
814
+ flac.binmode # For Windows folks...
815
+
816
+ flac.seek((@tags['offset'] + @tags['block_size']), IO::SEEK_CUR)
817
+ rest_of_file = flac.read()
818
+ flac.seek((@tags['offset'] - 4), IO::SEEK_SET)
819
+
820
+ flac.write(vch) # Write the VORBIS_COMMENT header
821
+ flac.write(vcd) # Write the VORBIS_COMMENT data
822
+ flac.write(rest_of_file) # Write the rest of the file
823
+
824
+ flac.close
825
+ end
826
+
827
+ end
828
+
829
+ # If called directly from the command line, run meta_flac on each argument
830
+ if __FILE__ == $0
831
+ ARGV.each do |filename|
832
+ FlacInfo.new(filename).meta_flac
833
+ puts
834
+ end
591
835
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: flacinfo-rb
5
5
  version: !ruby/object:Gem::Version
6
- version: "0.2"
7
- date: 2007-06-23 00:00:00 -06:00
6
+ version: "0.3"
7
+ date: 2007-07-01 00:00:00 -06:00
8
8
  summary: Pure Ruby lib for accessing metadata (including Vorbis tags) from Flac files
9
9
  require_paths:
10
10
  - lib