automated reformat pass
This commit is contained in:
parent
66fe9bc514
commit
d0d8588e5f
1 changed files with 458 additions and 442 deletions
448
rebuild_db.py
448
rebuild_db.py
|
@ -14,19 +14,22 @@
|
||||||
# along with this program; if not, write to the Free Software
|
# along with this program; if not, write to the Free Software
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
from past.builtins import cmp
|
from __future__ import print_function
|
||||||
|
|
||||||
from builtins import chr
|
from builtins import chr
|
||||||
from builtins import input
|
from builtins import input
|
||||||
from builtins import map
|
from builtins import map
|
||||||
from builtins import range
|
from builtins import range
|
||||||
from past.utils import old_div
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
__title__="KeyJ's iPod shuffle Database Builder"
|
|
||||||
__version__="1.0"
|
from past.builtins import cmp
|
||||||
__author__="Martin Fiedler"
|
from past.utils import old_div
|
||||||
__email__="martin.fiedler@gmx.net"
|
|
||||||
|
__title__ = "KeyJ's iPod shuffle Database Builder"
|
||||||
|
__version__ = "1.0"
|
||||||
|
__author__ = "Martin Fiedler"
|
||||||
|
__email__ = "martin.fiedler@gmx.net"
|
||||||
|
|
||||||
""" VERSION HISTORY
|
""" VERSION HISTORY
|
||||||
1.0-rc1 (2006-04-26)
|
1.0-rc1 (2006-04-26)
|
||||||
|
@ -71,34 +74,35 @@ __email__="martin.fiedler@gmx.net"
|
||||||
* initial public release, Win32 only
|
* initial public release, Win32 only
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import sys, os, os.path, array, getopt, random, fnmatch, operator, string
|
||||||
|
|
||||||
import sys,os,os.path,array,getopt,random,types,fnmatch,operator,string
|
# @formatter:off
|
||||||
|
KnownProps = ('filename', 'size', 'ignore', 'type', 'shuffle', 'reuse', 'bookmark')
|
||||||
KnownProps=('filename','size','ignore','type','shuffle','reuse','bookmark')
|
Rules = [
|
||||||
Rules=[
|
([('filename', '~', '*.mp3')], {'type': 1, 'shuffle': 1, 'bookmark': 0}),
|
||||||
([('filename','~','*.mp3')], {'type':1, 'shuffle':1, 'bookmark':0}),
|
([('filename', '~', '*.m4?')], {'type': 2, 'shuffle': 1, 'bookmark': 0}),
|
||||||
([('filename','~','*.m4?')], {'type':2, 'shuffle':1, 'bookmark':0}),
|
([('filename', '~', '*.m4b')], { 'shuffle': 0, 'bookmark': 1}),
|
||||||
([('filename','~','*.m4b')], { 'shuffle':0, 'bookmark':1}),
|
([('filename', '~', '*.aa')], {'type': 1, 'shuffle': 0, 'bookmark': 1, 'reuse': 1}),
|
||||||
([('filename','~','*.aa')], {'type':1, 'shuffle':0, 'bookmark':1, 'reuse':1}),
|
([('filename', '~', '*.wav')], {'type': 4, 'shuffle': 0, 'bookmark': 0}),
|
||||||
([('filename','~','*.wav')], {'type':4, 'shuffle':0, 'bookmark':0}),
|
([('filename', '~', '*.book.???')], { 'shuffle': 0, 'bookmark': 1}),
|
||||||
([('filename','~','*.book.???')], { 'shuffle':0, 'bookmark':1}),
|
([('filename', '~', '*.announce.???')], { 'shuffle': 0, 'bookmark': 0}),
|
||||||
([('filename','~','*.announce.???')], { 'shuffle':0, 'bookmark':0}),
|
([('filename', '~', '/recycled/*')], {'ignore': 1}),
|
||||||
([('filename','~','/recycled/*')], {'ignore':1}),
|
|
||||||
]
|
]
|
||||||
|
# @formatter:on
|
||||||
|
|
||||||
Options={
|
Options = {
|
||||||
"volume":None,
|
"volume": None,
|
||||||
"interactive":False,
|
"interactive": False,
|
||||||
"smart":True,
|
"smart": True,
|
||||||
"home":True,
|
"home": True,
|
||||||
"logging":True,
|
"logging": True,
|
||||||
"reuse":1,
|
"reuse": 1,
|
||||||
"logfile":"rebuild_db.log.txt",
|
"logfile": "rebuild_db.log.txt",
|
||||||
"rename":False
|
"rename": False
|
||||||
}
|
}
|
||||||
domains=[]
|
domains = []
|
||||||
total_count=0
|
total_count = 0
|
||||||
KnownEntries={}
|
KnownEntries = {}
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
|
@ -108,21 +112,21 @@ def open_log():
|
||||||
global logfile
|
global logfile
|
||||||
if Options['logging']:
|
if Options['logging']:
|
||||||
try:
|
try:
|
||||||
logfile=file(Options['logfile'],"w")
|
logfile = file(Options['logfile'], "w")
|
||||||
except IOError:
|
except IOError:
|
||||||
logfile=None
|
logfile = None
|
||||||
else:
|
else:
|
||||||
logfile=None
|
logfile = None
|
||||||
|
|
||||||
|
|
||||||
def log(line="",newline=True):
|
def log(line="", newline=True):
|
||||||
global logfile
|
global logfile
|
||||||
if newline:
|
if newline:
|
||||||
print(line)
|
print(line)
|
||||||
line+="\n"
|
line += "\n"
|
||||||
else:
|
else:
|
||||||
print(line, end=' ')
|
print(line, end=' ')
|
||||||
line+=" "
|
line += " "
|
||||||
if logfile:
|
if logfile:
|
||||||
try:
|
try:
|
||||||
logfile.write(line)
|
logfile.write(line)
|
||||||
|
@ -154,59 +158,62 @@ def filesize(filename):
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
def MatchRule(props,rule):
|
def MatchRule(props, rule):
|
||||||
try:
|
try:
|
||||||
prop,op,ref=props[rule[0]],rule[1],rule[2]
|
prop, op, ref = props[rule[0]], rule[1], rule[2]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return False
|
return False
|
||||||
if rule[1]=='~':
|
if rule[1] == '~':
|
||||||
return fnmatch.fnmatchcase(prop.lower(),ref.lower())
|
return fnmatch.fnmatchcase(prop.lower(), ref.lower())
|
||||||
elif rule[1]=='=':
|
elif rule[1] == '=':
|
||||||
return cmp(prop,ref)==0
|
return cmp(prop, ref) == 0
|
||||||
elif rule[1]=='>':
|
elif rule[1] == '>':
|
||||||
return cmp(prop,ref)>0
|
return cmp(prop, ref) > 0
|
||||||
elif rule[1]=='<':
|
elif rule[1] == '<':
|
||||||
return cmp(prop,ref)<0
|
return cmp(prop, ref) < 0
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def ParseValue(val):
|
def ParseValue(val):
|
||||||
if len(val)>=2 and ((val[0]=="'" and val[-1]=="'") or (val[0]=='"' and val[-1]=='"')):
|
if len(val) >= 2 and ((val[0] == "'" and val[-1] == "'") or (val[0] == '"' and val[-1] == '"')):
|
||||||
return val[1:-1]
|
return val[1:-1]
|
||||||
try:
|
try:
|
||||||
return int(val)
|
return int(val)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
def ParseRule(rule):
|
def ParseRule(rule):
|
||||||
sep_pos=min([rule.find(sep) for sep in "~=<>" if rule.find(sep)>0])
|
sep_pos = min([rule.find(sep) for sep in "~=<>" if rule.find(sep) > 0])
|
||||||
prop=rule[:sep_pos].strip()
|
prop = rule[:sep_pos].strip()
|
||||||
if not prop in KnownProps:
|
if not prop in KnownProps:
|
||||||
log("WARNING: unknown property `%s'"%prop)
|
log("WARNING: unknown property `%s'" % prop)
|
||||||
return (prop,rule[sep_pos],ParseValue(rule[sep_pos+1:].strip()))
|
return (prop, rule[sep_pos], ParseValue(rule[sep_pos + 1:].strip()))
|
||||||
|
|
||||||
|
|
||||||
def ParseAction(action):
|
def ParseAction(action):
|
||||||
prop,value=list(map(string.strip,action.split('=',1)))
|
prop, value = list(map(string.strip, action.split('=', 1)))
|
||||||
if not prop in KnownProps:
|
if not prop in KnownProps:
|
||||||
log("WARNING: unknown property `%s'"%prop)
|
log("WARNING: unknown property `%s'" % prop)
|
||||||
return (prop,ParseValue(value))
|
return (prop, ParseValue(value))
|
||||||
|
|
||||||
|
|
||||||
def ParseRuleLine(line):
|
def ParseRuleLine(line):
|
||||||
line=line.strip()
|
line = line.strip()
|
||||||
if not(line) or line[0]=="#":
|
if not (line) or line[0] == "#":
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
# split line into "ruleset: action"
|
# split line into "ruleset: action"
|
||||||
tmp=line.split(":")
|
tmp = line.split(":")
|
||||||
ruleset=list(map(string.strip,":".join(tmp[:-1]).split(",")))
|
ruleset = list(map(string.strip, ":".join(tmp[:-1]).split(",")))
|
||||||
actions=dict(list(map(ParseAction,tmp[-1].split(","))))
|
actions = dict(list(map(ParseAction, tmp[-1].split(","))))
|
||||||
if len(ruleset)==1 and not(ruleset[0]):
|
if len(ruleset) == 1 and not (ruleset[0]):
|
||||||
return ([],actions)
|
return ([], actions)
|
||||||
else:
|
else:
|
||||||
return (list(map(ParseRule,ruleset)),actions)
|
return (list(map(ParseRule, ruleset)), actions)
|
||||||
except OSError: #(ValueError,IndexError,KeyError):
|
except OSError: # (ValueError,IndexError,KeyError):
|
||||||
log("WARNING: rule `%s' is malformed, ignoring"%line)
|
log("WARNING: rule `%s' is malformed, ignoring" % line)
|
||||||
return None
|
return None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -219,29 +226,30 @@ def safe_char(c):
|
||||||
return c
|
return c
|
||||||
return "_"
|
return "_"
|
||||||
|
|
||||||
def rename_safely(path,name):
|
|
||||||
base,ext=os.path.splitext(name)
|
def rename_safely(path, name):
|
||||||
newname=''.join(map(safe_char,base))
|
base, ext = os.path.splitext(name)
|
||||||
if name==newname+ext:
|
newname = ''.join(map(safe_char, base))
|
||||||
|
if name == newname + ext:
|
||||||
return name
|
return name
|
||||||
if os.path.exists("%s/%s%s"%(path,newname,ext)):
|
if os.path.exists("%s/%s%s" % (path, newname, ext)):
|
||||||
i=0
|
i = 0
|
||||||
while os.path.exists("%s/%s_%d%s"%(path,newname,i,ext)):
|
while os.path.exists("%s/%s_%d%s" % (path, newname, i, ext)):
|
||||||
i+=1
|
i += 1
|
||||||
newname+="_%d"%i
|
newname += "_%d" % i
|
||||||
newname+=ext
|
newname += ext
|
||||||
try:
|
try:
|
||||||
os.rename("%s/%s"%(path,name),"%s/%s"%(path,newname))
|
os.rename("%s/%s" % (path, name), "%s/%s" % (path, newname))
|
||||||
except OSError:
|
except OSError:
|
||||||
pass # don't fail if the rename didn't work
|
pass # don't fail if the rename didn't work
|
||||||
return newname
|
return newname
|
||||||
|
|
||||||
|
|
||||||
def write_to_db(filename):
|
def write_to_db(filename):
|
||||||
global iTunesSD,domains,total_count,KnownEntries,Rules
|
global iTunesSD, domains, total_count, KnownEntries, Rules
|
||||||
|
|
||||||
# set default properties
|
# set default properties
|
||||||
props={
|
props = {
|
||||||
'filename': filename,
|
'filename': filename,
|
||||||
'size': filesize(filename[1:]),
|
'size': filesize(filename[1:]),
|
||||||
'ignore': 0,
|
'ignore': 0,
|
||||||
|
@ -252,63 +260,65 @@ def write_to_db(filename):
|
||||||
}
|
}
|
||||||
|
|
||||||
# check and apply rules
|
# check and apply rules
|
||||||
for ruleset,action in Rules:
|
for ruleset, action in Rules:
|
||||||
if reduce(operator.__and__,[MatchRule(props,rule) for rule in ruleset],True):
|
if reduce(operator.__and__, [MatchRule(props, rule) for rule in ruleset], True):
|
||||||
props.update(action)
|
props.update(action)
|
||||||
if props['ignore']: return 0
|
if props['ignore']: return 0
|
||||||
|
|
||||||
# retrieve entry from known entries or rebuild it
|
# retrieve entry from known entries or rebuild it
|
||||||
entry=props['reuse'] and (filename in KnownEntries) and KnownEntries[filename]
|
entry = props['reuse'] and (filename in KnownEntries) and KnownEntries[filename]
|
||||||
if not entry:
|
if not entry:
|
||||||
header[29]=props['type']
|
header[29] = props['type']
|
||||||
entry=header.tostring()+ \
|
entry = header.tostring() + \
|
||||||
"".join([c+"\0" for c in filename[:261]])+ \
|
"".join([c + "\0" for c in filename[:261]]) + \
|
||||||
"\0"*(525-2*len(filename))
|
"\0" * (525 - 2 * len(filename))
|
||||||
|
|
||||||
# write entry, modifying shuffleflag and bookmarkflag at least
|
# write entry, modifying shuffleflag and bookmarkflag at least
|
||||||
iTunesSD.write(entry[:555]+chr(props['shuffle'])+chr(props['bookmark'])+entry[557])
|
iTunesSD.write(entry[:555] + chr(props['shuffle']) + chr(props['bookmark']) + entry[557])
|
||||||
if props['shuffle']: domains[-1].append(total_count)
|
if props['shuffle']: domains[-1].append(total_count)
|
||||||
total_count+=1
|
total_count += 1
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
def make_key(s):
|
def make_key(s):
|
||||||
if not s: return s
|
if not s: return s
|
||||||
s=s.lower()
|
s = s.lower()
|
||||||
for i in range(len(s)):
|
for i in range(len(s)):
|
||||||
if s[i].isdigit(): break
|
if s[i].isdigit(): break
|
||||||
if not s[i].isdigit(): return s
|
if not s[i].isdigit(): return s
|
||||||
for j in range(i,len(s)):
|
for j in range(i, len(s)):
|
||||||
if not s[j].isdigit(): break
|
if not s[j].isdigit(): break
|
||||||
if s[j].isdigit(): j+=1
|
if s[j].isdigit(): j += 1
|
||||||
return (s[:i],int(s[i:j]),make_key(s[j:]))
|
return (s[:i], int(s[i:j]), make_key(s[j:]))
|
||||||
|
|
||||||
|
|
||||||
def key_repr(x):
|
def key_repr(x):
|
||||||
if type(x)==tuple:
|
if type(x) == tuple:
|
||||||
return "%s%d%s"%(x[0],x[1],key_repr(x[2]))
|
return "%s%d%s" % (x[0], x[1], key_repr(x[2]))
|
||||||
else:
|
else:
|
||||||
return x
|
return x
|
||||||
|
|
||||||
def cmp_key(a,b):
|
|
||||||
if type(a)==tuple and type(b)==tuple:
|
def cmp_key(a, b):
|
||||||
return cmp(a[0],b[0]) or cmp(a[1],b[1]) or cmp_key(a[2],b[2])
|
if type(a) == tuple and type(b) == tuple:
|
||||||
|
return cmp(a[0], b[0]) or cmp(a[1], b[1]) or cmp_key(a[2], b[2])
|
||||||
else:
|
else:
|
||||||
return cmp(key_repr(a),key_repr(b))
|
return cmp(key_repr(a), key_repr(b))
|
||||||
|
|
||||||
|
|
||||||
def file_entry(path,name,prefix=""):
|
def file_entry(path, name, prefix=""):
|
||||||
if not(name) or name[0]==".": return None
|
if not (name) or name[0] == ".": return None
|
||||||
fullname="%s/%s"%(path,name)
|
fullname = "%s/%s" % (path, name)
|
||||||
may_rename=not(fullname.startswith("./iPod_Control")) and Options['rename']
|
may_rename = not (fullname.startswith("./iPod_Control")) and Options['rename']
|
||||||
try:
|
try:
|
||||||
if os.path.islink(fullname):
|
if os.path.islink(fullname):
|
||||||
return None
|
return None
|
||||||
if os.path.isdir(fullname):
|
if os.path.isdir(fullname):
|
||||||
if may_rename: name=rename_safely(path,name)
|
if may_rename: name = rename_safely(path, name)
|
||||||
return (0,make_key(name),prefix+name)
|
return (0, make_key(name), prefix + name)
|
||||||
if os.path.splitext(name)[1].lower() in (".mp3",".m4a",".m4b",".m4p",".aa",".wav"):
|
if os.path.splitext(name)[1].lower() in (".mp3", ".m4a", ".m4b", ".m4p", ".aa", ".wav"):
|
||||||
if may_rename: name=rename_safely(path,name)
|
if may_rename: name = rename_safely(path, name)
|
||||||
return (1,make_key(name),prefix+name)
|
return (1, make_key(name), prefix + name)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
@ -317,19 +327,19 @@ def file_entry(path,name,prefix=""):
|
||||||
def browse(path, interactive):
|
def browse(path, interactive):
|
||||||
global domains
|
global domains
|
||||||
|
|
||||||
if path[-1]=="/": path=path[:-1]
|
if path[-1] == "/": path = path[:-1]
|
||||||
displaypath=path[1:]
|
displaypath = path[1:]
|
||||||
if not displaypath: displaypath="/"
|
if not displaypath: displaypath = "/"
|
||||||
|
|
||||||
if interactive:
|
if interactive:
|
||||||
while 1:
|
while 1:
|
||||||
try:
|
try:
|
||||||
choice=input("include `%s'? [(Y)es, (N)o, (A)ll] "%displaypath)[:1].lower()
|
choice = input("include `%s'? [(Y)es, (N)o, (A)ll] " % displaypath)[:1].lower()
|
||||||
except EOFError:
|
except EOFError:
|
||||||
raise KeyboardInterrupt
|
raise KeyboardInterrupt
|
||||||
if not choice: continue
|
if not choice: continue
|
||||||
if choice in "at": # all/alle/tous/<dontknow>
|
if choice in "at": # all/alle/tous/<dontknow>
|
||||||
interactive=0
|
interactive = 0
|
||||||
break
|
break
|
||||||
if choice in "yjos": # yes/ja/oui/si
|
if choice in "yjos": # yes/ja/oui/si
|
||||||
break
|
break
|
||||||
|
@ -337,71 +347,73 @@ def browse(path, interactive):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
files=[_f for _f in [file_entry(path,name) for name in os.listdir(path)] if _f]
|
files = [_f for _f in [file_entry(path, name) for name in os.listdir(path)] if _f]
|
||||||
except OSError:
|
except OSError:
|
||||||
return
|
return
|
||||||
|
|
||||||
if path=="./iPod_Control/Music":
|
if path == "./iPod_Control/Music":
|
||||||
subdirs=[x[2] for x in files if not x[0]]
|
subdirs = [x[2] for x in files if not x[0]]
|
||||||
files=[x for x in files if x[0]]
|
files = [x for x in files if x[0]]
|
||||||
for dir in subdirs:
|
for dir in subdirs:
|
||||||
subpath="%s/%s"%(path,dir)
|
subpath = "%s/%s" % (path, dir)
|
||||||
try:
|
try:
|
||||||
files.extend([x for x in [file_entry(subpath,name,dir+"/") for name in os.listdir(subpath)] if x and x[0]])
|
files.extend(
|
||||||
|
[x for x in [file_entry(subpath, name, dir + "/") for name in os.listdir(subpath)] if x and x[0]])
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
files.sort(cmp_key)
|
files.sort(cmp_key)
|
||||||
count=len([None for x in files if x[0]])
|
count = len([None for x in files if x[0]])
|
||||||
if count: domains.append([])
|
if count: domains.append([])
|
||||||
|
|
||||||
real_count=0
|
real_count = 0
|
||||||
for item in files:
|
for item in files:
|
||||||
fullname="%s/%s"%(path,item[2])
|
fullname = "%s/%s" % (path, item[2])
|
||||||
if item[0]:
|
if item[0]:
|
||||||
real_count+=write_to_db(fullname[1:])
|
real_count += write_to_db(fullname[1:])
|
||||||
else:
|
else:
|
||||||
browse(fullname,interactive)
|
browse(fullname, interactive)
|
||||||
|
|
||||||
if real_count==count:
|
if real_count == count:
|
||||||
log("%s: %d files"%(displaypath,count))
|
log("%s: %d files" % (displaypath, count))
|
||||||
else:
|
else:
|
||||||
log("%s: %d files (out of %d)"%(displaypath,real_count,count))
|
log("%s: %d files (out of %d)" % (displaypath, real_count, count))
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
def stringval(i):
|
def stringval(i):
|
||||||
if i<0: i+=0x1000000
|
if i < 0: i += 0x1000000
|
||||||
return "%c%c%c"%(i&0xFF,(i>>8)&0xFF,(i>>16)&0xFF)
|
return "%c%c%c" % (i & 0xFF, (i >> 8) & 0xFF, (i >> 16) & 0xFF)
|
||||||
|
|
||||||
|
|
||||||
def listval(i):
|
def listval(i):
|
||||||
if i<0: i+=0x1000000
|
if i < 0: i += 0x1000000
|
||||||
return [i&0xFF,(i>>8)&0xFF,(i>>16)&0xFF]
|
return [i & 0xFF, (i >> 8) & 0xFF, (i >> 16) & 0xFF]
|
||||||
|
|
||||||
|
|
||||||
def make_playback_state(volume=None):
|
def make_playback_state(volume=None):
|
||||||
# I'm not at all proud of this function. Why can't stupid Python make strings
|
# I'm not at all proud of this function. Why can't stupid Python make strings
|
||||||
# mutable?!
|
# mutable?!
|
||||||
log("Setting playback state ...",False)
|
log("Setting playback state ...", False)
|
||||||
PState=[]
|
PState = []
|
||||||
try:
|
try:
|
||||||
f=file("iPod_Control/iTunes/iTunesPState","rb")
|
f = file("iPod_Control/iTunes/iTunesPState", "rb")
|
||||||
a=array.array('B')
|
a = array.array('B')
|
||||||
a.fromstring(f.read())
|
a.fromstring(f.read())
|
||||||
PState=a.tolist()
|
PState = a.tolist()
|
||||||
f.close()
|
f.close()
|
||||||
except IOError as EOFError:
|
except IOError as EOFError:
|
||||||
del PState[:]
|
del PState[:]
|
||||||
if len(PState)!=21:
|
if len(PState) != 21:
|
||||||
PState=listval(29)+[0]*15+listval(1) # volume 29, FW ver 1.0
|
PState = listval(29) + [0] * 15 + listval(1) # volume 29, FW ver 1.0
|
||||||
PState[3:15]=[0]*6+[1]+[0]*5 # track 0, shuffle mode, start of track
|
PState[3:15] = [0] * 6 + [1] + [0] * 5 # track 0, shuffle mode, start of track
|
||||||
if volume is not None:
|
if volume is not None:
|
||||||
PState[:3]=listval(volume)
|
PState[:3] = listval(volume)
|
||||||
try:
|
try:
|
||||||
f=file("iPod_Control/iTunes/iTunesPState","wb")
|
f = file("iPod_Control/iTunes/iTunesPState", "wb")
|
||||||
array.array('B',PState).tofile(f)
|
array.array('B', PState).tofile(f)
|
||||||
f.close()
|
f.close()
|
||||||
except IOError:
|
except IOError:
|
||||||
log("FAILED.")
|
log("FAILED.")
|
||||||
|
@ -411,10 +423,10 @@ def make_playback_state(volume=None):
|
||||||
|
|
||||||
|
|
||||||
def make_stats(count):
|
def make_stats(count):
|
||||||
log("Creating statistics file ...",False)
|
log("Creating statistics file ...", False)
|
||||||
try:
|
try:
|
||||||
file("iPod_Control/iTunes/iTunesStats","wb").write(\
|
file("iPod_Control/iTunes/iTunesStats", "wb").write( \
|
||||||
stringval(count)+"\0"*3+(stringval(18)+"\xff"*3+"\0"*12)*count)
|
stringval(count) + "\0" * 3 + (stringval(18) + "\xff" * 3 + "\0" * 12) * count)
|
||||||
except IOError:
|
except IOError:
|
||||||
log("FAILED.")
|
log("FAILED.")
|
||||||
return 0
|
return 0
|
||||||
|
@ -427,54 +439,56 @@ def make_stats(count):
|
||||||
|
|
||||||
def smart_shuffle():
|
def smart_shuffle():
|
||||||
try:
|
try:
|
||||||
slice_count=max(list(map(len,domains)))
|
slice_count = max(list(map(len, domains)))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return []
|
return []
|
||||||
slices=[[] for x in range(slice_count)]
|
slices = [[] for x in range(slice_count)]
|
||||||
slice_fill=[0]*slice_count
|
slice_fill = [0] * slice_count
|
||||||
|
|
||||||
for d in range(len(domains)):
|
for d in range(len(domains)):
|
||||||
used=[]
|
used = []
|
||||||
if not domains[d]: continue
|
if not domains[d]: continue
|
||||||
for n in domains[d]:
|
for n in domains[d]:
|
||||||
# find slices where the nearest track of the same domain is far away
|
# find slices where the nearest track of the same domain is far away
|
||||||
metric=[min([slice_count]+[min(abs(s-u),abs(s-u+slice_count),abs(s-u-slice_count)) for u in used]) for s in range(slice_count)]
|
metric = [
|
||||||
thresh=old_div((max(metric)+1),2)
|
min([slice_count] + [min(abs(s - u), abs(s - u + slice_count), abs(s - u - slice_count)) for u in used])
|
||||||
farthest=[s for s in range(slice_count) if metric[s]>=thresh]
|
for s in range(slice_count)]
|
||||||
|
thresh = old_div((max(metric) + 1), 2)
|
||||||
|
farthest = [s for s in range(slice_count) if metric[s] >= thresh]
|
||||||
|
|
||||||
# find emptiest slices
|
# find emptiest slices
|
||||||
thresh=old_div((min(slice_fill)+max(slice_fill)+1),2)
|
thresh = old_div((min(slice_fill) + max(slice_fill) + 1), 2)
|
||||||
emptiest=[s for s in range(slice_count) if slice_fill[s]<=thresh if (s in farthest)]
|
emptiest = [s for s in range(slice_count) if slice_fill[s] <= thresh if (s in farthest)]
|
||||||
|
|
||||||
# choose one of the remaining candidates and add the track to the chosen slice
|
# choose one of the remaining candidates and add the track to the chosen slice
|
||||||
s=random.choice(emptiest or farthest)
|
s = random.choice(emptiest or farthest)
|
||||||
slices[s].append((n,d))
|
slices[s].append((n, d))
|
||||||
slice_fill[s]+=1
|
slice_fill[s] += 1
|
||||||
used.append(s)
|
used.append(s)
|
||||||
|
|
||||||
# shuffle slices and avoid adjacent tracks of the same domain at slice boundaries
|
# shuffle slices and avoid adjacent tracks of the same domain at slice boundaries
|
||||||
seq=[]
|
seq = []
|
||||||
last_domain=-1
|
last_domain = -1
|
||||||
for slice in slices:
|
for slice in slices:
|
||||||
random.shuffle(slice)
|
random.shuffle(slice)
|
||||||
if len(slice)>2 and slice[0][1]==last_domain:
|
if len(slice) > 2 and slice[0][1] == last_domain:
|
||||||
slice.append(slice.pop(0))
|
slice.append(slice.pop(0))
|
||||||
seq+=[x[0] for x in slice]
|
seq += [x[0] for x in slice]
|
||||||
last_domain=slice[-1][1]
|
last_domain = slice[-1][1]
|
||||||
return seq
|
return seq
|
||||||
|
|
||||||
|
|
||||||
def make_shuffle(count):
|
def make_shuffle(count):
|
||||||
random.seed()
|
random.seed()
|
||||||
if Options['smart']:
|
if Options['smart']:
|
||||||
log("Generating smart shuffle sequence ...",False)
|
log("Generating smart shuffle sequence ...", False)
|
||||||
seq=smart_shuffle()
|
seq = smart_shuffle()
|
||||||
else:
|
else:
|
||||||
log("Generating shuffle sequence ...",False)
|
log("Generating shuffle sequence ...", False)
|
||||||
seq=list(range(count))
|
seq = list(range(count))
|
||||||
random.shuffle(seq)
|
random.shuffle(seq)
|
||||||
try:
|
try:
|
||||||
file("iPod_Control/iTunes/iTunesShuffle","wb").write("".join(map(stringval,seq)))
|
file("iPod_Control/iTunes/iTunesShuffle", "wb").write("".join(map(stringval, seq)))
|
||||||
except IOError:
|
except IOError:
|
||||||
log("FAILED.")
|
log("FAILED.")
|
||||||
return 0
|
return 0
|
||||||
|
@ -486,13 +500,13 @@ def make_shuffle(count):
|
||||||
|
|
||||||
|
|
||||||
def main(dirs):
|
def main(dirs):
|
||||||
global header,iTunesSD,total_count,KnownEntries,Rules
|
global header, iTunesSD, total_count, KnownEntries, Rules
|
||||||
log("Welcome to %s, version %s"%(__title__,__version__))
|
log("Welcome to %s, version %s" % (__title__, __version__))
|
||||||
log()
|
log()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
f=file("rebuild_db.rules","r")
|
f = file("rebuild_db.rules", "r")
|
||||||
Rules+=[_f for _f in map(ParseRuleLine,f.read().split("\n")) if _f]
|
Rules += [_f for _f in map(ParseRuleLine, f.read().split("\n")) if _f]
|
||||||
f.close()
|
f.close()
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
|
@ -504,40 +518,40 @@ Please make sure that:
|
||||||
(*) the iPod was correctly initialized with iTunes""")
|
(*) the iPod was correctly initialized with iTunes""")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
header=array.array('B')
|
header = array.array('B')
|
||||||
iTunesSD=None
|
iTunesSD = None
|
||||||
try:
|
try:
|
||||||
iTunesSD=file("iPod_Control/iTunes/iTunesSD","rb")
|
iTunesSD = file("iPod_Control/iTunes/iTunesSD", "rb")
|
||||||
header.fromfile(iTunesSD,51)
|
header.fromfile(iTunesSD, 51)
|
||||||
if Options['reuse']:
|
if Options['reuse']:
|
||||||
iTunesSD.seek(18)
|
iTunesSD.seek(18)
|
||||||
entry=iTunesSD.read(558)
|
entry = iTunesSD.read(558)
|
||||||
while len(entry)==558:
|
while len(entry) == 558:
|
||||||
filename=entry[33::2].split("\0",1)[0]
|
filename = entry[33::2].split("\0", 1)[0]
|
||||||
KnownEntries[filename]=entry
|
KnownEntries[filename] = entry
|
||||||
entry=iTunesSD.read(558)
|
entry = iTunesSD.read(558)
|
||||||
except (IOError,EOFError):
|
except (IOError, EOFError):
|
||||||
pass
|
pass
|
||||||
if iTunesSD: iTunesSD.close()
|
if iTunesSD: iTunesSD.close()
|
||||||
|
|
||||||
if len(header)==51:
|
if len(header) == 51:
|
||||||
log("Using iTunesSD headers from existing database.")
|
log("Using iTunesSD headers from existing database.")
|
||||||
if KnownEntries:
|
if KnownEntries:
|
||||||
log("Collected %d entries from existing database."%len(KnownEntries))
|
log("Collected %d entries from existing database." % len(KnownEntries))
|
||||||
else:
|
else:
|
||||||
del header[18:]
|
del header[18:]
|
||||||
if len(header)==18:
|
if len(header) == 18:
|
||||||
log("Using iTunesSD main header from existing database.")
|
log("Using iTunesSD main header from existing database.")
|
||||||
else:
|
else:
|
||||||
del header[:]
|
del header[:]
|
||||||
log("Rebuilding iTunesSD main header from scratch.")
|
log("Rebuilding iTunesSD main header from scratch.")
|
||||||
header.fromlist([0,0,0,1,6,0,0,0,18]+[0]*9)
|
header.fromlist([0, 0, 0, 1, 6, 0, 0, 0, 18] + [0] * 9)
|
||||||
log("Rebuilding iTunesSD entry header from scratch.")
|
log("Rebuilding iTunesSD entry header from scratch.")
|
||||||
header.fromlist([0,2,46,90,165,1]+[0]*20+[100,0,0,1,0,2,0])
|
header.fromlist([0, 2, 46, 90, 165, 1] + [0] * 20 + [100, 0, 0, 1, 0, 2, 0])
|
||||||
|
|
||||||
log()
|
log()
|
||||||
try:
|
try:
|
||||||
iTunesSD=file("iPod_Control/iTunes/iTunesSD","wb")
|
iTunesSD = file("iPod_Control/iTunes/iTunesSD", "wb")
|
||||||
header[:18].tofile(iTunesSD)
|
header[:18].tofile(iTunesSD)
|
||||||
except IOError:
|
except IOError:
|
||||||
log("""ERROR: Cannot write to the iPod database file (iTunesSD)!
|
log("""ERROR: Cannot write to the iPod database file (iTunesSD)!
|
||||||
|
@ -551,22 +565,22 @@ Please make sure that:
|
||||||
try:
|
try:
|
||||||
if dirs:
|
if dirs:
|
||||||
for dir in dirs:
|
for dir in dirs:
|
||||||
browse("./"+dir,Options['interactive'])
|
browse("./" + dir, Options['interactive'])
|
||||||
else:
|
else:
|
||||||
browse(".",Options['interactive'])
|
browse(".", Options['interactive'])
|
||||||
log("%d playable files were found on your iPod."%total_count)
|
log("%d playable files were found on your iPod." % total_count)
|
||||||
log()
|
log()
|
||||||
log("Fixing iTunesSD header.")
|
log("Fixing iTunesSD header.")
|
||||||
iTunesSD.seek(0)
|
iTunesSD.seek(0)
|
||||||
iTunesSD.write("\0%c%c"%(total_count>>8,total_count&0xFF))
|
iTunesSD.write("\0%c%c" % (total_count >> 8, total_count & 0xFF))
|
||||||
iTunesSD.close()
|
iTunesSD.close()
|
||||||
except IOError:
|
except IOError:
|
||||||
log("ERROR: Some strange errors occured while writing iTunesSD.")
|
log("ERROR: Some strange errors occured while writing iTunesSD.")
|
||||||
log(" You may have to re-initialize the iPod using iTunes.")
|
log(" You may have to re-initialize the iPod using iTunes.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if make_playback_state(Options['volume'])* \
|
if make_playback_state(Options['volume']) * \
|
||||||
make_stats(total_count)* \
|
make_stats(total_count) * \
|
||||||
make_shuffle(total_count):
|
make_shuffle(total_count):
|
||||||
log()
|
log()
|
||||||
log("The iPod shuffle database was rebuilt successfully.")
|
log("The iPod shuffle database was rebuilt successfully.")
|
||||||
|
@ -581,7 +595,7 @@ Please make sure that:
|
||||||
|
|
||||||
|
|
||||||
def help():
|
def help():
|
||||||
print("Usage: %s [OPTION]... [DIRECTORY]..."%sys.argv[0])
|
print("Usage: %s [OPTION]... [DIRECTORY]..." % sys.argv[0])
|
||||||
print("""Rebuild iPod shuffle database.
|
print("""Rebuild iPod shuffle database.
|
||||||
|
|
||||||
Mandatory arguments to long options are mandatory for short options too.
|
Mandatory arguments to long options are mandatory for short options too.
|
||||||
|
@ -599,47 +613,49 @@ searched for playable files, unless at least one DIRECTORY is specified.""")
|
||||||
|
|
||||||
|
|
||||||
def opterr(msg):
|
def opterr(msg):
|
||||||
print("parse error:",msg)
|
print("parse error:", msg)
|
||||||
print("use `%s -h' to get help"%sys.argv[0])
|
print("use `%s -h' to get help" % sys.argv[0])
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def parse_options():
|
def parse_options():
|
||||||
try:
|
try:
|
||||||
opts,args=getopt.getopt(sys.argv[1:],"hiv:snlfL:r",\
|
opts, args = getopt.getopt(sys.argv[1:], "hiv:snlfL:r", \
|
||||||
["help","interactive","volume=","nosmart","nochdir","nolog","force","logfile=","rename"])
|
["help", "interactive", "volume=", "nosmart", "nochdir", "nolog", "force",
|
||||||
|
"logfile=", "rename"])
|
||||||
except getopt.GetoptError as message:
|
except getopt.GetoptError as message:
|
||||||
opterr(message)
|
opterr(message)
|
||||||
for opt,arg in opts:
|
for opt, arg in opts:
|
||||||
if opt in ("-h","--help"):
|
if opt in ("-h", "--help"):
|
||||||
help()
|
help()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
elif opt in ("-i","--interactive"):
|
elif opt in ("-i", "--interactive"):
|
||||||
Options['interactive']=True
|
Options['interactive'] = True
|
||||||
elif opt in ("-v","--volume"):
|
elif opt in ("-v", "--volume"):
|
||||||
try:
|
try:
|
||||||
Options['volume']=int(arg)
|
Options['volume'] = int(arg)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
opterr("invalid volume")
|
opterr("invalid volume")
|
||||||
elif opt in ("-s","--nosmart"):
|
elif opt in ("-s", "--nosmart"):
|
||||||
Options['smart']=False
|
Options['smart'] = False
|
||||||
elif opt in ("-n","--nochdir"):
|
elif opt in ("-n", "--nochdir"):
|
||||||
Options['home']=False
|
Options['home'] = False
|
||||||
elif opt in ("-l","--nolog"):
|
elif opt in ("-l", "--nolog"):
|
||||||
Options['logging']=False
|
Options['logging'] = False
|
||||||
elif opt in ("-f","--force"):
|
elif opt in ("-f", "--force"):
|
||||||
Options['reuse']=0
|
Options['reuse'] = 0
|
||||||
elif opt in ("-L","--logfile"):
|
elif opt in ("-L", "--logfile"):
|
||||||
Options['logfile']=arg
|
Options['logfile'] = arg
|
||||||
elif opt in ("-r","--rename"):
|
elif opt in ("-r", "--rename"):
|
||||||
Options['rename']=True
|
Options['rename'] = True
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
if __name__=="__main__":
|
if __name__ == "__main__":
|
||||||
args=parse_options()
|
args = parse_options()
|
||||||
go_home()
|
go_home()
|
||||||
open_log()
|
open_log()
|
||||||
try:
|
try:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue