~ubuntu-branches/ubuntu/utopic/gnupod-tools/utopic

« back to all changes in this revision

Viewing changes to src/ext/iTunesDB.pm

  • Committer: Bazaar Package Importer
  • Author(s): Quôc Peyrot
  • Date: 2004-02-01 00:32:51 UTC
  • Revision ID: james.westby@ubuntu.com-20040201003251-n61x4ub9f9hnnupy
Tags: upstream-0.94rc1
Import upstream version 0.94rc1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# iTunesDB.pm - Version 20040116
 
2
#  Copyright (C) 2002-2004 Adrian Ulrich <pab at blinkenlights.ch>
 
3
#  Part of the gnupod-tools collection
 
4
#
 
5
#  URL: http://blinkenlights.ch/cgi-bin/fm.pl?get=ipod
 
6
#
 
7
#    This program is free software; you can redistribute it and/or modify
 
8
#    it under the terms of the GNU General Public License as published by
 
9
#    the Free Software Foundation; either version 2 of the License, or
 
10
#    (at your option) any later version.
 
11
#
 
12
#    This program is distributed in the hope that it will be useful,
 
13
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
#    GNU General Public License for more details.
 
16
#
 
17
#    You should have received a copy of the GNU General Public License
 
18
#    along with this program; if not, write to the Free Software
 
19
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
20
#
 
21
# iTunes and iPod are trademarks of Apple
 
22
#
 
23
# This product is not supported/written/published by Apple!
 
24
 
 
25
package GNUpod::iTunesDB;
 
26
use strict;
 
27
use Unicode::String;
 
28
use GNUpod::FooBar;
 
29
 
 
30
use vars qw(%mhod_id @mhod_array);
 
31
 
 
32
#mk_mhod() will take care of lc() entries
 
33
%mhod_id = ("title", 1, "path", 2, "album", 3, "artist", 4, "genre", 5, "fdesc", 6, "eq", 7, "comment", 8, "composer", 12);# "SPLPREF",50, "SPLDATA",51, "PLTHING", 100) ;
 
34
 foreach(keys(%mhod_id)) {
 
35
  $mhod_array[$mhod_id{$_}] = $_;
 
36
 }
 
37
 
 
38
## GENERAL #########################################################
 
39
# create an iTunesDB header
 
40
sub mk_mhbd
 
41
{
 
42
my ($hr) = @_;
 
43
 
 
44
my $ret = "mhbd";
 
45
   $ret .= pack("h8", _itop(104));                  #Header Size
 
46
   $ret .= pack("h8", _itop($hr->{size}+104));       #size of the whole mhdb
 
47
   $ret .= pack("H8", "01");                       #?
 
48
   $ret .= pack("H8", "01");                       #? - changed to 2 from itunes2 to 3 .. version? We are iTunes version 1 ;)
 
49
   $ret .= pack("H8", "02");                       #?
 
50
   $ret .= pack("H160", "00");                     #dummy space
 
51
return $ret;
 
52
}
 
53
 
 
54
## GENERAL #########################################################
 
55
# a iTunesDB has 2 mhsd's: (This is a child of mk_mhbd)
 
56
# mhsd1 holds every song on the ipod
 
57
# mhsd2 holds playlists
 
58
sub mk_mhsd
 
59
{
 
60
my ($hr) = @_;
 
61
 
 
62
my $ret = "mhsd";
 
63
   $ret .= pack("h8", _itop(96));                      #Headersize, static
 
64
   $ret .= pack("h8", _itop($hr->{size}+96));               #Size
 
65
   $ret .= pack("h8", _itop($hr->{type}));                   #type .. 1 = song .. 2 = playlist
 
66
   $ret .= pack("H160", "00");                        #dummy space
 
67
return $ret;
 
68
}
 
69
 
 
70
## GENERAL ##########################################################
 
71
# Create an mhit entry, needs to know about the length of his
 
72
# mhod(s) (You have to create them yourself..!)
 
73
sub mk_mhit {
 
74
my($hr) = @_;
 
75
my %file_hash = %{$hr->{fh}};
 
76
 
 
77
#We have to fix 'volume'
 
78
my $vol = sprintf("%.0f",( int($file_hash{volume})*2.55 ));
 
79
 
 
80
if($vol >= 0 && $vol <= 255) { } #Nothing to do
 
81
elsif($vol < 0 && $vol >= -255) {            #Convert value
 
82
 $vol = oct("0xFFFFFFFF") + $vol; 
 
83
}
 
84
else {
 
85
 print STDERR "** Warning: ID $file_hash{id} has volume set to $file_hash{volume} percent. Volume set to +-0%\n";
 
86
 $vol = 0; #We won't nuke the iPod with an ultra high volume setting..
 
87
}
 
88
 
 
89
foreach( ("rating", "prerating") ) {
 
90
 if($file_hash{$_} && $file_hash{$_} !~ /^(2|4|6|8|10)0$/) {
 
91
  print STDERR "Warning: Song $file_hash{id} has an invalid $_: $file_hash{$_}\n";
 
92
  $file_hash{$_} = 0;
 
93
 }
 
94
}
 
95
 
 
96
 
 
97
#Check for stupid input
 
98
my ($c_id) = $file_hash{id} =~ /(\d+)/;
 
99
if($c_id < 1) {
 
100
  print STDERR "Warning: ID has can't be $c_id, has to be > 0\n";
 
101
  print STDERR "         This song *won't* be visible on the iPod\n";
 
102
}
 
103
 
 
104
my $ret = "mhit";
 
105
   $ret .= pack("h8", _itop(156));                           #header size
 
106
   $ret .= pack("h8", _itop(int($hr->{size})+156));           #len of this entry
 
107
   $ret .= pack("h8", _itop($hr->{count}));                     #num of mhods in this mhit
 
108
   $ret .= pack("h8", _itop($c_id));                 #Song index number
 
109
   $ret .= pack("h8", _itop(1));                             #?
 
110
   $ret .= pack("H8");                                      #dummyspace
 
111
   $ret .= pack("h8", _itop(256+(oct('0x14000000')
 
112
                            *($file_hash{rating}/20))));     #type+rating .. this is very STUPID..
 
113
   $ret .= pack("h8", _mactime());                           #timestamp (we create a dummy timestamp, iTunes doesn't seem to make use of this..?!)
 
114
   $ret .= pack("h8", _itop($file_hash{filesize}));          #filesize
 
115
   $ret .= pack("h8", _itop($file_hash{time}));              #seconds of song
 
116
   $ret .= pack("h8", _itop($file_hash{songnum}));           #nr. on CD .. we dunno use it (in this version)
 
117
   $ret .= pack("h8", _itop($file_hash{songs}));             #songs on this CD
 
118
   $ret .= pack("h8", _itop($file_hash{year}));              #the year
 
119
   $ret .= pack("h8", _itop($file_hash{bitrate}));           #bitrate
 
120
   $ret .= pack("H4", "00");                                #??
 
121
   $ret .= pack("h4", _itop( ($file_hash{srate} || 44100),0xffff));    #Srate (note: h4!)
 
122
   $ret .= pack("h8", _itop($vol));                         #Volume
 
123
   $ret .= pack("h8", _itop($file_hash{starttime}));        #Start time?
 
124
   $ret .= pack("h8", _itop($file_hash{stoptime}));          #Stop time?
 
125
   $ret .= pack("H8");
 
126
   $ret .= pack("h8", _itop($file_hash{playcount}));
 
127
   $ret .= pack("H8");                                      #Sometimes eq playcount .. ?!
 
128
   $ret .= pack("h8");                                      #Last playtime.. FIXME
 
129
   $ret .= pack("h8", _itop($file_hash{cdnum}));            #cd number
 
130
   $ret .= pack("h8", _itop($file_hash{cds}));              #number of cds
 
131
   $ret .= pack("H8");                                      #hardcoded space 
 
132
   $ret .= pack("h8", _mactime());                          #dummy timestamp again...
 
133
   $ret .= pack("H16");
 
134
   $ret .= pack("H8");                          #??
 
135
   $ret .= pack("h8", _itop(($file_hash{prerating}/20)*oct('0x140000')));      #This is also stupid: the iTunesDB has a rating history
 
136
   $ret .= pack("H8");                          # ???
 
137
   $ret .= pack("H56");  
 
138
                         
 
139
return $ret;
 
140
}
 
141
 
 
142
 
 
143
## GENERAL ##########################################################
 
144
# An mhod simply holds information
 
145
sub mk_mhod
 
146
{
 
147
##   - type id
 
148
#1   - titel
 
149
#2   - ipod filename
 
150
#3   - album
 
151
#4   - interpret
 
152
#5   - genre
 
153
#6   - filetype
 
154
#7   - EQ Setting
 
155
#8   - comment
 
156
#12  - composer
 
157
#100 - Playlist item or/and PlaylistLayout (used for trash? ;))
 
158
 
 
159
my ($hr) = @_;
 
160
my $type_string = $hr->{stype};
 
161
my $string = $hr->{string};
 
162
my $fqid = $hr->{fqid};
 
163
my $type = $mhod_id{lc($type_string)};
 
164
 
 
165
#Appnd size for normal mhod's
 
166
my $mod = 40;
 
167
 
 
168
#Called with fqid, this has to be an PLTHING (100)
 
169
if($fqid) { 
 
170
 #fqid set, that's a pl item!
 
171
 $type = 100;
 
172
 #Playlist mhods are longer
 
173
 $mod += 4;
 
174
}
 
175
elsif(!$type) { #No type and no fqid, skip it
 
176
 return undef;
 
177
}
 
178
else { #has a type, default fqid
 
179
 $fqid=1;
 
180
}
 
181
 
 
182
if($type == 7 && $string !~ /#!#\d+#!#/) {
 
183
warn "iTunesDB.pm: warning: wrong format: '$type_string=\"$string\"'\n";
 
184
warn "             value should be like '#!#NUMBER#!#', ignoring value\n";
 
185
$string = undef;
 
186
}
 
187
 
 
188
$string = _ipod_string($string); #cache data
 
189
my $ret = "mhod";                                          #header
 
190
$ret .= pack("h8", _itop(24));                     #size of header
 
191
$ret .= pack("h8", _itop(length($string)+$mod));   # size of header+body
 
192
$ret .= pack("h8", _itop("$type"));                #type of the entry
 
193
$ret .= pack("H16");                               #dummy space
 
194
$ret .= pack("h8", _itop($fqid));                  #Refers to this id if a PL item
 
195
                                                   #else ->  1
 
196
$ret .= pack("h8", _itop(length($string)));        #size of string
 
197
 
 
198
 
 
199
if($type != 100){ #no PL mhod
 
200
 $ret .= pack("h16");           #trash
 
201
 $ret .= $string;               #the string
 
202
}
 
203
else { #PL mhod
 
204
 $ret .= pack("h24"); #playlist mhods are a different
 
205
}
 
206
return $ret;
 
207
}
 
208
 
 
209
 
 
210
## GENERAL #################################################################
 
211
# Create a spl-pref (type=50) mhod
 
212
sub mk_splprefmhod {
 
213
 my($hs) = @_;
 
214
 my($live, $chkrgx, $chklim, $mos) = 0;
 
215
 #Bool stuff
 
216
 $live        = 1 if $hs->{liveupdate};
 
217
my $checkrule   = int($hs->{checkrule});
 
218
 $mos         = 1 if $hs->{mos};
 
219
 
 
220
if($checkrule < 1 || $checkrule > 3) {
 
221
 warn "iTunesDB.pm: error: 'checkrule' ($checkrule) out of range. value set to 1 (=LimitMatch)\n";
 
222
 $checkrule = 1;
 
223
}
 
224
 
 
225
$chkrgx = 1 if $checkrule>1;
 
226
$chklim = $checkrule-$chkrgx*2;
 
227
#lim-only = 1 / match only = 2 / both = 3
 
228
 
 
229
 my $ret = "mhod";
 
230
 $ret .= pack("h8", _itop(24));    #Size of header
 
231
 $ret .= pack("h8", _itop(96));
 
232
 $ret .= pack("h8", _itop(50));
 
233
 $ret .= pack("H16");
 
234
 $ret .= pack("h2", _itop($live,0xff)); #LiveUpdate ?
 
235
 $ret .= pack("h2", _itop($chkrgx,0xff)); #Check regexps?
 
236
 $ret .= pack("h2", _itop($chklim,0xff)); #Check limits?
 
237
 $ret .= pack("h2", _itop($hs->{item},0xff)); #Wich item?
 
238
 $ret .= pack("h2", _itop($hs->{sort},0xff)); #How to sort
 
239
 $ret .= pack("h6");
 
240
 $ret .= pack("h8", _itop($hs->{value})); #lval
 
241
 $ret .= pack("h2", _itop($mos,0xff));        #mos
 
242
 $ret .= pack("h118");
 
243
}
 
244
 
 
245
## GENERAL #################################################################
 
246
# Create a spl-data (type=51) mhod
 
247
sub mk_spldatamhod {
 
248
 my($hs) = @_;
 
249
 
 
250
 my $anymatch = 1 if $hs->{anymatch};
 
251
 
 
252
if(ref($hs->{data}) ne "ARRAY") {
 
253
 warn "iTunesDB.pm: warning: no spldata found in spl, iTunes4-workaround enabled\n";
 
254
 push(@{$hs->{data}}, {field=>4,action=>2,string=>""});
 
255
}
 
256
 
 
257
 my $cr = undef;
 
258
 foreach my $chr (@{$hs->{data}}) {
 
259
     my $string = undef;
 
260
#Fixme: this is ugly (same as read_spldata)
 
261
     if($chr->{field} =~ /^(2|3|4|8|9|14|18)$/) {
 
262
        $string = Unicode::String::utf8($chr->{string})->utf16;
 
263
     }
 
264
     else {
 
265
        my ($from, $to) = $chr->{string} =~ /(\d+):?(\d*)/;
 
266
        $to ||=$from;
 
267
        $string  = pack("H8");
 
268
        $string .= pack("H8", _x86itop($from));
 
269
        $string .= pack("H24");
 
270
        $string .= pack("H8", _x86itop(1));
 
271
        $string .= pack("H8");
 
272
        $string .= pack("H8", _x86itop($to));
 
273
        $string .= pack("H24");
 
274
        $string .= pack("H8", _x86itop(1));
 
275
        $string .= pack("H40");
 
276
      #  __hd($string);
 
277
     }
 
278
 
 
279
     if(length($string) > 254) { #length field is limited to 0xfe!
 
280
        warn "iTunesDB.pm: splstring to long for iTunes, cropping\n";
 
281
        $string = substr($string,0,254);
 
282
     }
 
283
     
 
284
     $cr .= pack("H6");
 
285
     $cr .= pack("h2", _itop($chr->{field},0xff));
 
286
     $cr .= pack("H6", reverse("010000"));
 
287
     $cr .= pack("h2", _itop($chr->{action},0xff));
 
288
     $cr .= pack("H94");
 
289
     $cr .= pack("h2", _itop(length($string),0xff));
 
290
     $cr .= $string;
 
291
 }
 
292
 
 
293
 my $ret = "mhod";
 
294
 $ret .= pack("h8", _itop(24));    #Size of header
 
295
 $ret .= pack("h8", _itop(length($cr)+160));    #header+body size
 
296
 $ret .= pack("h8", _itop(51));    #type
 
297
 $ret .= pack("H16");
 
298
 $ret .= "SLst";                   #Magic
 
299
 $ret .= pack("H8", reverse("00010001")); #?
 
300
 $ret .= pack("h6");
 
301
 $ret .= pack("h2", _itop(int(@{$hs->{data}}),0xff));     #HTM (Childs from cr)
 
302
 $ret .= pack("h6");
 
303
 $ret .= pack("h2", _itop($anymatch,0xff));     #anymatch rule on or off
 
304
 $ret .= pack("h240");
 
305
 
 
306
 
 
307
 $ret .= $cr;
 
308
return $ret;
 
309
}
 
310
 
 
311
 
 
312
 
 
313
 
 
314
 
 
315
## FILES #########################################################
 
316
# header for all files (like you use mk_mhlp for playlists)
 
317
sub mk_mhlt
 
318
{
 
319
my ($hr) = @_;
 
320
 
 
321
my $ret = "mhlt";
 
322
   $ret .= pack("h8", _itop(92));                   #Header size (static)
 
323
   $ret .= pack("h8", _itop($hr->{songs})); #songs in this itunesdb
 
324
   $ret .= pack("H160", "00");                      #dummy space
 
325
return $ret;
 
326
}
 
327
 
 
328
 
 
329
 
 
330
 
 
331
 
 
332
 
 
333
 
 
334
 
 
335
 
 
336
## PLAYLIST #######################################################
 
337
# header for ALL playlists
 
338
sub mk_mhlp
 
339
{
 
340
 
 
341
my ($hr) = @_;
 
342
 
 
343
my $ret = "mhlp";
 
344
   $ret .= pack("h8", _itop(92));                   #Static header size
 
345
   $ret .= pack("h8", _itop($hr->{playlists}));          #playlists on iPod (including main!)
 
346
   $ret .= pack("h160", "00");                     #dummy space
 
347
return $ret;
 
348
}
 
349
 
 
350
 
 
351
## PLAYLIST ######################################################
 
352
# Creates an header for a new playlist (child of mk_mhlp)
 
353
sub mk_mhyp
 
354
{
 
355
my($hr) = @_;
 
356
 
 
357
#We need to create a listview-layout and an mhod with the name..
 
358
my $appnd = mk_mhod({stype=>"title", string=>$hr->{name}}).__dummy_listview();   #itunes prefs for this PL & PL name (default PL has  device name as PL name)
 
359
 
 
360
##Child mhods calc..
 
361
##We create 2 mhod's here.. mktunes may have created more mhods.. so we
 
362
##have to adjust the childs here
 
363
my $cmh = 2+$hr->{mhods};
 
364
 
 
365
my $ret .= "mhyp";
 
366
   $ret .= pack("h8", _itop(108)); #type
 
367
   $ret .= pack("h8", _itop($hr->{size}+108+(length($appnd))));          #size
 
368
   $ret .= pack("h8", _itop($cmh));                           #mhods
 
369
   $ret .= pack("h8", _itop($hr->{files}));   #songs in pl
 
370
   $ret .= pack("h8", _itop($hr->{type}));    # 1 = main .. 0=not main
 
371
   $ret .= pack("H8", "00");                          #?
 
372
   $ret .= pack("H8", "00");                  #?
 
373
   $ret .= pack("H8", "00");                  #?
 
374
   $ret .= pack("H144", "00");                    #dummy space
 
375
 
 
376
 return $ret.$appnd;
 
377
}
 
378
 
 
379
 
 
380
## PLAYLIST ##################################################
 
381
# header for new Playlist item (child if mk_mhyp)
 
382
sub mk_mhip
 
383
 {
 
384
my ($hr) = @_;
 
385
#sid = SongId
 
386
#plid = playlist order ID
 
387
my $ret = "mhip";
 
388
   $ret .= pack("h8", _itop(76));
 
389
   $ret .= pack("h8", _itop(76));
 
390
   $ret .= pack("h8", _itop($hr->{childs})); #Mhod childs !
 
391
   $ret .= pack("H8", "00");
 
392
   $ret .= pack("h8", _itop($hr->{plid})); #ORDER id
 
393
   $ret .= pack("h8", _itop($hr->{sid}));   #song id in playlist
 
394
   $ret .= pack("H96", "00");
 
395
  return $ret;
 
396
 }
 
397
 
 
398
 
 
399
 
 
400
 
 
401
 
 
402
 
 
403
 
 
404
 
 
405
## _INTERNAL ###################################################
 
406
#Convert utf8 (what we got from XML::Parser) to utf16 (ipod)
 
407
sub _ipod_string {
 
408
my ($utf8string) = @_;
 
409
#We got utf8 from parser, the iPod likes utf16.., swapped..
 
410
$utf8string = Unicode::String::utf8($utf8string)->utf16;
 
411
$utf8string = Unicode::String::byteswap2($utf8string);
 
412
return $utf8string;
 
413
}
 
414
 
 
415
 
 
416
 
 
417
## _INTERNAL ##################################################
 
418
#returns a (dummy) timestamp in MAC time format
 
419
sub _mactime {
 
420
my $x =    1234567890;
 
421
return sprintf("%08X", $x);
 
422
}
 
423
 
 
424
 
 
425
 
 
426
## _INTERNAL ##################################################
 
427
#int to ipod
 
428
sub _itop
 
429
{
 
430
my($in, $checkmax) = @_;
 
431
my($int) = $in =~ /(\d+)/;
 
432
 
 
433
$checkmax |= 0xffffffff;
 
434
 
 
435
if($int > $checkmax) {
 
436
 die "iTunesDB.pm: FATAL: $int > $checkmax (<- maximal value), can't continue!\n"
 
437
}
 
438
 
 
439
return scalar(reverse(sprintf("%08X", $int )));
 
440
}
 
441
 
 
442
## _INTERNAL ##################################################
 
443
#int to x86 ipodval (spl!!)
 
444
sub _x86itop
 
445
{
 
446
my($in, $checkmax) = @_;
 
447
my($int) = $in =~ /(\d+)/;
 
448
 
 
449
$checkmax |= 0xffffffff;
 
450
 
 
451
if($int > $checkmax) {
 
452
 die "iTunesDB.pm: FATAL: $int > $checkmax (<- maximal value), can't continue!\n"
 
453
}
 
454
 
 
455
 
 
456
return scalar((sprintf("%08X", $int )));
 
457
}
 
458
 
 
459
 
 
460
 
 
461
 
 
462
 
 
463
## _INTERNAL ##################################################
 
464
#Create a dummy listview, this function could disappear in
 
465
#future, only meant to be used internal by this module, dont
 
466
#use it yourself..
 
467
sub __dummy_listview
 
468
{
 
469
my($ret, $foobar);
 
470
$ret = "mhod";                          #header
 
471
$ret .= pack("H8", reverse("18"));      #size of header
 
472
$ret .= pack("H8", reverse("8802"));    #$slen+40 - size of header+body
 
473
$ret .= pack("H8", reverse("64"));      #type of the entry
 
474
$ret .= pack("H48", "00");                #?
 
475
$ret .= pack("H8", reverse("840001"));  #? (Static?)
 
476
$ret .= pack("H8", reverse("01"));      #?
 
477
$ret .= pack("H8", reverse("09"));      #?
 
478
$ret .= pack("H8", reverse("00"));      #?
 
479
$ret .= pack("H8",reverse("010025")); #static? (..or width of col?)
 
480
$ret .= pack("H8",reverse("00"));     #how to sort
 
481
$ret .= pack("H16", "00");
 
482
$ret .= pack("H8", reverse("0200c8"));
 
483
$ret .= pack("H8", reverse("01"));
 
484
$ret .= pack("H16","00");
 
485
$ret .= pack("H8", reverse("0d003c"));
 
486
$ret .= pack("H24","00");
 
487
$ret .= pack("H8", reverse("04007d"));
 
488
$ret .= pack("H24", "00");
 
489
$ret .= pack("H8", reverse("03007d"));
 
490
$ret .= pack("H24", "00");
 
491
$ret .= pack("H8", reverse("080064"));
 
492
$ret .= pack("H24", "00");
 
493
$ret .= pack("H8", reverse("170064"));
 
494
$ret .= pack("H8", reverse("01"));
 
495
$ret .= pack("H16", "00");
 
496
$ret .= pack("H8", reverse("140050"));
 
497
$ret .= pack("H8", reverse("01"));
 
498
$ret .= pack("H16", "00");
 
499
$ret .= pack("H8", reverse("15007d"));
 
500
$ret .= pack("H8", reverse("01"));
 
501
$ret .= pack("H752", "00");
 
502
$ret .= pack("H8", reverse("65"));
 
503
$ret .= pack("H152", "00");
 
504
 
 
505
# Every playlist has such an mhod, it tells iTunes (and other programs?) how the
 
506
# the playlist shall look (visible coloums.. etc..)
 
507
# But we are using always the same layout static.. we don't support this mhod type..
 
508
# But we write it (to make iTunes happy)
 
509
return $ret
 
510
}
 
511
 
 
512
 
 
513
## END WRITE FUNCTIONS ##
 
514
 
 
515
 
 
516
 
 
517
 
 
518
### Here are the READ sub's used by tunes2pod.pl
 
519
 
 
520
###########################################
 
521
# Get a INT value
 
522
sub get_int {
 
523
my($start, $anz) = @_;
 
524
my $buffer = undef;
 
525
# paranoia checks
 
526
$start = int($start);
 
527
$anz = int($anz);
 
528
#seek to the given position
 
529
seek(FILE, $start, 0);
 
530
#start reading
 
531
read(FILE, $buffer, $anz);
 
532
 return GNUpod::FooBar::shx2int($buffer);
 
533
}
 
534
 
 
535
 
 
536
###########################################
 
537
# Get a x86INT value
 
538
sub get_x86_int {
 
539
my($start, $anz) = @_;
 
540
 
 
541
my($buffer, $xx, $xr) = undef;
 
542
# paranoia checks
 
543
$start = int($start);
 
544
$anz = int($anz);
 
545
 
 
546
#seek to the given position
 
547
seek(FILE, $start, 0);
 
548
#start reading
 
549
read(FILE, $buffer, $anz);
 
550
 return GNUpod::FooBar::shx2_x86_int($buffer);
 
551
}
 
552
 
 
553
 
 
554
 
 
555
####################################################
 
556
# Get all SPL items
 
557
sub read_spldata {
 
558
 my($hr) = @_;
 
559
 
 
560
my $diff = $hr->{start}+160;
 
561
my @ret = ();
 
562
 
 
563
 for(1..$hr->{htm}) {
 
564
  my $field = get_int($diff+3, 1);
 
565
  my $action= get_int($diff+7, 1);
 
566
  my $slen  = get_int($diff+55,1); #Whoa! This is true: string is limited to 0xfe (254) chars!! (iTunes4)
 
567
  my $rs    = undef; #ReturnSting
 
568
#Fixme: this is ugly
 
569
   if($field =~ /^(2|3|4|8|9|14|18)$/) { #Is a string type
 
570
    my $string= get_string($diff+56, $slen);
 
571
    #No byteswap here?? why???
 
572
    $rs = Unicode::String::utf16($string)->utf8;
 
573
   }
 
574
   else { #Is INT (Or range)
 
575
    my $xfint = get_x86_int($diff+56+4,4);
 
576
    my $xtint = get_x86_int($diff+56+28,4);
 
577
    $rs = "$xfint:$xtint";
 
578
   }
 
579
  $diff += $slen+56;
 
580
  push(@ret, {field=>$field,action=>$action,string=>$rs});
 
581
 }
 
582
 return \@ret;
 
583
}
 
584
 
 
585
 
 
586
#################################################
 
587
# Read SPLpref data
 
588
sub read_splpref {
 
589
 my($hs) = @_;
 
590
 my ($live, $chkrgx, $chklim, $mos);
 
591
 
 
592
    $live    = 1 if   get_int($hs->{start}+24,1);
 
593
    $chkrgx  = 1 if get_int($hs->{start}+25,1);
 
594
    $chklim  = 1 if get_int($hs->{start}+26,1);
 
595
 my $item    =    get_int($hs->{start}+27,1);
 
596
 my $sort    =    get_int($hs->{start}+28,1);
 
597
 my $limit   =   get_int($hs->{start}+32,4);
 
598
    $mos     = 1 if get_int($hs->{start}+36,1);
 
599
 return({live=>$live,
 
600
         value=>$limit, iitem=>$item, isort=>$sort,mos=>$mos,checkrule=>($chklim+($chkrgx*2))});
 
601
}
 
602
 
 
603
#################################################
 
604
# Do a hexDump ..
 
605
sub __hd {
 
606
   open(KK,">/tmp/XLZ"); print KK $_[0]; close(KK);
 
607
   system("hexdump -vC /tmp/XLZ");
 
608
}
 
609
 
 
610
 
 
611
###########################################
 
612
#get a SINGLE mhod entry:
 
613
# return+seek = new_mhod should be there
 
614
sub get_mhod {
 
615
my ($seek) = @_;
 
616
 
 
617
my $id  = get_string($seek, 4);          #are we lost?
 
618
my $ml  = get_int($seek+8, 4);           #Length of this mhod
 
619
my $mty = get_int($seek+12, 4);          #type number
 
620
my $xl  = get_int($seek+28,4);           #String length
 
621
 
 
622
## That's spl stuff, only to be used with 51 mhod's
 
623
my $htm = get_int($seek+35,1); #Only set for 51
 
624
my $anym= get_int($seek+39,1); #Only set for 51
 
625
my $spldata = undef; #dummy
 
626
my $splpref = undef; #dummy
 
627
 
 
628
if($id eq "mhod") { #Seek was okay
 
629
    my $foo = get_string($seek+($ml-$xl), $xl); #string of the entry 
 
630
    #$foo is now UTF16 (Swapped), but we need an utf8
 
631
    $foo = Unicode::String::byteswap2($foo);
 
632
    $foo = Unicode::String::utf16($foo)->utf8;
 
633
 
 
634
 ##Special handling for SPLs
 
635
 if($mty == 51) { #Get data from spldata mhod
 
636
   $foo = undef;
 
637
   $spldata = read_spldata({start=>$seek, htm=>$htm});
 
638
 }
 
639
 elsif($mty == 50) { #Get prefs from splpref mhod
 
640
  $foo = undef;
 
641
  $splpref = read_splpref({start=>$seek, end=>$ml});
 
642
 }
 
643
 return({size=>$ml,string=>$foo,type=>$mty,spldata=>$spldata,splpref=>$splpref,matchrule=>$anym});
 
644
 
 
645
}
 
646
else {
 
647
 return({size=>-1});
 
648
}
 
649
}
 
650
 
 
651
 
 
652
 
 
653
##############################################
 
654
# get an mhip entry
 
655
sub get_mhip {
 
656
 my($pos) = @_;
 
657
 my $oid = 0;
 
658
 if(get_string($pos, 4) eq "mhip") {
 
659
  my $oof = get_int($pos+4, 4);
 
660
  my $mhods=get_int($pos+12,4);
 
661
 
 
662
  for(my $i=0;$i<$mhods;$i++) {
 
663
   my $mhs = get_mhod($pos+$oof)->{size};
 
664
   die "Fatal seek error in get_mhip, can't continue\n" if $mhs == -1;
 
665
   $oid+=$mhs;
 
666
  }
 
667
 
 
668
   my $plid = get_int($pos+5*4,4);
 
669
   my $sid  = get_int($pos+6*4, 4);
 
670
  return({size=>($oid+$oof),sid=>$sid,plid=>$plid});
 
671
 }
 
672
 
 
673
#we are lost
 
674
 return ({size=>-1});
 
675
}
 
676
 
 
677
 
 
678
###########################################
 
679
# Reads a string
 
680
sub get_string {
 
681
my ($start, $anz) = @_;
 
682
my($buffer) = undef;
 
683
$start = int($start);
 
684
$anz = int($anz);
 
685
seek(FILE, $start, 0);
 
686
#start reading
 
687
read(FILE, $buffer, $anz);
 
688
 return $buffer;
 
689
}
 
690
 
 
691
 
 
692
 
 
693
 
 
694
#############################################
 
695
# Get a playlist (Should be called get_mhyp, but it does the whole playlist)
 
696
sub get_pl {
 
697
 my($pos) = @_;
 
698
 
 
699
 my %ret_hash = ();
 
700
 my @pldata = ();
 
701
 
 
702
  if(get_string($pos, 4) eq "mhyp") { #Ok, its an mhyp
 
703
      $ret_hash{type} = get_int($pos+20, 4); #Is it a main playlist?
 
704
   my $scount         = get_int($pos+16, 4); #How many songs should we expect?
 
705
   my $header_len     = get_int($pos+4, 4);  #Size of the header
 
706
   my $mhyp_len     = get_int($pos+8, 4);   #Size of mhyp
 
707
   my $mhods          = get_int($pos+12,4); #How many mhods we have here
 
708
#Its a MPL, do a fast skip
 
709
if($ret_hash{type}) {
 
710
 return ($pos+$mhyp_len, {type=>1}) 
 
711
}
 
712
   $pos += $header_len; #set pos to start of first mhod
 
713
   #We can now read the name of the Playlist
 
714
   #Ehpod is buggy and writes the playlist name 2 times.. well catch both of them
 
715
   #MusicMatch is also stupid and doesn't create a playlist mhod
 
716
   #for the mainPlaylist
 
717
   my ($oid, $plname, $itt) = undef;
 
718
 for(my $i=0;$i<$mhods;$i++) {
 
719
   my $mhh = get_mhod($pos);
 
720
   if($mhh->{size} == -1) {
 
721
    print STDERR "*** FATAL: Expected to find $mhods mhods,\n";
 
722
    print STDERR "*** but i failed to get nr. $i\n";
 
723
    print STDERR "*** Please send your iTuneDB to:\n";
 
724
    print STDERR "*** pab\@blinkenlights.ch\n";
 
725
    print STDERR "!!! iTunesDB.pm panic!\n";
 
726
    exit(1);
 
727
   }
 
728
   $pos+=$mhh->{size};
 
729
   if($mhh->{type} == 1) {
 
730
     $ret_hash{name} = $mhh->{string};
 
731
   }
 
732
   elsif(ref($mhh->{splpref}) eq "HASH") {
 
733
     $ret_hash{splpref} = $mhh->{splpref};
 
734
   }
 
735
   elsif(ref($mhh->{spldata}) eq "ARRAY") {
 
736
     $ret_hash{spldata} = $mhh->{spldata};
 
737
     $ret_hash{matchrule}=$mhh->{matchrule};
 
738
   }
 
739
 }
 
740
 
 
741
   #Now get the items
 
742
 for(my $i = 0; $i<$scount;$i++) {
 
743
    my $mhih = get_mhip($pos);
 
744
    if($mhih->{size} == -1) {
 
745
       print STDERR "*** FATAL: Expected to find $scount songs,\n";
 
746
       print STDERR "*** but i failed to get nr. $i\n";
 
747
       print STDERR "*** Your iTunesDB maybe corrupt or you found\n";
 
748
       print STDERR "*** a bug in GNUpod. Please send this\n";
 
749
       print STDERR "*** iTunesDB to pab\@blinkenlights.ch\n\n";
 
750
       print STDERR "!!! iTunesDB.pm panic!\n";
 
751
       exit(1);
 
752
    }
 
753
    $pos += $mhih->{size};
 
754
     push(@pldata, $mhih->{sid}) if $mhih->{sid};
 
755
   }
 
756
   $ret_hash{content} = \@pldata;
 
757
   return ($pos, \%ret_hash);   
 
758
  }
 
759
 
 
760
 #Seek was wrong
 
761
 return -1;
 
762
}
 
763
 
 
764
 
 
765
 
 
766
###########################################
 
767
# Get mhit + child mhods
 
768
sub get_mhits {
 
769
my ($sum) = @_;
 
770
if(get_string($sum, 4) eq "mhit") { #Ok, its a mhit
 
771
 
 
772
my %ret     = ();
 
773
 
 
774
#Infos stored in mhit
 
775
$ret{id}       = get_int($sum+16,4);
 
776
$ret{filesize} = get_int($sum+36,4);
 
777
$ret{time}     = get_int($sum+40,4);
 
778
$ret{cdnum}    = get_int($sum+92,4);
 
779
$ret{cds}      = get_int($sum+96,4);
 
780
$ret{songnum}  = get_int($sum+44,4);
 
781
$ret{songs}    = get_int($sum+48,4);
 
782
$ret{year}     = get_int($sum+52,4);
 
783
$ret{bitrate}  = get_int($sum+56,4);
 
784
$ret{srate}    = get_int($sum+62,2); #What is 60-61 ?!!
 
785
$ret{volume}   = get_int($sum+64,4);
 
786
$ret{starttime}= get_int($sum+68,4);
 
787
$ret{stoptime} = get_int($sum+72,4);
 
788
$ret{playcount} = get_int($sum+80,4); #84 has also something to do with playcounts. (Like rating + prerating?)
 
789
$ret{rating}    = int((get_int($sum+28,4)-256)/oct('0x14000000')) * 20;
 
790
$ret{prerating} = int(get_int($sum+120,4) / oct('0x140000')) * 20;
 
791
 
 
792
####### We have to convert the 'volume' to percent...
 
793
####### The iPod doesn't store the volume-value in percent..
 
794
#Minus value (-X%)
 
795
$ret{volume} -= oct("0xffffffff") if $ret{volume} > 255;
 
796
 
 
797
#Convert it to percent
 
798
$ret{volume} = sprintf("%.0f",($ret{volume}/2.55));
 
799
 
 
800
## Paranoia check
 
801
if(abs($ret{volume}) > 100) {
 
802
 print " *** BUG *** .. Volume is $ret{volume} percent.. this is impossible :)\n";
 
803
 print "Please send this iTunesDB to pab\@blinkenlights.ch .. thanks :)\n";
 
804
 print ">> Volume set to 0 percent..\n";
 
805
 $ret{volume} = 0;
 
806
}
 
807
 
 
808
 
 
809
 #Now get the mhods from this mhit
 
810
my $mhods = get_int($sum+12,4);
 
811
$sum += get_int($sum+4,4);
 
812
 
 
813
 for(my $i=0;$i<$mhods;$i++) {
 
814
    my $mhh = get_mhod($sum);
 
815
    if($mhh->{size} == -1) {
 
816
     print STDERR "** FATAL: Expected to find $mhods mhods,\n";
 
817
     print STDERR "** but i failed to get nr $i\n";
 
818
     print STDERR "*** Please send your iTuneDB to:\n";
 
819
     print STDERR "*** pab\@blinkenlights.ch\n";
 
820
     print STDERR "!!! iTunesDB.pm panic!\n";     
 
821
     exit(1);
 
822
    }
 
823
    $sum+=$mhh->{size};
 
824
    my $xml_name = $mhod_array[$mhh->{type}];
 
825
    if($xml_name) { #Has an xml name.. sounds interesting
 
826
      $ret{$xml_name} = $mhh->{string};
 
827
    }
 
828
    else {
 
829
     warn "iTunesDB.pm: found unhandled mhod type '$mhh->{type}'\n";
 
830
    }
 
831
 }
 
832
return ($sum,\%ret);          #black magic, returns next (possible?) start of the mhit
 
833
}
 
834
#Was no mhod
 
835
 return -1;
 
836
}
 
837
 
 
838
 
 
839
 
 
840
 
 
841
#########################################################
 
842
# Returns start of part1 (files) and part2 (playlists)
 
843
sub get_starts {
 
844
#Get start of first mhit:
 
845
my $mhbd_s     = get_int(4,4);
 
846
my $pdi        = get_int($mhbd_s+8,4); #Used to calculate start of playlist
 
847
my $mhsd_s     = get_int($mhbd_s+4,4);
 
848
my $mhlt_s     = get_int($mhbd_s+$mhsd_s+4,4);
 
849
my $pos = $mhbd_s+$mhsd_s+$mhlt_s; #pos is now the start of the first mhit (always 292?);
 
850
 
 
851
#How many songs are on the iPod ?
 
852
my $sseek = $mhbd_s + $mhsd_s;
 
853
my $songs = get_int($sseek+8,4);
 
854
 
 
855
#How many playlists should we find ?
 
856
$sseek = $mhbd_s + $pdi;
 
857
$sseek += get_int($sseek+4,4);
 
858
my $pls = get_int($sseek+8,4);
 
859
return({position=>$pos,pdi=>($pos+$pdi),songs=>$songs,playlists=>$pls});
 
860
}
 
861
 
 
862
 
 
863
 
 
864
######################## Other funny stuff #########################
 
865
 
 
866
##############################################
 
867
# Read PlayCounts (Only used for rating atm?)
 
868
sub readPLC {
 
869
 my($file) = @_;
 
870
 open(RATING, "$file") or return ();
 
871
 
 
872
 my $os = 108;
 
873
 my $buff;
 
874
 my %pcrh = ();
 
875
 while(1) {
 
876
  seek(RATING, $os, 0);
 
877
  last unless read(RATING, $buff, 2) == 2;
 
878
  my $xin = GNUpod::FooBar::shx2int($buff);
 
879
  my $xnum = (($os-108)/16+1);
 
880
  $pcrh{$xnum} = $xin if $xin;
 
881
  warn "debug: $xnum has $xin\n" if $xin;
 
882
  $os += 16;
 
883
 }
 
884
 
 
885
close(RATING);
 
886
 return \%pcrh;
 
887
}
 
888
 
 
889
##############################################
 
890
# Read OnTheGo data
 
891
sub readOTG {
 
892
 my($file) = @_;
 
893
 
 
894
 my $buff = undef;
 
895
 open(OTG, "$file") or return ();
 
896
  seek(OTG, 12, 0);
 
897
  read(OTG, $buff, 4);
 
898
  
 
899
  my $items = GNUpod::FooBar::shx2int($buff); 
 
900
 
 
901
  my @content = ();
 
902
  my $offst = 20;
 
903
  for(1..$items) {
 
904
   seek(OTG, $offst, 0);
 
905
   read(OTG, $buff, 4);
 
906
   push(@content, GNUpod::FooBar::shx2int($buff));
 
907
   $offst+=4;
 
908
  }
 
909
  return @content;
 
910
}
 
911
 
 
912
 
 
913
########################################################
 
914
# Open the iTunesDB file..
 
915
sub open_itunesdb {
 
916
 open(FILE, $_[0]);
 
917
}
 
918
 
 
919
########################################################
 
920
# Close the iTunesDB file..
 
921
sub close_itunesdb {
 
922
 close(FILE);
 
923
}
 
924
 
 
925
 
 
926
1;