riff 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.
@@ -3,52 +3,106 @@
3
3
  # simple PCM WAVE file of a 1K tone and encodes some
4
4
  # metadata into it.
5
5
 
6
- require 'lib/riff/base.rb'
6
+ require 'lib/riff/reader.rb'
7
7
  require 'lib/riff/writer.rb'
8
8
 
9
-
10
9
  include Riff
11
10
 
11
+ class Bext
12
+
13
+ attr_accessor :description,
14
+ :originator,
15
+ :originator_reference,
16
+ :origination_time,
17
+ :time_reference,
18
+ :coding_history
19
+
20
+ class << self
21
+ def sanitize_for_length(field,length)
22
+ field.to_s[0,length].ljust(length,"\0")
23
+ end
24
+
25
+ def ethernet_addy
26
+ md = `/sbin/ifconfig | grep ether`.match(/([a-f0-9]{2})\:([a-f0-9]{2})\:([a-f0-9]{2})\:([a-f0-9]{2})\:([a-f0-9]{2})\:([a-f0-9]{2})/)
27
+ md[1,6].join.ljust(12,"0")
28
+ end
29
+
30
+ end
31
+
32
+ def initialize
33
+ @description = ""
34
+ @originator = ""
35
+ @originator_reference = "jamie" + Bext.ethernet_addy + Time.now.strftime("%H%M%S") +
36
+ (([0] * 10).map {|e| rand(10)}).join
37
+ @origination_time = Time.now
38
+ @time_reference = 0
39
+ @coding_history = ""
40
+ @smpte_umid = ""
41
+ end
42
+
43
+ def out
44
+ time_ref_bytes = Builder::int_to_four_little_endian_bytes(@time_reference).map {|b| b.chr}
45
+ Bext.sanitize_for_length(@description,256) +
46
+ Bext.sanitize_for_length(@originator,32) +
47
+ Bext.sanitize_for_length(@originator_reference,32) +
48
+ @origination_time.strftime("%Y-%m-%d%H:%M:%S") +
49
+ "\0\0\0\0\0\0\0\0" + "\001\000\000\000" +
50
+ Bext.sanitize_for_length(@smpte_umid,64) +
51
+ ("\000" * 190)# + @coding_history
52
+ end
53
+
54
+ end
55
+
12
56
  SAMPLES_PER_SECOND = 48000
13
57
  FREQ = 1000
14
58
  AMPLITUDE = 0x0fff
15
59
  FILE_TO_MAKE = "test.wav"
16
60
 
61
+ DESCRIPTION = "This file is a 1K tone generated programatically with Ruby libRiff"
62
+ AUTHOR = "Jamie Hardt"
63
+
17
64
  Builder.create(FILE_TO_MAKE,"WAVE") do |w|
18
65
  w.list("INFO") { |info|
19
- info.chunk("ICOP") { |c| #"copyright"
66
+ info.chunk("ICOP") {|c| # "copyright"
20
67
  c << "(c) 2007 Jamie Hardt. All Rights Reserved."
21
68
  }
22
- info.chunk("ICMT") {|c| # "comment"
23
- c << "This file is a 1K tone generated programatically with Ruby libRiff"
69
+ info.chunk("ICMT") {|c| # "comment"
70
+ c << DESCRIPTION
24
71
  }
25
- info.chunk("INAM") {|c| # "name" or "title"
26
- c<< "1K tone with metadata"
72
+ info.chunk("INAM") {|c| # "name" or "title"
73
+ c << "1K tone with metadata"
27
74
  }
28
75
  info.chunk("ISFT") {|c| # "software"
29
- c<< "libRiff v" + "%i.%i" % [Riff::VERSION::MAJOR, Riff::VERSION::MINOR]
76
+ c << "libRiff v" + "%i.%i" % [Riff::VERSION::MAJOR, Riff::VERSION::MINOR]
30
77
  }
31
78
  info.chunk("IART") {|c| # "artist" (maps to kMDItemAuthors in Spotlight)
32
- c << "Jamie Hardt"
79
+ c << AUTHOR
33
80
  }
34
81
  }
82
+ w.chunk("bext") {|b|
83
+ bext = Bext.new
84
+ bext.description = DESCRIPTION
85
+ bext.originator = AUTHOR
86
+ bext.coding_history = "Created by #{__FILE__}"
87
+ b << bext.out
88
+ }
35
89
  w.chunk("fmt ") { |c|
36
- c.putc(1);c.putc(0); #format 1, PCM
90
+ c.putc(1);c.putc(0) #format 1, PCM
37
91
  c.putc(1);c.putc(0) #1 channel
38
92
 
39
93
  # samples per second
40
- int_to_four_little_endian_bytes(SAMPLES_PER_SECOND).each {|byte| c.putc(byte)}
94
+ Builder.int_to_four_little_endian_bytes(SAMPLES_PER_SECOND).each {|byte| c.putc(byte)}
41
95
 
42
96
  # avg. bytes per second
43
- int_to_four_little_endian_bytes(SAMPLES_PER_SECOND * 2).each {|byte| c.putc(byte)}
97
+ Builder.int_to_four_little_endian_bytes(SAMPLES_PER_SECOND * 2).each {|byte| c.putc(byte)}
44
98
  c.putc(2);c.putc(0) #block alignment 2
45
99
  c.putc(16);c.putc(0) #16 bits per sample
46
100
  }
47
101
  w.chunk("data") { |c|
48
- denom = SAMPLES_PER_SECOND / (FREQ * 2)
49
- 0.upto(SAMPLES_PER_SECOND) do |i|
50
- val = (Math::sin(Math::PI * i/denom) * AMPLITUDE).floor
51
- int_to_four_little_endian_bytes(val)[0,2].each {|byte| c.putc(byte)}
102
+ factor = Math::PI / (SAMPLES_PER_SECOND / (FREQ * 2))
103
+ 1.upto(SAMPLES_PER_SECOND) do |i|
104
+ val = ( Math::sin( i * factor ) * AMPLITUDE ).floor
105
+ Builder::int_to_four_little_endian_bytes(val)[0,2].each {|byte| c.putc(byte)}
52
106
  end
53
107
  }
54
108
  end
@@ -0,0 +1,15 @@
1
+ require 'lib/riff/info.rb'
2
+
3
+ i = Riff::MetaEditor.new(ARGV[0])
4
+
5
+ i.comment = "This is a test comment written by metariff"
6
+
7
+ i.save!
8
+
9
+ j = Riff::MetaEditor.new(ARGV[0])
10
+
11
+ puts "%-4s %s" % ['4CC','Value']
12
+ puts "%-4s %s" % ["-"*4,"-"*74]
13
+ j.each_metadatum do |fourcc, value|
14
+ puts "%-4s %s" % [fourcc, value[0,74]]
15
+ end
@@ -1,4 +1,4 @@
1
- require 'lib/riff/base.rb'
1
+ require 'lib/riff/reader.rb'
2
2
 
3
3
  def print_chunk(ck,margin = "")
4
4
  if ck.is_list? then
@@ -11,6 +11,6 @@ def print_chunk(ck,margin = "")
11
11
  end
12
12
  end
13
13
 
14
- Riff::Base.open(ARGV[0],"r") do |wav|
14
+ Riff::Reader.open(ARGV[0],"r") do |wav|
15
15
  print_chunk( wav.root_chunk )
16
16
  end
@@ -0,0 +1,162 @@
1
+ require 'lib/riff/reader.rb'
2
+ require 'lib/riff/writer.rb'
3
+
4
+ module Riff
5
+
6
+ # Th MetaEditor class provides a reader-writer for the INFO meta-chunk on RIFF
7
+ # AVI and WAVE files.
8
+ #
9
+ # Aside from the stated methods, there is a convenience method for every attribute
10
+ # name listed in the <tt>MetaEditor.available_names</tt> array, thus, these lines are
11
+ # equivalent:
12
+ #
13
+ # editor.author = "Me Myself"
14
+ # editor.artist = "Me Myself"
15
+ # editor['IART'] = "Me Myself"
16
+ #
17
+ # Note that some names map to the same fourcc.
18
+ class MetaEditor
19
+
20
+ # the path to the file being edited
21
+ attr_reader :path
22
+
23
+ # the number of bytes padding to add after the INFO
24
+ # chunk. The default is 1024 bytes.
25
+ attr_accessor :pad_out
26
+
27
+ ATTRIBUTE_FOURCCs = [ 'IARL', 'IART','ICSM','ICMT', 'ICOP', 'ICRD',
28
+ 'ICRP', 'IDIM', 'IDPI', 'IENG','IGNR', 'IKEY',
29
+ 'ILGT', 'IMED', 'INAM', 'IPLT', 'IPRD', 'ISBJ',
30
+ 'ISFT', 'ISHP', 'ISRC', 'ISRF', 'ITCH']
31
+
32
+ ATTRIBUTE_NAMES = {
33
+ 'Archival Location' => 'IARL',
34
+ 'Artist' => 'IART',
35
+ 'Author' => 'IART',
36
+ 'Comissioned' => 'ICSM',
37
+ 'Comment' => 'ICMT',
38
+ 'Description' => 'ICMT',
39
+ 'Copyright' => 'ICOP',
40
+ 'Date Created' => 'ICRD',
41
+ 'Cropped' => 'ICRP',
42
+ 'Dimensions' => 'IDIM',
43
+ 'Dots Per Inch' => 'IDPI',
44
+ 'Engineer' => 'IENG',
45
+ 'Genre' => 'IGNR',
46
+ 'Keywords' => 'IKEY',
47
+ 'Lightness' => 'ILGT',
48
+ 'Medium' => 'IMED',
49
+ 'Title' => 'INAM',
50
+ 'Name' => 'INAM',
51
+ 'Number of Colors' => 'IPLT',
52
+ 'Product' => 'IPRD',
53
+ 'Subject' => 'ISBJ',
54
+ 'Software' => 'ISFT',
55
+ 'Encoding Application' => 'ISFT',
56
+ 'Sharpness' => 'ISHP',
57
+ 'Source' => 'ISRC',
58
+ 'Source Form' => 'ISRF',
59
+ 'Technician' => 'ITCH'
60
+ }
61
+
62
+ ATTRIBUTE_FOURCCs.each do |key|
63
+ class_eval <<-TXT
64
+ def #{key.downcase}=(val)
65
+ @metadata['#{key}'] = val
66
+ end
67
+ def #{key.downcase}
68
+ return @metadata['#{key}']
69
+ end
70
+ TXT
71
+ end
72
+
73
+ ATTRIBUTE_NAMES.each_pair do |key,value|
74
+ class_eval <<-TXT
75
+ def #{key.downcase.gsub(/ /,'_').gsub(/[^a-z_]/,'')}=(val)
76
+ @metadata['#{value}'] = val
77
+ end
78
+
79
+ def #{key.downcase.gsub(/ /,'_').gsub(/[^a-z_]/,'')}
80
+ return @metadata['#{value}']
81
+ end
82
+ TXT
83
+ end
84
+
85
+ # Returns an array of English names for a fourcc tag
86
+ def MetaEditor.names_for_fourcc(fourcc)
87
+ return (ATTRIBUTE_NAMES.select {|k,v| v == fourcc}).map {|a| a[0]}
88
+ end
89
+
90
+ # Returns an array of all the settable attributes for the file
91
+ def MetaEditor.available_names(fourcc)
92
+ return ATTRIBUTE_NAMES.keys
93
+ end
94
+
95
+ # Gives the fourcc the class will map to a given English name
96
+ def MetaEditor.fourcc_for_name(name)
97
+ return ATTRIBUTE_NAMES[name]
98
+ end
99
+
100
+ # Creates a MetaEditor object for the file locates at +path+
101
+ def initialize(path)
102
+ reader = Reader.open(path,'r')
103
+ @pad_out = 1024
104
+ @path = path
105
+ @metadata = {}
106
+ infoChunk = reader.root_chunk.find {|c| c.signature == 'INFO'}
107
+ if infoChunk && infoChunk.is_list?
108
+ infoChunk.each do |subChunk|
109
+ @metadata[subChunk.fourcc] = subChunk.body
110
+ end
111
+ end
112
+ end
113
+
114
+ def [](k)
115
+ return @metadata[k]
116
+ end
117
+
118
+ def []=(k,v)
119
+ @metadata[k] = v
120
+ end
121
+
122
+ def each_metadatum
123
+ @metadata.each_pair do |key,value|
124
+ yield key,value
125
+ end
126
+ end
127
+
128
+ # Commit the changed metadata to the file. Presently, the complete file
129
+ # is rewritten to a temp file and then is moved to the name of the target.
130
+ #
131
+ # If a pad_out number is specified, a "JUNK" chunk with a payload of pad_out
132
+ # bytes will be added after the INFO chunk.
133
+ def save!
134
+ tempPath = File.dirname(path) + "/" + (([?a] *8).map {|i| (i + rand(26)).chr}).join + ".tmp"
135
+ Reader.open(path,'r') do |reader|
136
+ begin
137
+ Builder.create(tempPath,reader.root_chunk.signature) do |riffwriter|
138
+ riffwriter.list('INFO') do |infochunk|
139
+ @metadata.each_pair do |key,value|
140
+ infochunk.chunk(key) { |io| io << value }
141
+ end
142
+ end
143
+ if @pad_out > 0
144
+ riffwriter.chunk("JUNK") do |jnk|
145
+ jnk << "\0" * @pad_out
146
+ end
147
+ end
148
+ reader.root_chunk.each do |ck|
149
+ unless ck.is_list? && ck.signature == 'INFO' then
150
+ riffwriter.chunk(ck.fourcc) {|io| io << ck.body}
151
+ end #unless
152
+ end
153
+ end
154
+ rescue Exception => e
155
+ File.unlink(tempPath) if FileTest.exist?(tempPath)
156
+ raise e
157
+ end
158
+ end
159
+ File.rename(tempPath,path)
160
+ end #save!
161
+ end #class
162
+ end # moule Riff
@@ -33,12 +33,12 @@
33
33
  # === Getting iXML metadata from a broadcast-WAV file
34
34
  #
35
35
  # require 'riff/base'
36
- # Riff::Base.open(ARGV[0],"r") do |wav|
36
+ # Riff::Reader.open(ARGV[0],"r") do |wav|
37
37
  # xml = wav.root_chunk['ixml'].body
38
38
  # end
39
39
  #
40
40
  # === Listing all the chunks in a file
41
- # require 'riff/base'
41
+ # require 'riff/reader'
42
42
  #
43
43
  # def print_chunk(ck,margin = "")
44
44
  # if ck.is_list? then
@@ -52,7 +52,7 @@
52
52
  # end
53
53
  # end
54
54
  #
55
- # Riff::Base.open(ARGV[0],"r") do |wav|
55
+ # Riff::Reader.open(ARGV[0],"r") do |wav|
56
56
  # print_chunk( wav.root_chunk )
57
57
  # end
58
58
  #
@@ -60,15 +60,15 @@ module Riff
60
60
 
61
61
  module VERSION
62
62
  MAJOR = 0
63
- MINOR = 2
63
+ MINOR = 3
64
64
  end #module version
65
65
 
66
- class Base
67
- # The Riff class provides a front-end to the other objects of the library.
66
+ class Reader
67
+ # The Reader class provides a front-end to the other objects of the library.
68
68
  # It's the only object that you interact with directly, and calling its methods
69
69
  # will return the other objects of the class.
70
70
 
71
- # Returns the File object for this Riff object.
71
+ # Returns the File object for this reader.
72
72
  attr_reader :file
73
73
 
74
74
  # Opens a RIFF file for reading. Pass a string path and string mode, as you would
@@ -79,8 +79,8 @@ module Riff
79
79
  # sole parameter, and will be automatically closed at the end of the block.
80
80
  # * If you pass a file which does not start with the RIFF fourcc, the file
81
81
  # is invalid and a FileTypeError will be thrown.
82
- def self.open(path,mode) # :yields: riff_object
83
- riff_obj = Riff::Base.new(path,mode)
82
+ def Reader.open(path,mode) # :yields: riff_object
83
+ riff_obj = Riff::Reader.new(path,"r")
84
84
  if block_given? && riff_obj then
85
85
  yield riff_obj
86
86
  riff_obj.close
@@ -102,26 +102,19 @@ module Riff
102
102
 
103
103
  class FileTypeError < StandardError ; end #:nodoc:
104
104
 
105
- protected
106
-
107
- def valid?
108
-
109
- end
110
-
111
105
  private
112
106
 
113
- def initialize(path, mode)
107
+ def initialize(path, mode) #:nodoc:
114
108
  raise(ArgumentError, "Riff.open only supports mode 'r' or 'rb'.") \
115
109
  unless mode == "r" || mode == "rb"
116
110
 
117
111
  @file = File.open(path,mode)
118
- return nil unless valid?
119
112
  end #def initialize
120
113
 
121
114
  end
122
115
 
123
116
 
124
- class Base
117
+ class Reader
125
118
 
126
119
  # The Chunk object represents a chunk of a larger RIFF file. The chunk may be
127
120
  # interrogated to reveal its fourcc and the size of its body. The chunk also
@@ -129,7 +122,6 @@ module Riff
129
122
  # Keep in mind that this bit of data can be very, very large in media files
130
123
  # (like .wav files) and that reading them in a language like ruby will be quite
131
124
  # slow.
132
-
133
125
  class Chunk
134
126
 
135
127
  # the four-character code of this chunk, always four characters long
@@ -145,7 +137,7 @@ module Riff
145
137
  # the offset of this chunk from the start of its file
146
138
  attr_reader :offset
147
139
 
148
-
140
+
149
141
  def self.read_chunk(fp) #:nodoc:
150
142
  code = fp.read(4)
151
143
  fp.pos -= 4
@@ -165,9 +157,10 @@ module Riff
165
157
  # Returns <tt>true</tt> if this object is a ListChunk. A convenience to typing
166
158
  # <tt>instance_of? Riff::ListChunk</tt>
167
159
  def is_list?
168
- instance_of? Riff::Base::ListChunk
160
+ instance_of? Riff::Reader::ListChunk
169
161
  end
170
-
162
+
163
+ # Yields each byte of the data chunk.
171
164
  def each_byte
172
165
  while @file.pos < @offset + 8 + @length
173
166
  yield @file.getc
@@ -234,14 +227,6 @@ module Riff
234
227
  end
235
228
  end
236
229
 
237
- def body # :nodoc:
238
- raise NoMethodError , "ListChunk#body is not permitted!"
239
- end
240
-
241
- def each_byte
242
- raise NoMethodError
243
- end
244
-
245
230
  protected
246
231
 
247
232
  def initialize(fp)
@@ -2,21 +2,11 @@ require 'stringio'
2
2
 
3
3
  module Riff
4
4
 
5
- def int_to_four_little_endian_bytes(i) # :nodoc:
6
- n =i # [ i.abs , 2^32 ].min
7
- [(n.to_i & 0xff) , (n.to_i & 0xff00)>>8,
8
- (n.to_i & 0xff0000)>>16,(n.to_i & 0xff000000)>>24 ]
9
- end
10
-
11
- def sanitize_fourcc(fourcc)
12
- fourcc[0,4].ljust(4,"\0")
13
- end
14
-
15
- # A Builder allows you to create a 'de novo' RIFF file. At this time
16
- # we do not support modifying RIFF files, only creating new ones.
5
+ # A Builder allows you to create a 'de novo' RIFF file recursively, similar
6
+ # in style to the XML Builder class.
17
7
  class Builder
18
8
 
19
- class IOProxy
9
+ class IOProxy #:nodoc:
20
10
 
21
11
  def initialize(i)
22
12
  @io = i
@@ -56,39 +46,50 @@ module Riff
56
46
  file.close
57
47
  end
58
48
 
59
- end #class << self
49
+ def int_to_four_little_endian_bytes(i) #:nodoc:
50
+ n =i # [ i.abs , 2^32 ].min
51
+ [(n.to_i & 0xff) , (n.to_i & 0xff00)>>8,
52
+ (n.to_i & 0xff0000)>>16,(n.to_i & 0xff000000)>>24 ]
53
+ end
54
+
55
+ def sanitize_fourcc(fourcc) #:nodoc:
56
+ fourcc[0,4].ljust(4,"\0")
57
+ end
60
58
 
61
- attr_reader :io
59
+ end #class << self
62
60
 
61
+ # Add a data chunk to the Builder. The method yields a write-only pseudo-IO
62
+ # object that you write the chunk data to. Only write the payload of the chunk
63
+ # here; the fourcc and size will be written for you. Do not pad the data you write to
64
+ # an even boundary, either.
63
65
  def chunk(fourcc) #:yields: stream
64
- $stderr.print("descending into chunk #{fourcc}, starts at #{@io.pos}\n")
65
- @io.write(sanitize_fourcc(fourcc))
66
+ @io.write(self.class.sanitize_fourcc(fourcc))
66
67
  4.times {@io.putc(0)}
67
68
  oldPos = @io.pos
68
69
  yield IOProxy.new(@io)
69
70
  length = @io.pos - oldPos
70
71
  @io.putc(0) if (length % 2) == 1
71
72
  @io.seek(oldPos-4,IO::SEEK_SET)
72
- bytes = int_to_four_little_endian_bytes(length)
73
- $stderr.print("will write bytes size: #{bytes.inspect}\n")
73
+ bytes = self.class.int_to_four_little_endian_bytes(length)
74
74
  bytes.each {|byte| @io.putc(byte)}
75
75
  @io.seek(0,IO::SEEK_END)
76
- $stderr.print("ascending from chunk #{fourcc}, total written #{length}\n")
77
76
  end
78
77
 
79
- def list(type)
78
+ # Add a RIFF "LIST" chunk. The method yields a builder, which you can add
79
+ # chunks and other lists to.
80
+ def list(type) #:yields: builder
80
81
  container("LIST",type) do |io|
81
82
  yield Builder.new(io)
82
83
  end
83
84
  end
84
85
 
85
- def initialize(stream)
86
+ def initialize(stream) # :nodoc:
86
87
  @io = stream
87
88
  end
88
89
 
89
- def container(fourcc,type)
90
- chunk(sanitize_fourcc(fourcc)) do |io|
91
- io.write(sanitize_fourcc(type))
90
+ def container(fourcc,type) # :nodoc:
91
+ chunk(self.class.sanitize_fourcc(fourcc)) do |io|
92
+ io.write(self.class.sanitize_fourcc(type))
92
93
  yield io
93
94
  end
94
95
  end
metadata CHANGED
@@ -3,9 +3,9 @@ rubygems_version: 0.9.1
3
3
  specification_version: 1
4
4
  name: riff
5
5
  version: !ruby/object:Gem::Version
6
- version: "0.2"
7
- date: 2007-12-24 00:00:00 -08:00
8
- summary: Library for reading and writing RIFF (Resource Interchange File Format) files. RIFF is a meta-format which is a common wrapper for multimedia files, such as .wav and .avi files.
6
+ version: "0.3"
7
+ date: 2007-12-27 00:00:00 -08:00
8
+ summary: Library for reading and writing RIFF (Resource Interchange File Format) files. RIFF is a meta-format which is a common wrapper for multimedia files, such as .wav and .avi files. Classes for reading and writing generic RIFF files are provided, as well as convenient INFO tag editing.
9
9
  require_paths:
10
10
  - lib
11
11
  email:
@@ -29,7 +29,8 @@ post_install_message:
29
29
  authors: []
30
30
 
31
31
  files:
32
- - lib/riff/base.rb
32
+ - lib/riff/info.rb
33
+ - lib/riff/reader.rb
33
34
  - lib/riff/writer.rb
34
35
  - Rakefile
35
36
  - test/riff_test.rb
@@ -38,6 +39,7 @@ files:
38
39
  - test_media/bad.riff
39
40
  - test_media/Pop.wav
40
41
  - examples/maketone.rb
42
+ - examples/metariff.rb
41
43
  - examples/readchunks.rb
42
44
  test_files:
43
45
  - test/riff_test_suite.rb