dmap 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +9 -0
- data/Rakefile +54 -0
- data/lib/dmap.rb +429 -0
- data/test/helper.rb +10 -0
- data/test/test_dmap.rb +0 -0
- metadata +73 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 JP Hastings-Spital
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
= dmap
|
2
|
+
|
3
|
+
I'm planning on building a Ruby library to parse and create DMAP bytestrings. Primarily in order to create a DAAP server in ruby, though if anyone has other interesting uses that would require changes to this code please don't hesitate to get in touch!
|
4
|
+
|
5
|
+
== What is dmap?
|
6
|
+
|
7
|
+
DMAP are a method of storing a variety of objects in a bytestring, its used all over the shot by Apple in iTunes, iPhoto and probably many other places. You can read up a little on the wikipedia (http://en.wikipedia.org/wiki/Digital_Audio_Access_Protocol) or read about the daap protocol (http://tapjam.net/daap/) to get the gist of it.
|
8
|
+
|
9
|
+
There's a big ol' list of the tags that are known on the deleet.de website (http://www.deleet.de/projekte/daap/?ContentCodes) but there are undoubtedly many more (I remember from an old project that similar file structure is used for QuickTime's mov containers and even for the mp4 container, so there may well be more there too). If you find any useful resources please get in touch!
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "dmap"
|
8
|
+
gem.version = "0.1.2"
|
9
|
+
gem.summary = %Q{Parses Apple's DMAP strings (4-byte serialization method)}
|
10
|
+
gem.description = %Q{Apple uses a system of serialization (I think its called dmap…) where a 4-byte string tells of the information following, both its type and what it represents. Its used in the DAAP (Protocol), QuickTime mov structure and doubtless many other places.}
|
11
|
+
gem.email = "jphastings@gmail.com"
|
12
|
+
gem.homepage = "http://github.com/jphastings/dmap"
|
13
|
+
gem.authors = ["JP Hastings-Spital"]
|
14
|
+
gem.add_development_dependency "Shoulda"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
Rake::TestTask.new(:test) do |test|
|
24
|
+
test.libs << 'lib' << 'test'
|
25
|
+
test.pattern = 'test/**/test_*.rb'
|
26
|
+
test.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'rcov/rcovtask'
|
31
|
+
Rcov::RcovTask.new do |test|
|
32
|
+
test.libs << 'test'
|
33
|
+
test.pattern = 'test/**/test_*.rb'
|
34
|
+
test.verbose = true
|
35
|
+
end
|
36
|
+
rescue LoadError
|
37
|
+
task :rcov do
|
38
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
task :test => :check_dependencies
|
43
|
+
|
44
|
+
task :default => :test
|
45
|
+
|
46
|
+
require 'rake/rdoctask'
|
47
|
+
Rake::RDocTask.new do |rdoc|
|
48
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
49
|
+
|
50
|
+
rdoc.rdoc_dir = 'rdoc'
|
51
|
+
rdoc.title = "dmap #{version}"
|
52
|
+
rdoc.rdoc_files.include('README*')
|
53
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
54
|
+
end
|
data/lib/dmap.rb
ADDED
@@ -0,0 +1,429 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'stringio'
|
3
|
+
require 'delegate'
|
4
|
+
|
5
|
+
# If you plan on extending this module (like you'll need to if you want extra tags available)
|
6
|
+
# make sure you don't use any class names with four letters in. It saves a bit of processor time
|
7
|
+
# if I don't have to remove
|
8
|
+
module DMAP
|
9
|
+
# Represents any DMAP tag and its content.
|
10
|
+
# Will have methods appropriate to the dmap specified
|
11
|
+
class Element
|
12
|
+
attr_reader :unparsed_content
|
13
|
+
attr_reader :name
|
14
|
+
attr_reader :real_class
|
15
|
+
|
16
|
+
# Accepts a string or an IO. The first four bytes (in either case) should be the
|
17
|
+
# tag you wish to make.
|
18
|
+
#
|
19
|
+
# If you have a big dmap file this is your lucky day, this class only
|
20
|
+
# processes the parts needed for any queries you're making, so its all on-the-fly.
|
21
|
+
#
|
22
|
+
# NB. if you specify `content` while passing a dmap tag you will overwrite anything
|
23
|
+
# that was in the dmap!
|
24
|
+
def initialize(tag_or_dmap,new_content = nil)
|
25
|
+
# Assume we have an IO object, if this fails then we probably have a string instead
|
26
|
+
begin
|
27
|
+
@tag = tag_or_dmap.read(4).upcase
|
28
|
+
@unparsed_content = true
|
29
|
+
@io = tag_or_dmap if new_content.nil?
|
30
|
+
rescue NoMethodError
|
31
|
+
@tag = tag_or_dmap[0..3].upcase
|
32
|
+
end
|
33
|
+
|
34
|
+
# Find out the details of this tag
|
35
|
+
begin
|
36
|
+
type,@name = DMAP.const_get(@tag)
|
37
|
+
rescue NameError
|
38
|
+
raise NameError, "I don't know how to interpret the tag '#{@tag}'. Please extend the DMAP module!"
|
39
|
+
end
|
40
|
+
|
41
|
+
self.send("parse_#{type}",new_content)
|
42
|
+
fudge = @real_class
|
43
|
+
eigenclass = class<<self; self; end
|
44
|
+
eigenclass.class_eval do
|
45
|
+
extend Forwardable
|
46
|
+
def_delegators :@real_class,*fudge.methods - ['__send__','__id__','to_dmap']
|
47
|
+
def inspect
|
48
|
+
"<#{@tag}: #{@real_class.inspect}>"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def close_stream
|
54
|
+
begin
|
55
|
+
@io.close
|
56
|
+
rescue NoMethodError
|
57
|
+
# There was no IO or its already been closed
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Adds the tag to a request to create the dmap
|
62
|
+
def to_dmap
|
63
|
+
"#{@tag.downcase}#{@real_class.to_dmap}"
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def parse_string(content)
|
69
|
+
begin
|
70
|
+
@real_class = String.new(content || @io.read(@io.read(4).unpack("N")[0])) # FIXME: Should be Q?
|
71
|
+
rescue NoMethodError
|
72
|
+
@real_class = String.new
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_list(content)
|
77
|
+
@real_class = Array.new(content || @io || [])
|
78
|
+
end
|
79
|
+
|
80
|
+
def parse_number(content,signed = false)
|
81
|
+
begin
|
82
|
+
if not content.nil? and content.is_a? Numeric
|
83
|
+
@real_class = MeasuredInteger.new(content,nil,signed)
|
84
|
+
elsif not content.nil? and (content[0].is_a? Numeric and content[1].is_a? Numeric)
|
85
|
+
@real_class = MeasuredInteger.new(content[0],content[1],signed)
|
86
|
+
else
|
87
|
+
box_size = @io.read(4).unpack("N")[0]
|
88
|
+
case box_size
|
89
|
+
when 1,2,4
|
90
|
+
num = @io.read(box_size).unpack(MeasuredInteger.pack_code(box_size,signed))[0]
|
91
|
+
when 8
|
92
|
+
num = @io.read(box_size).unpack("NN")
|
93
|
+
num = num[0]*65536 + num[1]
|
94
|
+
else
|
95
|
+
raise "I don't know how to unpack an integer #{box_size} bytes long"
|
96
|
+
end
|
97
|
+
@real_class = MeasuredInteger.new(num,box_size,signed)
|
98
|
+
end
|
99
|
+
rescue NoMethodError
|
100
|
+
@real_class = MeasuredInteger.new(0,0,signed)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# TODO
|
105
|
+
def parse_version(content)
|
106
|
+
begin
|
107
|
+
@real_class = Version.new(@io.read(@io.read(4).unpack("N")[0])) # FIXME: Should be Q?
|
108
|
+
rescue NoMethodError
|
109
|
+
@real_class = Version.new(0)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def parse_time(content)
|
114
|
+
begin
|
115
|
+
@real_class = Time.at(@io.read(@io.read(4).unpack("N")[0]).unpack("N")[0])
|
116
|
+
rescue NoMethodError
|
117
|
+
@real_class = Time.now
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def parse_signed(content)
|
122
|
+
parse_number(content,true)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# We may not always want to parse an entire DMAP in one go, here we extend Array so that
|
127
|
+
# we can hold the reference to the io and the point at which the data starts, so we can parse
|
128
|
+
# it later if the contents are requested
|
129
|
+
class Array < Array
|
130
|
+
attr_reader :unparsed_data
|
131
|
+
attr_accessor :parse_immediately
|
132
|
+
@@parse_immediately = false
|
133
|
+
|
134
|
+
def self.parse_immediately
|
135
|
+
@@parse_immediately
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.parse_immediately=(bool)
|
139
|
+
@@parse_immediately = (bool == true)
|
140
|
+
end
|
141
|
+
|
142
|
+
alias :original_new :initialize
|
143
|
+
def initialize(array_or_io)
|
144
|
+
original_new
|
145
|
+
begin
|
146
|
+
# Lets assume its an io
|
147
|
+
@dmap_length = array_or_io.read(4).unpack("N")[0] # FIXME: Should be Q? but that's not working?
|
148
|
+
@dmap_io = array_or_io
|
149
|
+
@dmap_start = @dmap_io.tell
|
150
|
+
@unparsed_data = true
|
151
|
+
parse_dmap if @@parse_immediately
|
152
|
+
rescue NoMethodError
|
153
|
+
begin
|
154
|
+
array_or_io.each do |element|
|
155
|
+
self.push element
|
156
|
+
end
|
157
|
+
rescue NoMethodError
|
158
|
+
end
|
159
|
+
@unparsed_data = false
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
[:==, :===, :=~, :clone, :display, :dup, :enum_for, :eql?, :equal?, :hash, :to_a, :to_enum, :each, :length].each do |method_name|
|
165
|
+
original = self.instance_method(method_name)
|
166
|
+
define_method(method_name) do
|
167
|
+
self.parse_dmap
|
168
|
+
original.bind(self).call
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def to_dmap
|
173
|
+
out = "\000\000\000\000"
|
174
|
+
(0...self.length).to_a.each do |n|
|
175
|
+
out << self[n].to_dmap
|
176
|
+
end
|
177
|
+
|
178
|
+
out[0..3] = [out.length - 4].pack("N")
|
179
|
+
out
|
180
|
+
end
|
181
|
+
|
182
|
+
def inspect
|
183
|
+
if not @unparsed_data
|
184
|
+
super
|
185
|
+
else
|
186
|
+
"Some unparsed DMAP elements"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Parse any unparsed dmap data stored, and add the elements to the array
|
191
|
+
def parse_dmap
|
192
|
+
return if not @unparsed_data
|
193
|
+
|
194
|
+
# Remember the position of the IO head so we can put it back later
|
195
|
+
io_position = @dmap_io.tell
|
196
|
+
|
197
|
+
# Go to the begining of the list
|
198
|
+
@dmap_io.seek(@dmap_start)
|
199
|
+
# Enumerate all tags in this list
|
200
|
+
while @dmap_io.tell < (@dmap_start + @dmap_length)
|
201
|
+
self.push Element.new(@dmap_io)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Return the IO head to where it was
|
205
|
+
@dmap_io.seek(io_position)
|
206
|
+
@unparsed_data = false
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Allows people to specify a integer and the size of the binary representation required
|
211
|
+
class MeasuredInteger
|
212
|
+
attr_reader :value
|
213
|
+
attr_accessor :box_size, :binary, :signed
|
214
|
+
|
215
|
+
def initialize(value,wanted_box_size = nil,signed = false)
|
216
|
+
@value = value
|
217
|
+
@binary = (box_size == 1)
|
218
|
+
self.box_size = wanted_box_size
|
219
|
+
@signed = signed
|
220
|
+
end
|
221
|
+
|
222
|
+
# Will set the box size to the largest value of the one you specify and the maximum needed for the
|
223
|
+
# current value.
|
224
|
+
def box_size=(wanted_box_size)
|
225
|
+
# Find the smallest number of bytes needed to express this number
|
226
|
+
@box_size = [wanted_box_size || 0,(Math.log(@value) / 2.07944154167984).ceil].max rescue 0 # For when value = 0
|
227
|
+
end
|
228
|
+
|
229
|
+
def to_dmap
|
230
|
+
case @box_size
|
231
|
+
when 1,2,4
|
232
|
+
[@box_size,@value].pack("N"<<MeasuredInteger.pack_code(@box_size,@signed))
|
233
|
+
when 8
|
234
|
+
[@box_size,@value / 65536,@value % 65536].pack((@signed) ? "Nll" : "NNN") # FIXME: How do you do signed version :S
|
235
|
+
else
|
236
|
+
raise "I don't know how to unpack an integer #{@box_size} bytes long"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def self.pack_code(length,signed)
|
241
|
+
out = {1=>"C",2=>"S",4=>"N"}[length]
|
242
|
+
out.downcase if signed
|
243
|
+
return out
|
244
|
+
end
|
245
|
+
|
246
|
+
def inspect
|
247
|
+
# This is a bit of a guess, no change to the data, just helps inspection
|
248
|
+
return (@value == 1) ? "true" : "false" if @binary
|
249
|
+
@value
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# A class to store version numbers
|
254
|
+
class Version
|
255
|
+
# TODO
|
256
|
+
def initialize(version)
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
def inspect
|
261
|
+
"Some kinda version"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# For adding String#to_dmap
|
266
|
+
class String < String
|
267
|
+
def to_dmap
|
268
|
+
return [self.length % 65536].pack("N") + self.to_s
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# For adding Time#to_dmap
|
273
|
+
class Time < Time
|
274
|
+
def to_dmap
|
275
|
+
"\000\000\000\004"<<[self.to_i].pack("N")
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
MLIT = [:list, 'dmap.listingitem']
|
280
|
+
ASSL = [:string, 'daap.sortalbumartist']
|
281
|
+
ASBO = [:number, 'daap.songbookmark']
|
282
|
+
AEGU = [:number, 'com.apple.itunes.gapless-dur']
|
283
|
+
MSRV = [:list, 'dmap.serverinforesponse']
|
284
|
+
AESN = [:string, 'com.apple.itunes.series-name']
|
285
|
+
ASDR = [:time, 'daap.songdatereleased']
|
286
|
+
MPER = [:number, 'dmap.persistentid']
|
287
|
+
ASSR = [:number, 'daap.songsamplerate']
|
288
|
+
ASBT = [:number, 'daap.songbeatsperminute']
|
289
|
+
AEGE = [:number, 'com.apple.itunes.gapless-enc-del']
|
290
|
+
MSTO = [:signed, 'dmap.utcoffset']
|
291
|
+
ABAR = [:list, 'daap.browseartistlisting']
|
292
|
+
MCNA = [:string, 'dmap.contentcodesname']
|
293
|
+
ASEQ = [:string, 'daap.songeqpreset']
|
294
|
+
AGRP = [:string, 'daap.songgrouping']
|
295
|
+
MSAU = [:number, 'dmap.authenticationmethod']
|
296
|
+
ASCN = [:string, 'daap.songcontentdescription']
|
297
|
+
AEGR = [:number, 'com.apple.itunes.gapless-resy']
|
298
|
+
ASSZ = [:number, 'daap.songsize']
|
299
|
+
MSTT = [:number, 'dmap.status']
|
300
|
+
MCTC = [:number, 'dmap.containercount']
|
301
|
+
ASGP = [:number, 'daap.songgapless']
|
302
|
+
APRO = [:version,'daap.protocolversion']
|
303
|
+
ABPL = [:number, 'daap.baseplaylist']
|
304
|
+
MSBR = [:number, 'dmap.supportsbrowse']
|
305
|
+
ASTN = [:number, 'daap.songtracknumber']
|
306
|
+
ASCR = [:number, 'daap.songcontentrating']
|
307
|
+
AEMK = [:number, 'com.apple.itunes.mediakind']
|
308
|
+
MUDL = [:list, 'dmap.deletedidlisting']
|
309
|
+
APSO = [:list, 'daap.playlistsongs']
|
310
|
+
ADBS = [:list, 'daap.databasesongs']
|
311
|
+
MDCL = [:list, 'dmap.dictionary']
|
312
|
+
ASLC = [:string, 'daap.songlongcontentdescription']
|
313
|
+
MSEX = [:number, 'dmap.supportsextensions']
|
314
|
+
ASYR = [:number, 'daap.songyear']
|
315
|
+
ASDA = [:time, 'daap.songdateadded']
|
316
|
+
AEPC = [:number, 'com.apple.itunes.is-podcast']
|
317
|
+
MUTY = [:number, 'dmap.updatetype']
|
318
|
+
MIKD = [:number, 'dmap.itemkind']
|
319
|
+
ASRV = [:signed, 'daap.songrelativevolume']
|
320
|
+
ASAA = [:string, 'daap.songalbumartist']
|
321
|
+
AECI = [:number, 'com.apple.itunes.itms-composerid']
|
322
|
+
MSMA = [:number, 'unknown_msma']
|
323
|
+
AEPS = [:number, 'com.apple.itunes.special-playlist']
|
324
|
+
CEJC = [:signed, 'com.apple.itunes.jukebox-client-vote']
|
325
|
+
ASDC = [:number, 'daap.songdisccount']
|
326
|
+
MLCL = [:list, 'dmap.listing']
|
327
|
+
ASSA = [:string, 'daap.sortartist']
|
328
|
+
ASAR = [:string, 'daap.songartist']
|
329
|
+
AEES = [:number, 'com.apple.itunes.episode-sort']
|
330
|
+
MSQY = [:number, 'dmap.supportsquery']
|
331
|
+
ASDN = [:number, 'daap.songdiscnumber']
|
332
|
+
AESG = [:number, 'com.apple.itunes.saved-genius']
|
333
|
+
MLOG = [:list, 'dmap.loginresponse']
|
334
|
+
ASSN = [:string, 'daap.sortname']
|
335
|
+
ASBR = [:number, 'daap.songbitrate']
|
336
|
+
MSTC = [:time, 'dmap.utctime']
|
337
|
+
ASDT = [:string, 'daap.songdescription']
|
338
|
+
AESP = [:number, 'com.apple.itunes.smart-playlist']
|
339
|
+
ABAL = [:list, 'daap.browsealbumlisting']
|
340
|
+
MBCL = [:list, 'dmap.bag']
|
341
|
+
MPRO = [:version,'dmap.protocolversion']
|
342
|
+
ASSS = [:string, 'daap.sortseriesname']
|
343
|
+
ASCD = [:number, 'daap.songcodectype']
|
344
|
+
AEGH = [:number, 'com.apple.itunes.gapless-heur']
|
345
|
+
APLY = [:list, 'daap.databaseplaylists']
|
346
|
+
ABCP = [:list, 'daap.browsecomposerlisting']
|
347
|
+
MCNM = [:number, 'dmap.contentcodesnumber']
|
348
|
+
ASFM = [:string, 'daap.songformat']
|
349
|
+
MSAL = [:number, 'dmap.supportsautologout']
|
350
|
+
ASTC = [:number, 'daap.songtrackcount']
|
351
|
+
ASCO = [:number, 'daap.songcompilation']
|
352
|
+
AEHD = [:number, 'com.apple.itunes.is-hd-video']
|
353
|
+
MSUP = [:number, 'dmap.supportsupdate']
|
354
|
+
MCTI = [:number, 'dmap.containeritemid']
|
355
|
+
ASHP = [:number, 'daap.songhasbeenplayed']
|
356
|
+
MSDC = [:number, 'dmap.databasescount']
|
357
|
+
AENN = [:string, 'com.apple.itunes.network-name']
|
358
|
+
ASUL = [:string, 'daap.songdataurl']
|
359
|
+
ASCS = [:number, 'daap.songcodecsubtype']
|
360
|
+
MUPD = [:list, 'dmap.updateresponse']
|
361
|
+
ASLS = [:number, 'daap.songlongsize']
|
362
|
+
ARIF = [:list, 'daap.resolveinfo']
|
363
|
+
AEAI = [:number, 'com.apple.itunes.itms-artistid']
|
364
|
+
MEDS = [:number, 'dmap.editcommandssupported']
|
365
|
+
#F�CH = [:number, 'dmap.haschildcontainers']
|
366
|
+
MSIX = [:number, 'dmap.supportsindex']
|
367
|
+
ATED = [:number, 'daap.supportsextradata']
|
368
|
+
AEPI = [:number, 'com.apple.itunes.itms-playlistid']
|
369
|
+
MIMC = [:number, 'dmap.itemcount']
|
370
|
+
AECR = [:string, 'com.apple.itunes.content-rating']
|
371
|
+
ASAI = [:number, 'daap.songalbumid']
|
372
|
+
MSML = [:list, 'unknown_msml']
|
373
|
+
ASDK = [:number, 'daap.songdatakind']
|
374
|
+
AESU = [:number, 'com.apple.itunes.season-num']
|
375
|
+
CEJI = [:number, 'com.apple.itunes.jukebox-current']
|
376
|
+
MLID = [:number, 'dmap.sessionid']
|
377
|
+
ASSC = [:string, 'daap.sortcomposer']
|
378
|
+
ASBK = [:number, 'daap.bookmarkable']
|
379
|
+
AEFP = [:number, 'com.apple.itunes.req-fplay']
|
380
|
+
MSRS = [:number, 'dmap.supportsresolve']
|
381
|
+
CEJV = [:number, 'com.apple.itunes.jukebox-vote']
|
382
|
+
ASDP = [:time, 'daap.songdatepurchased']
|
383
|
+
AESI = [:number, 'com.apple.itunes.itms-songid']
|
384
|
+
MPCO = [:number, 'dmap.parentcontainerid']
|
385
|
+
AEGD = [:number, 'com.apple.itunes.gapless-enc-dr']
|
386
|
+
ASSP = [:number, 'daap.songstoptime']
|
387
|
+
MSTM = [:number, 'dmap.timeoutinterval']
|
388
|
+
MCCR = [:list, 'dmap.contentcodesresponse']
|
389
|
+
ASED = [:number, 'daap.songextradata']
|
390
|
+
AESV = [:number, 'com.apple.itunes.music-sharing-version']
|
391
|
+
MRCO = [:number, 'dmap.returnedcount']
|
392
|
+
AEGI = [:number, 'com.apple.itunes.itms-genreid']
|
393
|
+
ASST = [:number, 'daap.songstarttime']
|
394
|
+
ASCM = [:string, 'daap.songcomment']
|
395
|
+
MSTS = [:string, 'dmap.statusstring']
|
396
|
+
ASGN = [:string, 'daap.songgenre']
|
397
|
+
APRM = [:number, 'daap.playlistrepeatmode']
|
398
|
+
ABGN = [:list, 'daap.browsegenrelisting']
|
399
|
+
MCON = [:list, 'dmap.container']
|
400
|
+
MSAS = [:number, 'dmap.authenticationschemes']
|
401
|
+
ASTM = [:number, 'daap.songtime']
|
402
|
+
ASCP = [:string, 'daap.songcomposer']
|
403
|
+
AEHV = [:number, 'com.apple.itunes.has-video']
|
404
|
+
MTCO = [:number, 'dmap.specifiedtotalcount']
|
405
|
+
ABRO = [:list, 'daap.databasebrowse']
|
406
|
+
MCTY = [:number, 'dmap.contentcodestype']
|
407
|
+
ASKY = [:string, 'daap.songkeywords']
|
408
|
+
APSM = [:number, 'daap.playlistshufflemode']
|
409
|
+
MSED = [:number, 'unknown_msed']
|
410
|
+
ASCT = [:string, 'daap.songcategory']
|
411
|
+
AENV = [:number, 'com.apple.itunes.norm-volume']
|
412
|
+
ASUR = [:number, 'daap.songuserrating']
|
413
|
+
MUSR = [:number, 'dmap.serverrevision']
|
414
|
+
MIID = [:number, 'dmap.itemid']
|
415
|
+
ASPU = [:string, 'daap.songpodcasturl']
|
416
|
+
ARSV = [:list, 'daap.resolve']
|
417
|
+
MSLR = [:number, 'dmap.loginrequired']
|
418
|
+
AVDB = [:list, 'daap.serverdatabases']
|
419
|
+
ASDB = [:number, 'daap.songdisabled']
|
420
|
+
AEPP = [:number, 'com.apple.itunes.is-podcast-playlist']
|
421
|
+
MINM = [:string, 'dmap.itemname']
|
422
|
+
ASAL = [:string, 'daap.songalbum']
|
423
|
+
AEEN = [:string, 'com.apple.itunes.episode-num-str']
|
424
|
+
ASSU = [:string, 'daap.sortalbum']
|
425
|
+
MSPI = [:number, 'dmap.supportspersistentids']
|
426
|
+
CEJS = [:signed, 'com.apple.itunes.jukebox-score']
|
427
|
+
ASDM = [:time, 'daap.songdatemodified']
|
428
|
+
AESF = [:number, 'com.apple.itunes.itms-storefrontid']
|
429
|
+
end
|
data/test/helper.rb
ADDED
data/test/test_dmap.rb
ADDED
Binary file
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dmap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- JP Hastings-Spital
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-06 00:00:00 +00:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: Shoulda
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: "Apple uses a system of serialization (I think its called dmap\xE2\x80\xA6) where a 4-byte string tells of the information following, both its type and what it represents. Its used in the DAAP (Protocol), QuickTime mov structure and doubtless many other places."
|
26
|
+
email: jphastings@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.rdoc
|
34
|
+
files:
|
35
|
+
- .document
|
36
|
+
- .gitignore
|
37
|
+
- LICENSE
|
38
|
+
- README.rdoc
|
39
|
+
- Rakefile
|
40
|
+
- lib/dmap.rb
|
41
|
+
- test/helper.rb
|
42
|
+
- test/test_dmap.rb
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://github.com/jphastings/dmap
|
45
|
+
licenses: []
|
46
|
+
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options:
|
49
|
+
- --charset=UTF-8
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
version:
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.3.5
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Parses Apple's DMAP strings (4-byte serialization method)
|
71
|
+
test_files:
|
72
|
+
- test/helper.rb
|
73
|
+
- test/test_dmap.rb
|