MP4Info 0.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/README +2 -1
  2. data/lib/mp4info.rb +124 -31
  3. data/test/test.rb +83 -82
  4. metadata +4 -3
data/README CHANGED
@@ -4,7 +4,8 @@ It is based on the Perl module MP4::Info (http://search.cpan.org/~jhar/MP4-Info/
4
4
  Note: MP4Info does not currently support Unicode strings.
5
5
 
6
6
  = License
7
- Copyright (C) 2006 Jason Terk <rain@xidus.net>
7
+ Copyright (c) 2004-2007 Jonathan Harris <jhar@cpan.org>
8
+ Copyright (C) 2006-2007 Jason Terk <rain@xidus.net>
8
9
 
9
10
  This program is free software; you can redistribute it and/or modify
10
11
  it under the terms of version 2 of the GNU General Public License as
data/lib/mp4info.rb CHANGED
@@ -4,7 +4,8 @@
4
4
  # Note: MP4Info does not currently support Unicode strings.
5
5
  #
6
6
  # = License
7
- # Copyright (C) 2006 Jason Terk <rain@xidus.net>
7
+ # Copyright (c) 2004-2007 Jonathan Harris <jhar@cpan.org>
8
+ # Copyright (C) 2006-2007 Jason Terk <rain@xidus.net>
8
9
  #
9
10
  # This program is free software; you can redistribute it and/or modify
10
11
  # it under the terms of version 2 of the GNU General Public License as
@@ -21,6 +22,8 @@
21
22
  #
22
23
  # See the README file for usage information.
23
24
 
25
+ require 'tempfile'
26
+
24
27
  class MP4Info
25
28
  # Initialize a new MP4Info object from an IO object
26
29
  def initialize(io_stream)
@@ -51,7 +54,8 @@ class MP4Info
51
54
  # Non standard data atoms
52
55
  @other_atoms = {
53
56
  "MDAT" => :parse_mdat, "META" => :parse_meta,
54
- "MVHD" => :parse_mvhd, "STSD" => :parse_stsd
57
+ "MVHD" => :parse_mvhd, "STSD" => :parse_stsd,
58
+ "MOOV" => :parse_moov
55
59
  }
56
60
 
57
61
  # Info/Tag aliases
@@ -62,7 +66,7 @@ class MP4Info
62
66
 
63
67
  # Sanity check
64
68
  head = read_or_raise(io_stream, 8, "#{io_stream} does not appear to be an IO stream")
65
- raise "#{io_stream} does not appear to be an IO stream" unless head[4..7].downcase == "ftyp"
69
+ raise "#{io_stream} does not appear to be an MP4 file" unless head[4..7].downcase == "ftyp"
66
70
 
67
71
  # Back to the beginning
68
72
  io_stream.rewind
@@ -101,38 +105,54 @@ class MP4Info
101
105
  private
102
106
  # Parse a container
103
107
  def parse_container(io_stream, level, size)
104
- level = level + 1
105
- cont_end = io_stream.pos + size
108
+ level += 1
109
+ container_end = io_stream.pos + size
106
110
 
107
- while io_stream.pos < cont_end do
108
- parse_atom io_stream, level
111
+ while io_stream.pos < container_end do
112
+ parse_atom io_stream, level, container_end - io_stream.pos
109
113
  end
110
114
 
111
- if (io_stream.pos != cont_end)
115
+ if (io_stream.pos != container_end)
112
116
  raise "Parse error"
113
117
  end
114
118
  end
115
119
 
116
120
  # Parse an atom
117
- def parse_atom(io_stream, level)
121
+ def parse_atom(io_stream, level, parent_size)
118
122
  head = read_or_raise(io_stream, 8, "Premature end of file")
119
123
 
120
124
  size, id = head.unpack("Na4")
121
- if (size == 1)
125
+
126
+ if (size == 0)
127
+ position = io_stream.pos
128
+ io_stream.seek(0, 2)
129
+ size = io_stream.pos - position
130
+ io_stream.seek(position, 0)
131
+ elsif (size == 1)
122
132
  # Extended size, whatever that means
123
133
  head = read_or_raise(io_stream, 8, "Premature end of file")
124
134
  hi, low = head.unpack("NN")
125
135
  size = hi * (2**32) + low - 16
136
+
137
+ if (size > parent_size)
138
+ # Atom extends outside of parent container; skip to the end
139
+ io_stream.seek(parent_size - 16, 1)
140
+ return
141
+ end
142
+
143
+ size -= 16
126
144
  else
127
- size = size - 8
128
- end
129
-
130
- if (size <= 0)
131
- if (size == 0 and level ==1)
145
+ if (size > parent_size)
146
+ # Atom extends outside of parent container; skip to the end
147
+ io_stream.seek(parent_size - 8, 1)
132
148
  return
133
- else
134
- raise "Parse error"
135
149
  end
150
+
151
+ size -= 8;
152
+ end
153
+
154
+ if (size < 0)
155
+ raise "Parse error"
136
156
  end
137
157
 
138
158
  re = /[^\w\-]/
@@ -153,6 +173,25 @@ class MP4Info
153
173
  end
154
174
  end
155
175
 
176
+ # Parse a MOOV container
177
+ #
178
+ # Pre-conditions: size = size of atom contents
179
+ # io_stream points to start of atom contents
180
+ #
181
+ # Post-condition: io_stream points past end of atom contents
182
+ def parse_moov(io_stream, level, size)
183
+ data = read_or_raise(io_stream, size, "Premature end of file")
184
+
185
+ cache = Tempfile.new "mp4info"
186
+ cache.write data
187
+ cache.open
188
+ cache.rewind
189
+
190
+ parse_container(cache, level, size)
191
+
192
+ cache.close!
193
+ end
194
+
156
195
  # Parse an MDAT atom
157
196
  #
158
197
  # Pre-conditions: size = size of atom contents
@@ -161,7 +200,7 @@ class MP4Info
161
200
  # Post-condition: io_stream points past end of atom contents
162
201
  def parse_mdat(io_stream, level, size)
163
202
  @info_atoms["SIZE"] = 0 unless @info_atoms["SIZE"]
164
- @info_atoms["SIZE"] = @info_atoms["SIZE"] + size
203
+ @info_atoms["SIZE"] += size
165
204
  io_stream.seek(size, 1)
166
205
  end
167
206
 
@@ -225,7 +264,9 @@ class MP4Info
225
264
  # Is this an audio track?
226
265
  if (data_format == "mp4a" || data_format == "drms" ||
227
266
  data_format == "samr" || data_format == "sawb" ||
228
- data_format == "sawp" || data_format == "enca")
267
+ data_format == "sawp" || data_format == "enca" ||
268
+ data_format == "alac" )
269
+ @info_atoms["ENCODING"] = data_format
229
270
  @info_atoms["FREQUENCY"] = (data[40..43].unpack("N")[0] * 1.0) / 65536000
230
271
  printf " %sFreq=%s\n", ' ' * ( 2 * level ), @info_atoms["FREQUENCY"] if $DEBUG
231
272
  end
@@ -234,6 +275,45 @@ class MP4Info
234
275
  @info_atoms["ENCRYPTED"] = true;
235
276
  end
236
277
  end
278
+
279
+ # User-defined box. Used by PSP - See ffmpeg libavformat/movenc.c
280
+ #
281
+ # Pre-conditions: size = size of atom contents
282
+ # io_stream points to start of atom contents
283
+ #
284
+ # Post-condition: io_stream points past end of atom contents
285
+ def parse_uuid(io_stream, level, size)
286
+ data = read_or_raise(io_stream, size, "Premature end of file")
287
+
288
+ return unless size > 26
289
+
290
+ u1, u2, u3, u4 = data.unpack 'a4NNN'
291
+
292
+ if (u1 == "USMT")
293
+ pspsize, pspid = data[16..23].unpack 'Na4'
294
+
295
+ return unless pspsize == size - 16
296
+
297
+ if (pspid == "MTDT")
298
+ nblocks = data[24..25].unpack 'n'
299
+ data = data[26..(data.length - 1)]
300
+
301
+ while nblocks
302
+ bsize, btype, flags, ptype = data.unpack 'nNnn'
303
+
304
+ if (btype == 1 && bsize == 12 &&
305
+ ptype == 1 && @data_atoms["NAM"].nil?)
306
+ @data_atoms["NAM"] = data[10..(10 + bsize - 11)]
307
+ elsif (btype == 4 && bsize > 12 && ptype == 1)
308
+ @data_atoms["TOO"] = data[10..(10 + bsize - 11)]
309
+ end
310
+
311
+ data = data[bsize..(data.length - 1)]
312
+ nblocks -= 1
313
+ end
314
+ end
315
+ end
316
+ end
237
317
 
238
318
  def parse_data(io_stream, level, size, id)
239
319
  # Possible genres...
@@ -322,27 +402,38 @@ class MP4Info
322
402
  ver = data.unpack("N")[0]
323
403
  if (ver == 0)
324
404
  return unless size > 7
325
- size = size - 7
405
+ size -= 7
326
406
  type = 1
327
407
  data = data[6..(6 + size - 1)]
328
408
 
329
409
  if (id == "TITL")
330
- return if @data_atoms["NAM"] != nil
410
+ return if !@data_atoms["NAM"].nil?
331
411
  id = "NAM"
332
412
  elsif (id == "DSCP")
333
- return if @data_atoms["CMT"] != nil
413
+ return if !@data_atoms["CMT"].nil?
334
414
  id = "CMT"
335
415
  elsif (id == "PERF")
336
- return if @data_atoms["ART"] != nil
416
+ return if !@data_atoms["ART"].nil?
337
417
  id = "ART"
338
418
  elsif (id == "AUTH")
339
- return if @data_atoms["WRT"] != nil
419
+ return if !@data_atoms["WRT"].nil?
340
420
  id = "WRT"
341
421
  end
342
422
  end
343
423
  end
344
424
 
345
- if (type == nil)
425
+ if (id == "MEAN" || id == "NAME" || id == "DATA")
426
+ if id == "DATA"
427
+ data = data[8..(data.length - 1)]
428
+ else
429
+ data = data[4..(data.length - 1)]
430
+ end
431
+
432
+ @data_atoms[id] = data
433
+ return
434
+ end
435
+
436
+ if (type.nil?)
346
437
  return unless size > 16
347
438
  size, atom, type = data.unpack("Na4N")
348
439
 
@@ -355,21 +446,23 @@ class MP4Info
355
446
 
356
447
  printf " %sType=#{type}, Size=#{size}\n", ' ' * ( 2 * level ) if $DEBUG
357
448
 
358
- if (type == 0)
449
+ if (id == "COVR")
450
+ @data_atoms[id] = data
451
+ elsif (type == 0)
359
452
  ints = data.unpack("n" * (size / 2))
360
453
  if (id == "GNRE")
361
454
  @data_atoms[id] = mp4_genres[ints[0]]
362
- elsif (size >= 6)
363
- @data_atoms[id] = [ints[1], ints[2]]
364
- else
455
+ elsif (id == "DISK" || id == "TRKN")
456
+ @data_atoms[id] = [ints[1], (size >= 6 ? ints[2] : 0)] if size >= 4
457
+ elsif (size >= 4)
365
458
  @data_atoms[id] = ints[1]
366
459
  end
367
460
  elsif (type == 1)
368
461
  if (id == "GEN")
369
- return if @data_atoms["GNRE"] != nil
462
+ return if !@data_atoms["GNRE"].nil?
370
463
  id = "GNRE"
371
464
  elsif (id == "AART")
372
- return if @data_atoms["ART"] != nil
465
+ return if !@data_atoms["ART"].nil?
373
466
  id = "ART"
374
467
  elsif (id == "DAY")
375
468
  data = data[0..3]
data/test/test.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Copyright (C) 2006 Jason Terk <rain@xidus.net>
3
+ # Copyright (c) 2004-2007 Jonathan Harris <jhar@cpan.org>
4
+ # Copyright (C) 2006-2007 Jason Terk <rain@xidus.net>
4
5
  #
5
6
  # This program is free software; you can redistribute it and/or modify
6
7
  # it under the terms of version 2 of the GNU General Public License as
@@ -73,33 +74,33 @@ class TestMP4Info < Test::Unit::TestCase
73
74
 
74
75
  info = {
75
76
  :ALB => 'Album',
76
- :APID => nil,
77
- :ART => 'Artist',
78
- :CMT => "Comment\r\n2nd line",
79
- :COVR => nil,
80
- :CPIL => 0,
81
- :CPRT => nil,
82
- :DAY => '2004',
83
- :DISK => [3,4],
84
- :GNRE => 'Acid Jazz',
85
- :GRP => 'Grouping',
86
- :NAM => 'Name',
87
- :TMPO => 100,
88
- :TOO => 'iTunes v4.6.0.15, QuickTime 6.5.1',
89
- :TRKN => [1,2],
90
- :WRT => 'Composer',
91
- :VERSION => 4,
92
- :LAYER => 1,
93
- :BITRATE => 50,
94
- :FREQUENCY => 44.1,
95
- :SIZE => 6962,
96
- :SECS => 1,
97
- :MM => 0,
98
- :SS => 1,
99
- :MS => 90,
100
- :TIME => '00:01',
101
- :COPYRIGHT => nil,
102
- :ENCRYPTED => nil
77
+ :APID => nil,
78
+ :ART => 'Artist',
79
+ :CMT => "Comment\r\n2nd line",
80
+ :COVR => nil,
81
+ :CPIL => 0,
82
+ :CPRT => nil,
83
+ :DAY => '2004',
84
+ :DISK => [3,4],
85
+ :GNRE => 'Acid Jazz',
86
+ :GRP => 'Grouping',
87
+ :NAM => 'Name',
88
+ :TMPO => 100,
89
+ :TOO => 'iTunes v4.6.0.15, QuickTime 6.5.1',
90
+ :TRKN => [1,2],
91
+ :WRT => 'Composer',
92
+ :VERSION => 4,
93
+ :LAYER => 1,
94
+ :BITRATE => 50,
95
+ :FREQUENCY => 44.1,
96
+ :SIZE => 6962,
97
+ :SECS => 1,
98
+ :MM => 0,
99
+ :SS => 1,
100
+ :MS => 90,
101
+ :TIME => '00:01',
102
+ :COPYRIGHT => nil,
103
+ :ENCRYPTED => nil
103
104
  }
104
105
 
105
106
  mp4 = MP4Info.open(file)
@@ -115,33 +116,33 @@ class TestMP4Info < Test::Unit::TestCase
115
116
 
116
117
  info = {
117
118
  :ALB => nil,
118
- :APID => nil,
119
- :ART => 'Artist',
120
- :CMT => nil,
121
- :COVR => nil,
122
- :CPIL => nil,
123
- :CPRT => nil,
124
- :DAY => nil,
125
- :DISK => nil,
126
- :GNRE => nil,
127
- :GRP => nil,
128
- :NAM => 'Name',
129
- :TMPO => nil,
130
- :TOO => 'Nero AAC Codec 2.9.9.91',
131
- :TRKN => nil,
132
- :WRT => nil,
133
- :VERSION => 4,
134
- :LAYER => 1,
135
- :BITRATE => 21,
136
- :FREQUENCY => 8,
137
- :SIZE => 3030,
138
- :SECS => 1,
139
- :MM => 0,
140
- :SS => 1,
141
- :MS => 153,
142
- :TIME => '00:01',
143
- :COPYRIGHT => nil,
144
- :ENCRYPTED => nil
119
+ :APID => nil,
120
+ :ART => 'Artist',
121
+ :CMT => nil,
122
+ :COVR => nil,
123
+ :CPIL => nil,
124
+ :CPRT => nil,
125
+ :DAY => nil,
126
+ :DISK => nil,
127
+ :GNRE => nil,
128
+ :GRP => nil,
129
+ :NAM => 'Name',
130
+ :TMPO => nil,
131
+ :TOO => 'Nero AAC Codec 2.9.9.91',
132
+ :TRKN => nil,
133
+ :WRT => nil,
134
+ :VERSION => 4,
135
+ :LAYER => 1,
136
+ :BITRATE => 21,
137
+ :FREQUENCY => 8,
138
+ :SIZE => 3030,
139
+ :SECS => 1,
140
+ :MM => 0,
141
+ :SS => 1,
142
+ :MS => 153,
143
+ :TIME => '00:01',
144
+ :COPYRIGHT => nil,
145
+ :ENCRYPTED => nil
145
146
  }
146
147
 
147
148
  mp4 = MP4Info.open(file)
@@ -161,33 +162,33 @@ class TestMP4Info < Test::Unit::TestCase
161
162
 
162
163
  info = {
163
164
  :ALB => 'Album',
164
- :APID => nil,
165
- :ART => 'AÆtist',
166
- :CMT => 'Comment',
167
- :COVR => nil,
168
- :CPIL => nil,
169
- :CPRT => nil,
170
- :DAY => 2004,
171
- :DISK => nil,
172
- :GNRE => 'Acid Jazz',
173
- :GRP => nil,
174
- :NAM => 'N™me',
175
- :TMPO => nil,
176
- :TOO => 'Helix Producer SDK 10.0 for Windows, Build 10.0.0.240',
177
- :TRKN => [1,0],
178
- :WRT => nil,
179
- :VERSION => 4,
180
- :LAYER => 1,
181
- :BITRATE => 93,
182
- :FREQUENCY => 1, # What part of "the sampling rate of the audio should be ... documented in the samplerate field" don't Real understand?
183
- :SIZE => 131682,
184
- :SECS => 11,
185
- :MM => 0,
186
- :SS => 11,
187
- :MS => 53,
188
- :TIME => '00:11',
189
- :COPYRIGHT => nil,
190
- :ENCRYPTED => nil
165
+ :APID => nil,
166
+ :ART => 'AÆtist',
167
+ :CMT => 'Comment',
168
+ :COVR => nil,
169
+ :CPIL => nil,
170
+ :CPRT => nil,
171
+ :DAY => 2004,
172
+ :DISK => nil,
173
+ :GNRE => 'Acid Jazz',
174
+ :GRP => nil,
175
+ :NAM => 'N™me',
176
+ :TMPO => nil,
177
+ :TOO => 'Helix Producer SDK 10.0 for Windows, Build 10.0.0.240',
178
+ :TRKN => [1,0],
179
+ :WRT => nil,
180
+ :VERSION => 4,
181
+ :LAYER => 1,
182
+ :BITRATE => 93,
183
+ :FREQUENCY => 1, # What part of "the sampling rate of the audio should be ... documented in the samplerate field" don't Real understand?
184
+ :SIZE => 131682,
185
+ :SECS => 11,
186
+ :MM => 0,
187
+ :SS => 11,
188
+ :MS => 53,
189
+ :TIME => '00:11',
190
+ :COPYRIGHT => nil,
191
+ :ENCRYPTED => nil
191
192
  }
192
193
 
193
194
  mp4 = MP4Info.open(file)
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.8.11
2
+ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: MP4Info
5
5
  version: !ruby/object:Gem::Version
6
- version: "0.2"
7
- date: 2006-05-23 00:00:00 -04:00
6
+ version: "0.3"
7
+ date: 2007-04-15 00:00:00 -04:00
8
8
  summary: MP4 tag reading library
9
9
  require_paths:
10
10
  - lib
@@ -25,6 +25,7 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
25
25
  platform: ruby
26
26
  signing_key:
27
27
  cert_chain:
28
+ post_install_message:
28
29
  authors:
29
30
  - Jason Terk
30
31
  files: