flacinfo-rb 0.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
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