MP4Info 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 (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: