/usr/lib/gpixpod/mh.py is in gpixpod 0.6.2-0ubuntu8.
This file is owned by root:root, with mode 0o755.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 | #!/usr/bin/env python
# GPixPod - organize photos on your iPod, freely!
# Copyright (C) 2006 Flavio Gargiulo (FLAGAR.com)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os, sys, shutil, time, cPickle
from utils import *
from imgconvert import *
from struct import *
from zlib import adler32 # Seems the fastest checksum method
from threading import Thread # Using threads when generating thumbs
class MH:
""" Process the MH database """
formats = {'mhfd':'<4s16I64x', 'mhsd':'<4s3I80x', 'mhli':'<4s2I80x', 'mhii':'<4s12I100x', 'mhod':'<4s2I1H2B8x',
'mhni':'<4s6I2h2H40x', 'mhla':'<4s2I80x', 'mhba':'<4s15I84x', 'mhia':'<4s4I20x', 'mhlf':'<4s2I80x', 'mhif':'<4s5I100x'}
lengths = dict(map(lambda(x): (x[0], calcsize(x[1])), formats.items()))
latest = {'mhfd':0, 'mhsd':0, 'mhli':0, 'mhii':0, 'mhoc':0, 'mhni':0, 'mhod':0, 'mhos':0, 'mhla':0, 'mhba':0, 'mhia':0, 'mhlf':0, 'mhif':0}
parents = {'mhfd':'mhfd', 'mhsd':'mhfd', 'mhli':'mhsd', 'mhii':'mhli', 'mhoe':'mhii', 'mhoc':'mhii', 'mhni':'mhoc', 'mhod':'mhni', 'mhos':'mhod',
'mhla':'mhsd', 'mhba':'mhla', 'mhob':'mhba', 'mhoa':'mhob', 'mhia':'mhba', 'mhlf':'mhsd', 'mhif':'mhlf'}
# MHOE: container MHOD for full res.; MHOC: container MHOD for thumb; MHOD: string MHOD type-3; MHOS: string MHOD type-3 subheader;
# MHOB: string MHOD type-1; MHOA: string MHOD type-1 subheader.
#thumbdbsizes = ((712, 480), (320, 240), (123, 88), (53, 41)) MOVED TO __INIT__
#thumbsizes = ((720, 480), (320, 240), (130, 88), (50, 41)) MOVED TO __INIT__
#thumbdims = tuple(map(lambda(x): x[0]*x[1]*2, thumbsizes)) MOVED TO __INIT__
#thumbfnames = (1019, 1024, 1015, 1036) # Default thumbnails filename parts
removed_photos_ids = []
new_photos_added = []
thumbs_extracted = False
progress = 0.0
def __init__(self, dir='/mnt/ipod/Photos/Photo Database', sglthumbsdir=None, opening=True, ipodmodel=None):
""" Initial setup for the Photo Database file """
# Figuring out the full filename and main path
if dir.find('Photo Database') == -1:
self.dbname = os.path.join(dir, 'Photo Database')
self.dbdir = dir
else:
self.dbname = dir
self.dbdir = dir.replace('Photo Database', '')
# Single thumbnails dir
if sglthumbsdir == None:
self.sglthumbsdir = os.path.join(self.dbdir, '.SingleThumbs')
else:
self.sglthumbsdir = sglthumbsdir
# Configuration files: thumbnails checksum pickle file
self.thumbschecksum = os.path.join(os.path.expanduser('~'), '.gpixpod', 'checksum')
# Detecting iPod model and setting thumbnail infos
if ipodmodel in ('5G', 'Photo', 'Color', 'Nano'):
self.ipod_model = ipodmodel
else:
self.ipod_model = getIpodModel(self.dbdir.replace('Photos', ''))
if self.ipod_model == '5G': # Supported for sure: developer's one
self.thumbdbsizes = ((712, 480), (320, 240), (123, 88), (53, 41))
self.thumbsizes = ((720, 480), (320, 240), (130, 88), (50, 41))
self.thumbfnames = (1019, 1024, 1015, 1036)
elif self.ipod_model == 'Photo': # Tested and verified
self.thumbdbsizes = ((712, 452), (235, 146), (130, 87), (43, 30))
self.thumbsizes = ((720, 480), (220, 176), (130, 88), (42, 30))
self.thumbfnames = (1019, 1013, 1015, 1009)
elif self.ipod_model == 'Color': # Guessed (same as Photo?)
self.thumbdbsizes = ((712, 452), (235, 146), (130, 87), (43, 30))
self.thumbsizes = ((720, 480), (220, 176), (130, 88), (42, 30))
self.thumbfnames = (1019, 1013, 1015, 1009)
elif self.ipod_model == 'Nano': # Tested and verified
self.thumbdbsizes = ((176, 132), (45, 37))
self.thumbsizes = ((176, 132), (42, 37))
self.thumbfnames = (1023, 1032)
else: # Using iPod 5G values as default for other, unsupported models. The GUI will warn.
self.thumbdbsizes = ((712, 480), (320, 240), (123, 88), (53, 41))
self.thumbsizes = ((720, 480), (320, 240), (130, 88), (50, 41))
self.thumbfnames = (1019, 1024, 1015, 1036)
self.thumbdims = tuple(map(lambda(x): x[0]*x[1]*2, self.thumbsizes))
if opening:
self.Open()
self.PX_ADDED = []
self.PX_REMOVED = []
def Open(self):
""" Actually open and process the Photo Database file """
self.dbcreatedfromscratch = False
try:
self.db = open(self.dbname, 'rb')
except IOError:
print "Directory does not contain a Photo Database file"
print "A new one will be created based on the path specified"
# FromScratch opening should be modelled better as in exceptions as in questions before overwriting
self._FromScratch()
self.dbcreatedfromscratch = True
if not os.path.isdir(self.dbdir):
os.makedirs(self.dbdir)
# Creating thumbnails files if they don't exist yet
if not os.path.isdir(os.path.join(self.dbdir, 'Thumbs')):
os.mkdir(os.path.join(self.dbdir, 'Thumbs'))
for x in self.thumbfnames:
t = open(os.path.join(self.dbdir, 'Thumbs', 'F%s_1.ithmb' % x), 'ab')
t.close()
self._SaveDB()
self.db = open(self.dbname, 'rb')
self._FirstScan()
# If creating from scratch, copying full resolution images by default
if self.dbcreatedfromscratch:
self.dbcopyfullres = True
if not self._ThumbsChecksumVerify():
print "Thumbnails cache checksum failed. Extracting from *.ithumb files."
self._ThumbsExtract()
self.WriteDatabaseLog()
# Enfast saving, avoiding rebuilding thumbs if there are no deletions
def _PhotoCreationDate(self, filename):
""" Try to get the date of the image from the EXIF headers, otherwise from the file """
try:
import mmpython.image.EXIF
f = open(filename, 'rb')
tags = mmpython.image.EXIF.process_file(f)
f.close()
# e.g. 'EXIF DateTimeOriginal' = '2004:12:15 16:49:49'
if tags.has_key('EXIF DateTimeOriginal'):
return time.strptime(tags['EXIF DateTimeOriginal'].printable, "%Y:%m:%d %H:%M:%S")
except ImportError, e:
pass
# Use the content modification time
st = os.stat(filename)
return time.localtime(st.st_mtime)
def _FromScratchApply(self):
""" Annul already opened Photo Database applying from scratch method """
self._FromScratch()
self.dbcreatedfromscratch = True
# Creating thumbnails files if they don't exist yet
if not os.path.isdir(os.path.join(self.dbdir, 'Thumbs')):
os.mkdir(os.path.join(self.dbdir, 'Thumbs'))
for x in self.thumbfnames:
t = open(os.path.join(self.dbdir, 'Thumbs', 'F%s_1.ithmb' % x), 'ab')
t.close()
self._SaveDB()
self.db = open(self.dbname, 'rb')
self._FirstScan()
self.dbcopyfullres = True
def _FromScratch(self):
""" Define a new self.dblines from scratch to generate a new empty database file """
# Template for an iPod video 5G
self.dblines = []
self.dblines.append(['mhfd', ['mhfd', 132, 1252, 0, 1, 3, 0, 100, 0, 0, 0, 0, 2, 0, 0, 0, 0], 0])
self.dblines.append(['mhsd', ['mhsd', 96, 188, 1], 0])
self.dblines.append(['mhli', ['mhli', 92, 0], 1])
self.dblines.append(['mhsd', ['mhsd', 96, 380, 2], 0])
self.dblines.append(['mhla', ['mhla', 92, 1], 3])
self.dblines.append(['mhba', ['mhba', 148, 192, 1, 0, 100, 0, 65536, 0, 0, 0, 0, 0, 0, 0, 100], 4])
self.dblines.append(['mhob', ['mhod', 24, 44, 1, 0, 1], 5])
self.dblines.append(['mhoa', [7, 1, 0, 'Library'.encode('utf-8')], 6])
self.dblines.append(['mhsd', ['mhsd', 96, 684, 3], 0])
self.dblines.append(['mhlf', ['mhlf', 92, 4], 5])
self.dblines.append(['mhif', ['mhif', 124, 124, 0, 1019, 691200], 6])
self.dblines.append(['mhif', ['mhif', 124, 124, 0, 1015, 22880], 6])
self.dblines.append(['mhif', ['mhif', 124, 124, 0, 1024, 153600], 6])
self.dblines.append(['mhif', ['mhif', 124, 124, 0, 1036, 4100], 6])
# Modifying template for iPod Photo/Color
if self.ipod_model == 'Photo' or self.ipod_model == 'Color':
self.dblines[-1][1][5] = 2520 # Modifying dimension of the 42x30 thumbnail
self.dblines[-2][1][5] = 77440 # Modifying dimension of the 220x176 thumbnail
# Updating thumbnails filenames parts
self.dblines[-4][1][4] = self.thumbfnames[0]
self.dblines[-3][1][4] = self.thumbfnames[2]
self.dblines[-2][1][4] = self.thumbfnames[1]
self.dblines[-1][1][4] = self.thumbfnames[3]
# Modifying template for iPod Nano
elif self.ipod_model == 'Nano':
del self.dblines[-4:-2] # deleting missing thumbnails types
# Updating thumbnails filename parts
self.dblines[-2][1][4] = self.thumbfnames[0]
self.dblines[-1][1][4] = self.thumbfnames[1]
self.dblines[-1][1][5] = 3108 # Modifying dimension of the 42x37 thumbnail
self.dblines[-2][1][5] = 46464 # Modifying dimension of the 176x132 thumbnail
self.dblines[-3][1][2] = 2 # Modifying MHLF indicating only 2 thumbnails types
self.dblines[-4][1][2] -= 248 # Updating parent MHSD total length, after having deleted 2 MHIF
self.dblines[0][1][2] -= 248 # Updating MHFD, too
def _FirstScan(self):
""" Scan the database file """
self.secondscan = False # Manage different format Photo Database probably generated by iPhoto
self.restartfromscratch = False # Restart from scratch if the Photo Database empty format generated by iTunes is in an odd status
self.db.seek(0, 2); dbsize = self.db.tell()
self.db.seek(0); self.dblines = []
self.dbcopyfullres = False
while self.db.tell() < dbsize:
dbline = unpack('<4s', self.db.read(4))[0]
self.db.seek(-4, 1)
self.dblines.append([dbline, list(unpack(self.formats[dbline], self.db.read(self.lengths[dbline]))), self.latest[self.parents[dbline]]])
if dbline == 'mhlf': # We really need to already have the MHIFs. Otherwise we get a crash.
if self.dblines[-1][1][2] == 0 and self.dblines[2][0] == 'mhli' and self.dblines[2][1][2] == 0: # No MHIFs, no images
self.restartfromscratch = True # Thus it is better to restart from scratch.
elif dbline == 'mhod':
mhod_type = self.dblines[-1][1][3]
if mhod_type == 5:
self.dblines[-1][0] = 'mhoe'
self.dbcopyfullres = True
elif mhod_type == 2:
if self.dblines[-4][0] == 'mhba':
# Then we are handling one of those recent iPod Nanoes with different Photo Database format, with a second string MHOD in photo albums MHBAs
# after the first string MHOD of its name, probably stored by iPhoto and containing a transition effect ignored by the iPod, and then ignored
# (deleted) also here, as documented in the iPodLinux.org/iTunesDB page
#mhoflength = self.dblines[-1][1][2]-24
# Got the length of this second string MHOD, we subtract its total length from its parents MHBA and MHSD
#self.dblines[self.latest['mhba']][1][2] -= mhoflength
#self.dblines[self.latest['mhba']][1][2] -= 24
#self.dblines[self.latest['mhsd']][1][2] -= mhoflength
#self.dblines[self.latest['mhsd']][1][2] -= 24
#del self.dblines[-1]
#self.db.read(mhoflength)
self.dblines[-1][0] = 'mhob'
mhodsubname = 'mhoa'
self.secondscan = True
else:
self.dblines[-1][0] = 'mhoc'
elif mhod_type == 1:
self.dblines[-1][0] = 'mhob'
mhodsubname = 'mhoa'
elif mhod_type == 3:
mhodsubname = 'mhos'
mhod_name = self.dblines[-1][0]
self.dblines[-1][2] = self.latest[self.parents[mhod_name]]
self.latest[mhod_name] = len(self.dblines)-1
if mhod_name == 'mhod' or mhod_name == 'mhob' or mhod_name == 'mhog': # string MHOD
mhodslen = unpack('<I', self.db.read(4))[0]; self.db.seek(-4, 1)
mhodsformat = '<3I%ss%sx' % (mhodslen, self.dblines[-1][1][5])
self.dblines.append([mhodsubname, list(unpack(mhodsformat, self.db.read(calcsize(mhodsformat)))), self.latest[mhod_name]])
self.latest[mhodsubname] = len(self.dblines)-1
else:
self.latest[dbline] = len(self.dblines)-1
self.db.close()
if self.restartfromscratch:
self._FromScratchApply()
self._RetrieveThumbnailsFilenameParts()
if self.secondscan:
self._SecondScan()
def _SecondScan(self):
""" Handle Photo Database files generated by iPhoto """
try:
for x in range(len(self.dblines)):
if self.dblines[x][0] == 'mhii':
if self.dblines[x+1][0] == 'mhob':
self.dblines[1][1][2] -= self.dblines[x+1][1][2]
self.dblines[x][1][2] -= self.dblines[x+1][1][2]
self.dblines[x][1][3] -= 1
del self.dblines[x+1]
del self.dblines[x+1]
if self.dblines[x][0] == 'mhsd':
latest_mhsd = x
if self.dblines[x][0] == 'mhba':
if self.dblines[x+3][0] == 'mhob':
self.dblines[latest_mhsd][1][2] -= self.dblines[x+3][1][2]
self.dblines[x][1][2] -= self.dblines[x+3][1][2]
self.dblines[x][1][3] -= 1
del self.dblines[x+3]
del self.dblines[x+3]
except:
pass
def _RetrieveThumbnailsFilenameParts(self):
""" Retrieve thumbnail filename parts (to name files in the Thumbs directory) """
self.thumbfnames = []
dblines_len = len(self.dblines)
# iPod Photo, Color and 5G seem to have 4 different thumbnails types; iPod Nano seems to have just 2 types.
for x in range(dblines_len-4, dblines_len):
if self.dblines[x][0] == 'mhif':
self.thumbfnames.append((self.dblines[x][1][5], self.dblines[x][1][4]))
self.thumbfnames = dict(self.thumbfnames)
def WriteDatabaseLog(self):
""" Write a .gpixpod.log debug file in the Photos directory """
logfile = open(os.path.join(self.dbdir, '.gpixpod.log'), 'w+')
for dbline in self.dblines:
print >> logfile, dbline
logfile.close()
def _SaveDB(self, filename=None):
""" Write all the changes back to the database file """
if filename == None:
filename = self.dbname
if filename == self.dbname and os.path.isfile(filename): # if saving to the same file, creating backup
backup_filename = os.path.join(self.dbdir, 'Photo Database.gpixpodbak')
if os.path.isfile(backup_filename):
os.remove(backup_filename)
os.rename(self.dbname, backup_filename)
self._PrepareToSave()
db = open(filename, 'w+b')
try:
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhos' or self.dblines[x][0] == 'mhoa':
db.write(pack('<3I%ss%sx' % (self.dblines[x][1][0], self.dblines[x-1][1][5]), *self.dblines[x][1]))
elif self.dblines[x][0] == 'mhoe' or self.dblines[x][0] == 'mhoc' or self.dblines[x][0] == 'mhob':
db.write(pack(self.formats['mhod'], *self.dblines[x][1]))
else:
db.write(pack(self.formats[self.dblines[x][0]], *self.dblines[x][1]))
except error:
print x, self.dblines[x][0]
db.close()
def Save(self, filename=None):
""" Save all """
self._UpdateThumbsOffsets()
self._ThumbsRebuild()
self._UpdateThumbsIDs()
self._ThumbsChecksumStore()
self._SaveDB(filename)
def Albums(self):
""" Get current photo albums """
result = []
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhba':
albumpics = []
picsn = self.dblines[x][1][4]
for y in range(0, picsn):
albumpics.append(self.dblines[x+3+y][1][4])
album = Album(self.dblines[x+2][1][3].decode("utf-8"), albumpics)
result.append(album)
return result
def Photos(self):
""" Get current photos details """
result = []
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhii':
if self.dblines[x][1][3] == 5 or self.dblines[x][1][3] == 3: # Photo has a full resolution copy.
fullres = self.dblines[x+4][1][3].decode("utf-16-le")
else:
fullres = None
photo = Photo(self.dblines[x][1][4], fullres)
result.append(photo)
return result
def AddAlbum(self, name):
""" Add a new album """
namelen = len(name)
namepad = getPadding(namelen)
mhob_totlen = 24+12+namelen+namepad
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhla':
self.dblines[x][1][2] += 1 # Incrementing number of albums
self.dblines[x-1][1][2] += 148+mhob_totlen # Incrementing total length of parent MHSD
mhla = x
if self.dblines[x][0] == 'mhba':
last_mhba = x # Getting the latest album list offset: needed to generate new IDs
self.dblines[0][1][7] += 1 # Incrementing next ID field in MHFD
# If iPod Nano, assuming 4 elements at the end of the list: MHSD, MHLF and 2 MHIF
if self.ipod_model == 'Nano':
afteralb = 4
# Else assuming 6 elements at the end of the list: MHSD, MHLF and 4 MHIF
else:
afteralb = 6
self.dblines.insert(len(self.dblines)-afteralb, ['mhba', ['mhba', 148, 148+mhob_totlen, 1, 0, (self.dblines[last_mhba][1][5]+1), 0, 393216, 0, 0, 0, 0, 0, 0, 0, \
(self.dblines[last_mhba][1][15]+self.dblines[last_mhba][1][4]+1)], mhla])
self.dblines.insert(len(self.dblines)-afteralb, ['mhob', ['mhod', 24, mhob_totlen, 1, 0, namepad], len(self.dblines)-6])
self.dblines.insert(len(self.dblines)-afteralb, ['mhoa', [namelen, 1, 0, name], len(self.dblines)-6])
def _GetMHNIs(self, mhii):
""" Get MHNI offsets for the specified MHII and for the current iPod model """
mhii_children = self.dblines[mhii][1][3]
if mhii_children == 5:
return (mhii+6, mhii+10, mhii+14, mhii+18)
elif mhii_children == 4:
return (mhii+2, mhii+6, mhii+10, mhii+14)
elif mhii_children == 3:
return (mhii+6, mhii+10)
elif mhii_children == 2:
return (mhii+2, mhii+6)
# Maybe we should use iPod 5G MHNI offsets as default in 'else'?
def _HasFullResolution(self, mhii):
""" Return True if the photo of the specified MHII has actually a full resolution image attached """
# Do it checking children
if self.dblines[mhii][1][3] == 5 or self.dblines[mhii][1][3] == 3:
return True
elif self.dblines[mhii][1][3] == 4 or self.dblines[mhii][1][3] == 2:
return False
# Raise exception with 'else'?
def HasFullResolution(self, photo_id):
""" Return True if the photo with the specified ID has actually a full resolution image attached """
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhii' and self.dblines[x][1][4] == photo_id:
return self._HasFullResolution(x)
break
def _ThumbsFilenames(self):
""" Get thumbs filenames """
j = os.path.join
photos = [str(x[1][4]) for x in self.dblines if x[0] == 'mhii']
return [j(self.sglthumbsdir, '%sx%s' % (x[0], x[1]), p) for x in self.thumbsizes for p in photos]
def _ThumbsChecksum(self):
""" Create a checksum for all single thumbs extracted """
photos = self._ThumbsFilenames()
chksum = []
for ph in photos:
#pf = open(ph)
#chksum.append((ph, adler32(pf.read())))
#pf.close()
chksum.append((ph, os.stat(ph)[8]))
return dict(chksum)
def _ThumbsChecksumStore(self):
""" Store a checksum for all single thumbs extracted """
fileWrite(self.thumbschecksum, cPickle.dumps(self._ThumbsChecksum()), 'w+')
def _ThumbsChecksumVerify(self):
""" Verify the single thumbs against previously created checksums """
try:
stored_chksumf = open(self.thumbschecksum)
stored_chksum = cPickle.load(stored_chksumf)
stored_chksumf.close()
chksum = self._ThumbsChecksum()
result = True
self.progress = pgrs = 0
totnum = len(stored_chksum)*1.0
for t, n in stored_chksum.iteritems():
pgrs += 1
self.progress = pgrs/totnum
if chksum[t] != n:
print chksum[t]
print n
result = False
break
self.progress = 1.0
return result
except:
self.progress = 1.0
return False
def _ThumbsExtract(self):
""" Extract all the thumbs in a specified hidden directory with a subdirectory per size format """
j = os.path.join
tdirs = [j(self.sglthumbsdir, '%sx%s' % (x[0], x[1])) for x in self.thumbsizes]
[os.makedirs(x) for x in tdirs if not os.path.isdir(x)]
pics = [str(x[1][4]) for x in self.dblines if x[0] == 'mhii']
ithumbs = [open(j(self.dbdir, 'Thumbs', 'F%s_1.ithmb' % self.thumbfnames[x]), 'rb') for x in self.thumbdims]
#[[fileWrite(j(d, x), t.read(s)) for d, t, s in zip(tdirs, ithumbs, self.thumbdims)] for x in pics]
totnum = len(tdirs)*len(pics)*1.0
self.progress = pgrs = 0
for x in pics:
for d, t, s in zip(tdirs, ithumbs, self.thumbdims):
f = open(j(d, x), 'w+b')
f.write(t.read(s))
f.close()
pgrs += 1
self.progress = pgrs/totnum
[x.close() for x in ithumbs]
self.thumbs_extracted = True
self._ThumbsChecksumStore()
self.progress = 1.0
def _ThumbsExtractChecking(self): # Checking should not be needed, so _ThumbsExtract should be normally the one to use
""" Extract all the thumbs in a specified hidden directory with a subdirectory per size format, checking database offset and size values """
thumb_dirs = []
thumbdims = list(self.thumbdims)
thumbsizes_len = len(self.thumbsizes)
for t in range(0, thumbsizes_len):
thumb_dirs.append(os.path.join(self.sglthumbsdir, '%sx%s' % (self.thumbsizes[t][0], self.thumbsizes[t][1])))
if not os.path.isdir(thumb_dirs[-1]):
os.makedirs(thumb_dirs[-1])
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhii':
current_id = self.dblines[x][1][4]
for y in self._GetMHNIs(x):
d = thumbdims.index(self.dblines[y][1][6])
thumbs = open(os.path.join(self.dbdir, 'Thumbs', 'F%s_1.ithmb' % self.thumbfnames[self.dblines[y][1][6]]), 'rb')
thumb = open(os.path.join(thumb_dirs[d], str(current_id)), 'w+b')
thumbs.seek(self.dblines[y][1][5])
thumb.write(thumbs.read(self.dblines[y][1][6]))
thumb.close()
thumbs.close()
self.thumbs_extracted = True
def _ThumbsRebuild(self):
""" Rebuild the thumbnails file from the single thumbs previously extracted """
maindir = self.sglthumbsdir
if len(self.PX_REMOVED) > 0:
thumbsopenmode = 'w+b'
else:
thumbsopenmode = 'ab'
thumbs = map(lambda(y): open(y, thumbsopenmode), map(lambda(x): os.path.join(self.dbdir, 'Thumbs', 'F%s_1.ithmb' % self.thumbfnames[x]), self.thumbdims))
tdirs = map(lambda(t): os.path.join(maindir, '%sx%s' % (t[0], t[1])), self.thumbsizes)
if len(self.PX_REMOVED) > 0:
tfiles = map(sorted, map(os.listdir, tdirs))
else:
tfiles = [map(str, self.PX_ADDED)]*len(tdirs)
#totnum = len(tfiles)*len(tfiles[0])*1.0
tfileslen = len(tfiles[0])
totnum = sum([x*tfileslen for x in self.thumbdims])*1.0
self.progress = pgrs = 0.0
for thmb, tdir, tf in zip(thumbs, tdirs, tfiles):
tf = map(lambda(f): os.path.join(tdir, f), tf)
#map(lambda(k): thmb.write(k.read()), tf)
for kf in tf:
k = open(kf, 'rb')
thmb.write(k.read())
pgrs += k.tell()
self.progress = pgrs/totnum
k.close()
thmb.close()
#self.thumbs_extracted = False
#self._ThumbsChecksumStore()
self.progress = 1.0
#
#for t in range(0, len(self.thumbsizes)):
# tdir = os.path.join(maindir, '%sx%s' % (self.thumbsizes[t][0], self.thumbsizes[t][1]))
# tdirlist = os.listdir(tdir)
# tdirlist.sort()
# thumbs = open(os.path.join(self.dbdir, 'Thumbs', 'F%s_1.ithmb' % self.thumbfnames[self.thumbdims[t]]), 'w+b')
# for f in tdirlist:
# thumbname = os.path.join(tdir, f)
# thumb = open(thumbname, 'rb')
# thumbs.write(thumb.read())
# thumb.close()
# #os.remove(thumbname)
# thumbs.close()
# #os.rmdir(tdir)
#os.rmdir(maindir)
#self.thumbs_extracted = False
def _UpdateThumbsOffsets(self):
""" Update the offsets for each thumbnail in MHNI definitions for all photos """
thumboffsets = [0, 0, 0, 0]
thumbdims = list(self.thumbdims)
thumbsizes_len = len(self.thumbsizes)
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhii':
for y in self._GetMHNIs(x):
try:
d = thumbdims.index(self.dblines[y][1][6])
self.dblines[y][1][5] = thumboffsets[d]
thumboffsets[d] += self.dblines[y][1][6]
except IndexError:
for line in self.dblines:
print line
raise "Index Error: %s %s" % (y, self.dblines[y])
def RemoveThumbsCache(self, thumbsdir=None):
""" Remove the cache of the single thumbnails """
try:
if thumbsdir == None:
maindir = self.sglthumbsdir
else:
maindir = thumbsdir
if os.path.isdir(maindir):
for t in self.thumbsizes:
td = os.path.join(maindir, '%sx%s' % (t[0], t[1]))
if os.path.isdir(td):
for tf in os.listdir(td):
tfn = os.path.join(td, tf)
if os.path.isfile(tfn):
os.remove(tfn)
os.rmdir(td)
os.rmdir(maindir)
except:
pass
def _RemovePhotoThumbs(self, photoid):
""" Remove photo thumbs """
for t in self.thumbsizes:
thumbdir = os.path.join(self.sglthumbsdir, '%sx%s' % (t[0], t[1]))
os.remove(os.path.join(thumbdir, str(photoid)))
def _RemovePhotoFromList(self, photoid):
""" Remove a photo from the photos list (MHLI) """
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhii':
if self.dblines[x][1][4] == photoid:
photo = x
elif self.dblines[x][1][4] > photoid:
#self.dblines[x][1][4] -= 1 # Adjusting IDs for next photos MOVED TO NEW FUNCTION: it CREATED problems when removing!!!
self.dblines[x][1][5] -= 1
# for t in (x+6, x+10, x+14, x+18):
# self.dblines[t][1][5] -= self.dblines[t][1][6] # Adjusting thumbnails offsets for next images
if self.dblines[x][0] == 'mhba':
self.dblines[x][1][5] -= 1 # Related decrement of subsequent album IDs
self.dblines[1][1][2] -= self.dblines[photo][1][2] # Decrementing total length of parent MHSD
self.dblines[2][1][2] -= 1 # Decrementing total number of pictures
self.dblines[0][1][7] -= 1 # Decrementing next ID field in MHFD
# Removing thumbnails
self._RemovePhotoThumbs(photoid)
self.PX_REMOVED.append(photoid)
photo_fullres = 0
photo_children = self.dblines[photo][1][3]
if self._HasFullResolution(photo): # Then deleting the full resolution image
self._DeleteFullResolution(self.dblines[photo+4][1][3])
del self.dblines[photo:photo+(4*photo_children+1)]
def _AddPhotoThumbs(self, filename, photoid, behaviour='Fit', autorotate=None):
""" Add photo thumbs """
thmbths = []
thumbsizes_len = len(self.thumbsizes)
for t in range(0, thumbsizes_len):
thmbth = Thread(target=self._AddPhotoThumb, args=[filename, photoid, t, behaviour, autorotate])
thmbth.start()
thmbths.append(thmbth)
for thmbth in thmbths:
thmbth.join()
def _AddPhotoThumb(self, filename, photoid, t, behaviour='Fit', autorotate=None):
""" Add photo thumb """
if behaviour == 'Zoom':
behaviourn = 1;
elif behaviour == 'Stretch':
behaviourn = 2;
else:
behaviourn = 0;
if autorotate == 'CW':
autorotaten = 1;
elif autorotate == 'CCW':
autorotaten = 2;
else:
autorotaten = 0;
thumbdn = os.path.join(self.sglthumbsdir, '%sx%s' % (self.thumbsizes[t][0], self.thumbsizes[t][1]))
thumbfn = os.path.join(thumbdn, str(photoid))
if not os.path.isdir(thumbdn):
os.makedirs(thumbdn)
if t == 0 and len(self.thumbsizes) != 2: # then is not TV photo neither iPod Nano
toInterlacedUYVY(filename, behaviourn, autorotaten, thumbfn)
else:
if t == 0 and self.ipod_model == 'Nano':
toRGB565(filename, self.thumbsizes[t][0], self.thumbsizes[t][1], False, False, behaviourn, autorotaten, thumbfn)
elif t == 1 and (self.ipod_model == 'Photo' or self.ipod_model == 'Color'):
toRGB565(filename, self.thumbsizes[t][0], self.thumbsizes[t][1], True, True, behaviourn, autorotaten, thumbfn)
else:
toRGB565(filename, self.thumbsizes[t][0], self.thumbsizes[t][1], True, False, behaviourn, autorotaten, thumbfn)
def _AddPhotoToList(self, filename, photofullres=None, behaviour='Fit', autorotate=None):
""" Add a new photo to the photos list (MHLI) """
if photofullres == None: # Then using DB global default
photofullres = self.dbcopyfullres
name = os.path.basename(filename) # check maximum filename string length FIXME
picdate = self._PhotoCreationDate(filename)
albums = 0
last_mhii = None
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhii':
last_mhii = x
if self.dblines[x][0] == 'mhba':
self.dblines[x][1][5] += 1 # Related increment of subsequent album IDs
albums += 1
mhli = 2
if last_mhii == None:
mhpos = mhii = 3
photo_id = 100
else:
if self._HasFullResolution(last_mhii):
last_mhii_fullres = 4
else:
last_mhii_fullres = 0
mhpos = mhii = last_mhii+last_mhii_fullres+4*len(self.thumbsizes)+1
photo_id = self.dblines[last_mhii][1][4]+1
# Copying full resolution image, if wanted
if photofullres:
self._CopyFullResolution(filename)
fullresdb = (':Full Resolution:%s:%s:%s:%s' % (picdate[0], picdate[1], picdate[2], name)).encode('utf-16-le')
fullresdb_len = len(fullresdb)
fullresdb_pad = getPadding(fullresdb_len)
fres = fullresdb_len+fullresdb_pad
totfres = 136+fres
freschild = 1
else:
totfres = 0
freschild = 0
# Incrementing total length of parent MHSD
self.dblines[1][1][2] += 152+(len(self.thumbdbsizes)*180)+totfres
# Generating thumbnails
self._AddPhotoThumbs(filename, photo_id, behaviour, autorotate)
self.PX_ADDED.append(photo_id)
# Photo header (MHII)
self.dblines.insert(mhpos, ['mhii', ['mhii', 152, 152+(len(self.thumbdbsizes)*180)+totfres, (freschild+len(self.thumbdbsizes)), photo_id, photo_id+albums,
0, 0, 0, 0, appleTime(picdate), appleTime(picdate), 0], mhli])
self.dblines[mhli][1][2] +=1 # Total number of pictures
self.dblines[0][1][7] += 1 # Incrementing next ID field in MHFD
# Inserting full resolution database details, if wanted
if photofullres:
self.dblines.insert(mhpos+1, ['mhoe', ['mhod', 24, 136+fres, 5, 0, 0], mhpos]); mhpos+=1
self.dblines.insert(mhpos+1, ['mhni', ['mhni', 76, 112+fres, 1, 1, 0, 0, 0, 0, 0, 0], mhpos]); mhpos+=1
self.dblines.insert(mhpos+1, ['mhod', ['mhod', 24, 36+fres, 3, 0, fullresdb_pad], mhpos]); mhpos+=1
self.dblines.insert(mhpos+1, ['mhos', [fullresdb_len, 2, 0, fullresdb], mhpos])
mhpos+=1
# Thumbs
for thumb in range(0, len(self.thumbdbsizes)):
if last_mhii == None:
offset = 0
else:
offset = self.dblines[last_mhii+last_mhii_fullres+2+4*thumb][1][5]+self.thumbdims[thumb]
self.dblines.insert(mhpos+1, ['mhoc', ['mhod', 24, 180, 2, 0, 0], mhii]); mhpos+=1
self.dblines.insert(mhpos+1, ['mhni', ['mhni', 76, 156, 1, self.thumbfnames[self.thumbdims[thumb]], offset, self.thumbdims[thumb],
0, 0, self.thumbdbsizes[thumb][1], self.thumbdbsizes[thumb][0]], mhpos]); mhpos+=1
self.dblines.insert(mhpos+1, ['mhod', ['mhod', 24, 80, 3, 0, 2], mhpos]); mhpos+=1
self.dblines.insert(mhpos+1, ['mhos', [42, 2, 0, (':Thumbs:F%s_1.ithmb' % self.thumbfnames[self.thumbdims[thumb]]).encode('utf-16-le')], mhpos]); mhpos+=1
return photo_id
def _CopyFullResolution(self, filename):
""" Copy the full resolution photo to destination """
name = os.path.basename(filename)
picdate = self._PhotoCreationDate(filename)
dest_dir = os.path.join(self.dbdir, 'Full Resolution', *map(str, picdate[:3]))
if not os.path.isdir(dest_dir):
os.makedirs(dest_dir)
shutil.copyfile(filename, os.path.join(dest_dir, name))
def _DeleteFullResolution(self, fullres_path):
""" Delete the full resolution photo from path (Mac, relative) specified """
#filename = fullres_path.decode('utf-16-le')
filename = fullres_path.decode('utf-16-le')
filename = filename.encode('utf-8')
filename = os.path.join(self.dbdir, *filename.split(':')[1:])
try:
if os.path.isfile(filename):
os.remove(filename)
except OSError:
print 'Problems removing %s.' % filename
def AddPhotoToAlbum(self, photo_id, album_id=0): # Default: adding to library
""" Add a photo to an album """
counter = 0
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhla':
self.dblines[x-1][1][2] += 40 # Incrementing total length of parent MHSD
if self.dblines[x][0] == 'mhba':
if counter == album_id:
mhba = x
break
counter+=1
self.dblines[mhba][1][2] += 40 # Incrementing total length of parent album
self.dblines[mhba][1][4] += 1 # Incrementing number of pictures for parent album
self.dblines.insert(mhba+2+self.dblines[mhba][1][4], ['mhia', ['mhia', 40, 40, 0, photo_id], mhba])
def RemovePhotoFromAlbum(self, photo_id, album_id):
""" Remove a photo reference from a specified photo album """
mhbas = 0
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhla':
mhla = x
if self.dblines[x][0] == 'mhba':
mhba = x
mhbas += 1
if self.dblines[x][0] == 'mhia':
if self.dblines[x][1][4] == photo_id and album_id+1 == mhbas:
self.dblines[mhla-1][1][2] -= 40
self.dblines[mhba][1][4] -= 1
self.dblines[mhba][1][2] -= 40
del self.dblines[x]
break
def _RemovePhotoFromAlbums(self, photo_id):
""" Remove a photo from all the albums which link to it """
photos_to_remove = []
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhla':
mhla = x
if self.dblines[x][0] == 'mhba':
current_mhba = x
if self.dblines[x][0] == 'mhia':
if self.dblines[x][1][4] == photo_id:
photos_to_remove.append(x)
self.dblines[current_mhba][1][2] -= 40
self.dblines[current_mhba][1][4] -= 1
self.dblines[mhla-1][1][2] -= 40
#elif self.dblines[x][1][4] > photo_id: # Moved to UpdatePhotoIDs: Created problems when removing!
# self.dblines[x][1][4] -= 1
photos_to_remove.sort(reverse=True)
for photo in photos_to_remove:
del self.dblines[photo]
def AddPhoto(self, filename, album_id, photofullres=None, behaviour='Fit', autorotate=None): # photofullres passed as boolean
""" Add a new photo to the file database """
photo_id = self._AddPhotoToList(filename, photofullres, behaviour, autorotate)
self.AddPhotoToAlbum(photo_id, 0) # First adding to the library
if album_id != 0:
self.AddPhotoToAlbum(photo_id, album_id)
def AddFullResolutionToPhoto(self, filename, photo_id):
""" Add a full resolution image to an existing photo, if currently without """
photos = self.Photos()
if len(photos) > (photo_id-100):
if photos[photo_id-100].fullres == None:
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhii' and self.dblines[x][1][4] == photo_id:
if self._HasFullResolution(x) == False:
name = os.path.basename(filename)
picdate = self._PhotoCreationDate(filename)
self._CopyFullResolution(filename)
fullresdb = (':Full Resolution:%s:%s:%s:%s' % (picdate[0], picdate[1], picdate[2], name)).encode('utf-16-le')
fullresdb_len = len(fullresdb)
fullresdb_pad = getPadding(fullresdb_len)
fres = fullresdb_len+fullresdb_pad
totfres = 136+fres
# Incrementing total length of parent MHSD
self.dblines[1][1][2] += totfres
# Incrementing total length and children of parent MHII
self.dblines[x][1][2] += totfres
self.dblines[x][1][3] += 1
# Inserting full resolution database details
mhpos = x
self.dblines.insert(mhpos+1, ['mhoe', ['mhod', 24, 136+fres, 5, 0, 0], mhpos]); mhpos+=1
self.dblines.insert(mhpos+1, ['mhni', ['mhni', 76, 112+fres, 1, 1, 0, 0, 0, 0, 0, 0], mhpos]); mhpos+=1
self.dblines.insert(mhpos+1, ['mhod', ['mhod', 24, 36+fres, 3, 0, fullresdb_pad], mhpos]); mhpos+=1
self.dblines.insert(mhpos+1, ['mhos', [fullresdb_len, 2, 0, fullresdb], mhpos])
break
def RemoveFullResolutionFromPhoto(self, photo_id):
""" Remove the full resolution image attached to an existing photo """
photos = self.Photos()
if len(photos) > (photo_id-100):
if photos[photo_id-100].fullres != None:
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhii' and self.dblines[x][1][4] == photo_id:
if self._HasFullResolution(x):
self._DeleteFullResolution(self.dblines[x+4][1][3])
# Getting total length of MHOE (full resolution image reference MHOD container)
totfres = self.dblines[x+1][1][2]
# Decrementing total length of parent MHSD
self.dblines[1][1][2] -= totfres
# Decrementing total length and children of parent MHII
self.dblines[x][1][2] -= totfres
self.dblines[x][1][3] -= 1
# Removing full resolution database details lines
del self.dblines[x+1:x+5]
break
def RemovePhoto(self, photo_id):
""" Remove a photo from the file database """
self._RemovePhotoFromAlbums(photo_id)
self._RemovePhotoFromList(photo_id)
def _UpdatePhotoIDs(self):
""" Update IDs of the photos avoiding more than +1 differences between numbers """
mhii = 100
IDs = {}
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhii':
IDs[self.dblines[x][1][4]] = mhii
self.dblines[x][1][4] = mhii
mhii += 1
if self.dblines[x][0] == 'mhia':
self.dblines[x][1][4] = IDs[self.dblines[x][1][4]]
def _UpdateThumbsIDs(self):
""" Update filenames of the thumbs avoiding more than +1 differences between numbers """
photoids = [str(x[1][4]) for x in self.dblines if x[0] == 'mhii']
thumbnewnames = self._ThumbsFilenames()
thumbnames = []
for t in self.thumbsizes:
tn = os.path.join(self.sglthumbsdir, '%sx%s' % (t[0], t[1]))
tc = [os.path.join(tn, tf) for tf in sorted(os.listdir(tn))]
thumbnames.extend(tc)
thumbnewnames.sort()
thumbnames.sort()
[os.rename(old, new) for old, new in zip(thumbnames, thumbnewnames) if old != new]
def RemoveAlbum(self, album_id):
""" Remove an existing album """
if album_id == 0:
print "Could not remove photo library!"
sys.exit()
try:
mhba = 0
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhla':
mhla = x
if self.dblines[x][0] == 'mhba':
if mhba == album_id:
self.dblines[mhla][1][2] -= 1 # Decrementing number of albums in album list (MHLA)
self.dblines[mhla-1][1][2] -= self.dblines[x][1][2] # Decrementing album total length from parent MHSD
del self.dblines[x:x+self.dblines[x][1][4]+3] # Actually deleting slice of the album, related MHOD and children MHIAs
break
mhba += 1
except IndexError:
print "Index error in RemoveAlbum: seems not fatal, but should be fixed"
def RemoveAlbumAndPhotos(self, album_id):
""" Remove an existing album actually deleting all its photos, too """
albums = self.Albums()
album_pics = albums[album_id].pics
for pic in album_pics:
self.RemovePhoto(pic)
mhba = 0
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhla':
mhla = x
if self.dblines[x][0] == 'mhba':
if mhba == album_id:
self.dblines[mhla][1][2] -= 1 # Decrementing number of albums in album list (MHLA)
self.dblines[mhla-1][1][2] -= self.dblines[x][1][2] # Decrementing album total length from parent MHSD
del self.dblines[x:x+3] # Actually deleting slice of the album, related MHOD and string subheader
break
mhba += 1
def RenameAlbum(self, album_id, new_album_name): # Maybe we should check the length of the provided name...
""" Rename a photo album """
new_album_name = new_album_name.encode('utf-8')
new_album_name_len = len(new_album_name)
new_album_name_pad = getPadding(new_album_name_len)
mhba = 0
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhla':
mhla = x
if self.dblines[x][0] == 'mhba':
if mhba == album_id:
old_length = self.dblines[x+2][1][0] + self.dblines[x+1][1][5] # Old string name + padding length
new_length = new_album_name_len + new_album_name_pad
if old_length != new_length:
diff_length = new_length - old_length # Needed to update parent containers' total lengths
self.dblines[mhla-1][1][2] += diff_length # Updating parent MHSD
self.dblines[x][1][2] += diff_length # Updating parent album
self.dblines[x+1][1][2] += diff_length # Updating parent string MHOD (MHOB)
self.dblines[x+1][1][5] = new_album_name_pad
self.dblines[x+2][1][0] = new_album_name_len
self.dblines[x+2][1][3] = new_album_name
mhba += 1
def FindLostPhotos(self, in_library=False):
""" Find photos currently not associated to any album """
lostphotos = []
mhba = 0
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhii':
lostphotos.append(self.dblines[x][1][4])
if self.dblines[x][0] == 'mhba':
if in_library == True:
if mhba == 0:
process = True
else:
process = False
else:
if mhba > 0:
process = True
else:
process = False
mhba += 1
if self.dblines[x][0] == 'mhia' and process == True:
if lostphotos.count(self.dblines[x][1][4]) > 0:
lostphotos.remove(self.dblines[x][1][4])
return lostphotos
def _PrepareToSave(self):
""" Adjust the contents list to be written to the database file, updating total lengths, number of children, etc. """
# Adjusting total length of MHFD
self.dblines[0][1][2] = 132
for x in range(0, len(self.dblines)):
if self.dblines[x][0] == 'mhsd':
self.dblines[0][1][2] += self.dblines[x][1][2]
self._UpdatePhotoIDs()
def Rescue(self):
""" Check and fix a Photo Database for broken parts and try to rescue it """
mhfd = 0
mhsd = []
mhii = []
mhba = []
for x in range(len(self.dblines)):
if self.dblines[x][0] == 'mhsd':
mhsd.append(x)
self.dblines[x][1][2] = self.lengths['mhsd'] # Annul total length to single MHSD length
elif self.dblines[x][0] == 'mhli':
mhli = x
self.dblines[x][1][2] = 0 # Annul number of photos
self.dblines[mhsd[-1]][1][2] += self.lengths['mhli']
elif self.dblines[x][0] == 'mhii':
mhii.append(x)
self.dblines[mhsd[-1]][1][2] += self.lengths['mhii']
self.dblines[mhli][1][2] += 1 # Incrementing number of photos
self.dblines[x][1][2] = self.lengths['mhii'] # Annul total length to single MHII length
self.dblines[x][1][3] = 0 # Annul number of children
elif self.dblines[x][0] == 'mhoe' or self.dblines[x][0] == 'mhoc':
self.dblines[x+1][1][2] = self.lengths['mhni'] + self.dblines[x+2][1][2] # Total child MHNI length = standard MHNI length + its child string MHOD length
self.dblines[x][1][2] = self.dblines[x][1][1] + self.dblines[x+1][1][2] # Total MHOE/MHOC length = its length (usually 24 as MHOD) + its child MHNI total length
self.dblines[mhsd[-1]][1][2] += self.dblines[x][1][2] # Incrementing parent MHSD total length
self.dblines[mhii[-1]][1][2] += self.dblines[x][1][2] # Incrementing parent MHII total length
self.dblines[mhii[-1]][1][3] += 1 # Incrementing parent MHII number of children
elif self.dblines[x][0] == 'mhla':
mhla = x
self.dblines[x][1][2] = 0 # Annul number of albums
self.dblines[mhsd[-1]][1][2] += self.lengths['mhla']
elif self.dblines[x][0] == 'mhba':
mhba.append(x)
self.dblines[mhsd[-1]][1][2] += self.lengths['mhba']
self.dblines[mhla][1][2] += 1 # Incrementing number of albums
self.dblines[x][1][2] = self.lengths['mhba'] # Annul total length to single MHBA length
self.dblines[x][1][4] = 0 # Annul number of photo refs
elif self.dblines[x][0] == 'mhob':
self.dblines[mhsd[-1]][1][2] += self.dblines[x][1][2] # Incrementing parent MHSD total length
self.dblines[mhba[-1]][1][2] += self.dblines[x][1][2] # Incrementing parent MHBA total length with the total size of the child string MHOD
elif self.dblines[x][0] == 'mhia':
self.dblines[mhsd[-1]][1][2] += self.dblines[x][1][2] # Incrementing parent MHSD total length
self.dblines[mhba[-1]][1][2] += self.dblines[x][1][2] # Incrementing parent MHBA total length with the size of MHIA
self.dblines[mhba[-1]][1][4] += 1 # Incrementing number of children photos
elif self.dblines[x][0] == 'mhlf':
mhlf = x
self.dblines[x][1][2] = 0 # Annul number of thumbnails files descriptors
self.dblines[mhsd[-1]][1][2] += self.lengths['mhlf']
elif self.dblines[x][0] == 'mhif':
self.dblines[mhsd[-1]][1][2] += self.dblines[x][1][2]
self.dblines[mhlf][1][2] += 1
# Updating MHFD total length summing the total lenghts of the children MHSDs
self.dblines[0][1][2] = self.lengths['mhfd']
for x in mhsd:
self.dblines[0][1][2] += self.dblines[x][1][2]
# STRUCTURES
class Album:
""" Album structure """
def __init__(self, name, pics):
""" Fill the album structure """
self.name = name
self.pics = pics
class Photo:
""" Photo structure """
def __init__(self, id, fullres):
" Fill the photo structure """
self.id = id
self.fullres = fullres
if __name__ == '__main__':
try:
DB = MH('./Photo Database')
for album in DB.Albums():
print album.name, '-', len(album.pics), 'pictures:', album.pics
for photo in DB.Photos():
print photo.id, photo.fullres
except KeyboardInterrupt:
print "Interruption requested by user"
|