#!/usr/bin/python
# Copyright (c) 2004 Danilo Segan <danilo@kvota.net>.
#
# This file is part of Philips HDD100 Toolkit for Linux.
#
# This 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.
#
# It 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; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

# Danilo did the programming, but it was simple thanks to the excellent
# documentation over at
#
#   http://solonweb.free.fr/tech.htm
#

import os, ID3, zlib, struct, sys

recordsize = 408

field_offsets = { "hidden" : 0, # 0/1
                  "reserved1" : 4, # 0
                  "reserved2" : 8, # 0
                  "path" : 12,
                  "name" : 68,
                  "bitrate" : 100, # bits per second
                  "samplerate" : 104, # bits per second
                  "duration" : 108, # seconds
                  "artist" : 112,
                  "album" : 192,
                  "genre" : 272,
                  "title" : 312,
                  "tracknumber" : 392, # track number
                  "year" : 396,
                  "size" : 400, # file size in bytes
                  "source" : 404 # reserved, 0
                  }

def get_files(musicdir, files):
    """Find all the files recursively in directory 'musicdir'.

    Also modifies record offsets if necessary."""
    import mp3
    maxpath = 0
    maxname = 0
    global field_offsets
    for file in os.listdir(musicdir):
        fullname = os.path.join(musicdir, file)
        if os.path.isdir(fullname):
            get_files(fullname,files)
        elif fullname[-3:].lower() == 'mp3':
            header = mp3.detect_mp3(fullname)
            if not header:
                print "Problem with %s: doesn't look like MP3 file" % (fullname)
                #header = { "bitrate" : 128*1000, "sampling_frequency" : 44100, "time" : "180" }
                continue
            record = { "file" : fullname,
                       "path" : os.path.dirname(fullname),
                       "name" : os.path.basename(fullname),
                       "bitrate" : header["bitrate"]*1000,
                       "sampling_frequency" : header["sampling_frequency"],
                       "duration" : header["time"] }
            try:
                id3info = ID3.ID3(fullname)
                for key in id3info.keys():
                    record[key.lower()] = id3info[key]
            except:
                pass

            if 'title' not in record.keys():
                (record['title'], ext) = os.path.splitext(record['name'])

            for key in ['path', 'name', 'album', 'artist', 'title', 'genre']:
                if not key in record.keys():
                    record[key] = u"unknown"
            for key in ['year', 'tracknumber', 'hidden', 'source']:
                if not key in record.keys():
                    record[key] = 0

            if len(record["name"]) > maxname:
                maxname = len(record["name"])
            if len(record["path"]) > maxpath:
                maxpath = len(record["path"])
            print record['file']
            trackid = len(files)
            record['trackid'] = trackid
            files.append(record)
            
#     #print "Adjusting path length to %d." % maxpath
#     ln = field_offsets["name"] - field_offsets["path"]
#     newlen = maxpath * 2
#     if newlen > ln:
#         diff = newlen - ln
#         if diff % 4 == 2:
#             diff += 2
#         field_offsets["name"] = field_offsets["name"] + diff
#     else:
#         diff = 0

#     #print "Adjusting name length to %d." % maxname
#     ln = field_offsets["bitrate"] - field_offsets["name"]
#     newlen = maxname * 2
#     if newlen > ln:
#         diff = newlen - ln + diff

#     if diff % 4 == 2:
#         diff += 2

#     for field in ['bitrate', 'samplerate', 'duration', 'artist', 'album',
#                   'genre', 'title', 'tracknumber', 'year', 'size', 'source']:
#         field_offsets[field] += diff
#     #print field_offsets
#     global recordsize
#     recordsize += diff


def field_format():
    format = "III" # hidden, reserved1, reserved2
        
    # file path
    len = field_offsets["name"] - field_offsets["path"]
    format += str(len) + "s"
    
    # file name
    len = field_offsets["bitrate"] - field_offsets["name"]
    format += str(len) + "s"
    
    format += "III" # bitrate, samplerate, duration
    
    # artist
    len = field_offsets["album"] - field_offsets["artist"]
    format += str(len) + "s"
    
    # album
    len = field_offsets["genre"] - field_offsets["album"]
    format += str(len) + "s"
    
    # genre
    len = field_offsets["title"] - field_offsets["genre"]
    format += str(len) + "s"
    
    # title
    len = field_offsets["tracknumber"] - field_offsets["title"]
    format += str(len) + "s"
    
    format += "IIII" # track number, year, size (bytes), source
    return format



def write_dat(files):
    """Writes DB5000.DAT file listing all the MP3 files."""
    #try:
    f = open(os.path.join(datadir, "db5000.dat"), "w")
    for file in files:
        format = field_format()

        path = file['path'] + "\\"
        path = path.replace("/", "\\")

        try:
            data = struct.pack( format,
                                0, # hidden
                                0, # reserved 1
                                0, # reserved 2
                                path.encode('UTF-16')[2:], # note the [2:] part here, in order to remove BOM
                                file['name'].encode('UTF-16')[2:],
                                int(file['bitrate']), # bitrate
                                int(file['sampling_frequency']), # samplerate
                                int(file['duration']), # duration
                                file['artist'].encode('UTF-16')[2:],
                                file['album'].encode('UTF-16')[2:],
                                file['genre'].encode('UTF-16')[2:],
                                file['title'].encode('UTF-16')[2:],
                                int(file['tracknumber']), # tracknumber
                                0, #int(file['year']), # year # FIXME
                                os.path.getsize(file['file']), # file size
                                int(0) # source
                                )
            f.write(data)
        except:
            print "Problem with encoding data for file %s." % file['name']
            del(f[data['trackid']])
    f.close()

    #except:
    #    print "Cannot open datadir for writing (%s)." % datadir


def printout(files):
    """Debugging function, print out DAT files."""
    global recordsize
    print recordsize
    for file in files:
        f = open(file, "r+")
        format = field_format()
        trackid = 0
        data = f.read(recordsize)
        while data:
            tuple = struct.unpack(format, data)
            (hidden, res1, res2, path, name, bitrate, samplerate, duration,
             artist, album, genre, title, tracknumber, year, size, source ) = tuple
            
            print "trackid: %d" % trackid
            print "hidden: %d" % hidden
            print "res1: %d" % res1
            print "res2: %d" % res2
            print "path: %s" % path
            print "name: %s" % name
            print "bitrate: %d" % bitrate
            print "samplerate: %d" % samplerate
            print "duration: %d" % duration
            print "artist: %s" % artist
            print "album: %s" % album
            print "genre: %s" % genre
            print "title: %s" % title
            print "track: %d" % tracknumber
            print "year: %d" % year
            print "size: %d" % size
            print "source: %d" % source
            data = f.read(recordsize)

            trackid += 1
                
        f.close()

def sort_table_by_field(dict, field):
    keys = {}
    for elemid in range(0, len(dict)):
        #print elemid
        #print dict[elemid]['artist']
        #dict[elemid]['trackid'] = elemid
        if field == 'artist':
            keys[dict[elemid][field]] = elemid;
        else:
            keys[dict[elemid][field]] = elemid;

    skeys = dict
    if field=='hidden' or field=='tracknumber' or field=='source':
        skeys.sort(lambda m,n: cmp(m[field], n[field]) or cmp(n['trackid'], m['trackid']))
    else:
        skeys.sort(lambda m,n: cmp(unicode(str(m[field]), "ISO-8859-1"), unicode(str(n[field]),"ISO-8859-1")) or cmp(n['trackid'], m['trackid']))
    return skeys

def write_index(files, filename, field):
    """Create different .IDX files."""
    print "Sorting by field '%s'..." % (field)
    sortedfiles = sort_table_by_field(files, field)
    id = 0
    f = open(filename, "wb")
    for file in sortedfiles:
        fileid = file['trackid']
        #print "%d: %s" % (fileid, file[field])
        try:
            data = struct.pack("III", 0, int(fileid), zlib.crc32(str(file[field])))
        except:
            data = struct.pack("III", 0, int(fileid), zlib.crc32(str(file['file'])))
        f.write(data)
    f.close()


files = []
if len(sys.argv) < 3:
    print "Syntax:\n\t%s [DATADIR] [MUSICDIR] ...\n\n\tDATA dir is where DATA files will be stored.\n\tMUSICDIR is directory with music files, you can have more than one.\n" % sys.argv[0]
    sys.exit(1)

datadir = sys.argv[1]
musicdirs = sys.argv[2:]

for musicdir in musicdirs:
    get_files(musicdir, files)

# debugging
#print files

write_dat(files)

# fixup track id numbers
num = 0
for file in files:
    file['trackid'] = num
    #print "%d. %s" % (num, file['title'])
    num += 1

indexes = {
    "@DEV" : "hidden",
    "FNAM" : "name",
    "FPTH" : "path",
    "TALB" : "album",
    "TCON" : "genre",
    "TIT2" : "title",
    "TPE1" : "artist",
    "TRCK" : "tracknumber",
    "XSRC" : "source"
    }

for name in indexes:
    filename = os.path.join(datadir, "DB5000_" + name + ".IDX")
    write_index(files, filename, indexes[name])


count = len(files)

print "%d files added." % count

f = open(os.path.join(datadir, 'db5000.hdr'), "r+")

f.seek(1036)
f.write(struct.pack("I", recordsize))
f.seek(1040)
f.write(struct.pack("I", count))
f.seek(1604)
f.write(struct.pack("I", int((field_offsets["name"] - field_offsets["path"])/2)))
f.seek(2148)
f.write(struct.pack("I", int((field_offsets["bitrate"] - field_offsets["name"])/2)))

#print recordsize
#print field_offsets

offs = struct.pack("14I",
                   field_offsets["reserved2"], 
                   field_offsets["path"], 
                   field_offsets["name"], 
                   field_offsets["bitrate"], 
                   field_offsets["samplerate"], 
                   field_offsets["duration"], 
                   field_offsets["artist"], 
                   field_offsets["album"], 
                   field_offsets["genre"], 
                   field_offsets["title"], 
                   field_offsets["tracknumber"], 
                   field_offsets["year"], 
                   field_offsets["size"], 
                   field_offsets["source"])

f.seek(12964)
f.write(offs)

# debugging, print out constructed DAT file
#printout([os.path.join(datadir,"db5000.dat")])
#printout(["/home/danilo/rad/philips-hdd/data-test/db5000.dat" ])
