1
# iTunesDB.pm - Version 20040116
2
# Copyright (C) 2002-2004 Adrian Ulrich <pab at blinkenlights.ch>
3
# Part of the gnupod-tools collection
5
# URL: http://blinkenlights.ch/cgi-bin/fm.pl?get=ipod
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.
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.
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
21
# iTunes and iPod are trademarks of Apple
23
# This product is not supported/written/published by Apple!
25
package GNUpod::iTunesDB;
30
use vars qw(%mhod_id @mhod_array);
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{$_}] = $_;
38
## GENERAL #########################################################
39
# create an iTunesDB header
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
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
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
70
## GENERAL ##########################################################
71
# Create an mhit entry, needs to know about the length of his
72
# mhod(s) (You have to create them yourself..!)
75
my %file_hash = %{$hr->{fh}};
77
#We have to fix 'volume'
78
my $vol = sprintf("%.0f",( int($file_hash{volume})*2.55 ));
80
if($vol >= 0 && $vol <= 255) { } #Nothing to do
81
elsif($vol < 0 && $vol >= -255) { #Convert value
82
$vol = oct("0xFFFFFFFF") + $vol;
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..
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";
97
#Check for stupid input
98
my ($c_id) = $file_hash{id} =~ /(\d+)/;
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";
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?
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...
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"); # ???
143
## GENERAL ##########################################################
144
# An mhod simply holds information
157
#100 - Playlist item or/and PlaylistLayout (used for trash? ;))
160
my $type_string = $hr->{stype};
161
my $string = $hr->{string};
162
my $fqid = $hr->{fqid};
163
my $type = $mhod_id{lc($type_string)};
165
#Appnd size for normal mhod's
168
#Called with fqid, this has to be an PLTHING (100)
170
#fqid set, that's a pl item!
172
#Playlist mhods are longer
175
elsif(!$type) { #No type and no fqid, skip it
178
else { #has a type, default fqid
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";
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
196
$ret .= pack("h8", _itop(length($string))); #size of string
199
if($type != 100){ #no PL mhod
200
$ret .= pack("h16"); #trash
201
$ret .= $string; #the string
204
$ret .= pack("h24"); #playlist mhods are a different
210
## GENERAL #################################################################
211
# Create a spl-pref (type=50) mhod
214
my($live, $chkrgx, $chklim, $mos) = 0;
216
$live = 1 if $hs->{liveupdate};
217
my $checkrule = int($hs->{checkrule});
218
$mos = 1 if $hs->{mos};
220
if($checkrule < 1 || $checkrule > 3) {
221
warn "iTunesDB.pm: error: 'checkrule' ($checkrule) out of range. value set to 1 (=LimitMatch)\n";
225
$chkrgx = 1 if $checkrule>1;
226
$chklim = $checkrule-$chkrgx*2;
227
#lim-only = 1 / match only = 2 / both = 3
230
$ret .= pack("h8", _itop(24)); #Size of header
231
$ret .= pack("h8", _itop(96));
232
$ret .= pack("h8", _itop(50));
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
240
$ret .= pack("h8", _itop($hs->{value})); #lval
241
$ret .= pack("h2", _itop($mos,0xff)); #mos
242
$ret .= pack("h118");
245
## GENERAL #################################################################
246
# Create a spl-data (type=51) mhod
250
my $anymatch = 1 if $hs->{anymatch};
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=>""});
258
foreach my $chr (@{$hs->{data}}) {
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;
265
my ($from, $to) = $chr->{string} =~ /(\d+):?(\d*)/;
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");
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);
285
$cr .= pack("h2", _itop($chr->{field},0xff));
286
$cr .= pack("H6", reverse("010000"));
287
$cr .= pack("h2", _itop($chr->{action},0xff));
289
$cr .= pack("h2", _itop(length($string),0xff));
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
298
$ret .= "SLst"; #Magic
299
$ret .= pack("H8", reverse("00010001")); #?
301
$ret .= pack("h2", _itop(int(@{$hs->{data}}),0xff)); #HTM (Childs from cr)
303
$ret .= pack("h2", _itop($anymatch,0xff)); #anymatch rule on or off
304
$ret .= pack("h240");
315
## FILES #########################################################
316
# header for all files (like you use mk_mhlp for playlists)
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
336
## PLAYLIST #######################################################
337
# header for ALL playlists
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
351
## PLAYLIST ######################################################
352
# Creates an header for a new playlist (child of mk_mhlp)
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)
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};
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
380
## PLAYLIST ##################################################
381
# header for new Playlist item (child if mk_mhyp)
386
#plid = playlist order ID
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");
405
## _INTERNAL ###################################################
406
#Convert utf8 (what we got from XML::Parser) to utf16 (ipod)
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);
417
## _INTERNAL ##################################################
418
#returns a (dummy) timestamp in MAC time format
421
return sprintf("%08X", $x);
426
## _INTERNAL ##################################################
430
my($in, $checkmax) = @_;
431
my($int) = $in =~ /(\d+)/;
433
$checkmax |= 0xffffffff;
435
if($int > $checkmax) {
436
die "iTunesDB.pm: FATAL: $int > $checkmax (<- maximal value), can't continue!\n"
439
return scalar(reverse(sprintf("%08X", $int )));
442
## _INTERNAL ##################################################
443
#int to x86 ipodval (spl!!)
446
my($in, $checkmax) = @_;
447
my($int) = $in =~ /(\d+)/;
449
$checkmax |= 0xffffffff;
451
if($int > $checkmax) {
452
die "iTunesDB.pm: FATAL: $int > $checkmax (<- maximal value), can't continue!\n"
456
return scalar((sprintf("%08X", $int )));
463
## _INTERNAL ##################################################
464
#Create a dummy listview, this function could disappear in
465
#future, only meant to be used internal by this module, dont
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");
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)
513
## END WRITE FUNCTIONS ##
518
### Here are the READ sub's used by tunes2pod.pl
520
###########################################
523
my($start, $anz) = @_;
526
$start = int($start);
528
#seek to the given position
529
seek(FILE, $start, 0);
531
read(FILE, $buffer, $anz);
532
return GNUpod::FooBar::shx2int($buffer);
536
###########################################
539
my($start, $anz) = @_;
541
my($buffer, $xx, $xr) = undef;
543
$start = int($start);
546
#seek to the given position
547
seek(FILE, $start, 0);
549
read(FILE, $buffer, $anz);
550
return GNUpod::FooBar::shx2_x86_int($buffer);
555
####################################################
560
my $diff = $hr->{start}+160;
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
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;
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";
580
push(@ret, {field=>$field,action=>$action,string=>$rs});
586
#################################################
590
my ($live, $chkrgx, $chklim, $mos);
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);
600
value=>$limit, iitem=>$item, isort=>$sort,mos=>$mos,checkrule=>($chklim+($chkrgx*2))});
603
#################################################
606
open(KK,">/tmp/XLZ"); print KK $_[0]; close(KK);
607
system("hexdump -vC /tmp/XLZ");
611
###########################################
612
#get a SINGLE mhod entry:
613
# return+seek = new_mhod should be there
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
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
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;
634
##Special handling for SPLs
635
if($mty == 51) { #Get data from spldata mhod
637
$spldata = read_spldata({start=>$seek, htm=>$htm});
639
elsif($mty == 50) { #Get prefs from splpref mhod
641
$splpref = read_splpref({start=>$seek, end=>$ml});
643
return({size=>$ml,string=>$foo,type=>$mty,spldata=>$spldata,splpref=>$splpref,matchrule=>$anym});
653
##############################################
658
if(get_string($pos, 4) eq "mhip") {
659
my $oof = get_int($pos+4, 4);
660
my $mhods=get_int($pos+12,4);
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;
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});
678
###########################################
681
my ($start, $anz) = @_;
683
$start = int($start);
685
seek(FILE, $start, 0);
687
read(FILE, $buffer, $anz);
694
#############################################
695
# Get a playlist (Should be called get_mhyp, but it does the whole playlist)
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})
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";
729
if($mhh->{type} == 1) {
730
$ret_hash{name} = $mhh->{string};
732
elsif(ref($mhh->{splpref}) eq "HASH") {
733
$ret_hash{splpref} = $mhh->{splpref};
735
elsif(ref($mhh->{spldata}) eq "ARRAY") {
736
$ret_hash{spldata} = $mhh->{spldata};
737
$ret_hash{matchrule}=$mhh->{matchrule};
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";
753
$pos += $mhih->{size};
754
push(@pldata, $mhih->{sid}) if $mhih->{sid};
756
$ret_hash{content} = \@pldata;
757
return ($pos, \%ret_hash);
766
###########################################
767
# Get mhit + child mhods
770
if(get_string($sum, 4) eq "mhit") { #Ok, its a mhit
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;
792
####### We have to convert the 'volume' to percent...
793
####### The iPod doesn't store the volume-value in percent..
795
$ret{volume} -= oct("0xffffffff") if $ret{volume} > 255;
797
#Convert it to percent
798
$ret{volume} = sprintf("%.0f",($ret{volume}/2.55));
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";
809
#Now get the mhods from this mhit
810
my $mhods = get_int($sum+12,4);
811
$sum += get_int($sum+4,4);
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";
824
my $xml_name = $mhod_array[$mhh->{type}];
825
if($xml_name) { #Has an xml name.. sounds interesting
826
$ret{$xml_name} = $mhh->{string};
829
warn "iTunesDB.pm: found unhandled mhod type '$mhh->{type}'\n";
832
return ($sum,\%ret); #black magic, returns next (possible?) start of the mhit
841
#########################################################
842
# Returns start of part1 (files) and part2 (playlists)
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?);
851
#How many songs are on the iPod ?
852
my $sseek = $mhbd_s + $mhsd_s;
853
my $songs = get_int($sseek+8,4);
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});
864
######################## Other funny stuff #########################
866
##############################################
867
# Read PlayCounts (Only used for rating atm?)
870
open(RATING, "$file") or return ();
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;
889
##############################################
895
open(OTG, "$file") or return ();
899
my $items = GNUpod::FooBar::shx2int($buff);
904
seek(OTG, $offst, 0);
906
push(@content, GNUpod::FooBar::shx2int($buff));
913
########################################################
914
# Open the iTunesDB file..
919
########################################################
920
# Close the iTunesDB file..