Tasks rework mostly done, replaygain now uses tasks. Start of style/docs cleanup.
[audiomangler.git] / audiomangler / cli.py
blob35513c3d0cc63fc2daabd7d27eeb71e7a25e6813
1 # -*- coding: utf-8 -*-
2 ###########################################################################
3 # Copyright (C) 2008 by Andrew Mahone
4 # <andrew.mahone@gmail.com>
6 # Copyright: See COPYING file that comes with this distribution
8 ###########################################################################
9 import sys
10 import getopt
11 import shutil
12 import os
13 import os.path
14 import errno
15 from functools import wraps
16 from audiomangler.config import Config
17 from audiomangler import util
18 from audiomangler.codecs import sync_sets, get_codec
19 from audiomangler.scanner import scan
20 from audiomangler.task import PoolTask
21 from audiomangler.logging import err, msg, fatal, ERROR, WARNING, INFO, DEBUG
23 def parse_options(options = []):
24 def decorator(f):
25 @wraps(f)
26 def proxy(*args):
27 if not args:
28 if len(sys.argv) == 1:
29 print_usage(options)
30 sys.exit(0)
31 else:
32 args = sys.argv[1:]
33 name_map = {}
34 s_opts = []
35 l_opts = []
36 for (s_opt, l_opt, name, desc) in options:
37 if s_opt:
38 name_map['-'+s_opt.rstrip(':')] = name
39 s_opts.append(s_opt)
40 if l_opt:
41 name_map['--'+l_opt.rstrip('=')] = name
42 l_opts.append(l_opt)
43 s_opts = ''.join(s_opts)
44 try:
45 (opts, args) = getopt.getopt(args, s_opts, l_opts)
46 except getopt.GetoptError:
47 print_usage(options)
48 sys.exit(0)
49 for k, v in opts:
50 k = name_map[k]
51 Config[k] = v
52 f(*args)
53 return proxy
54 return decorator
56 def print_usage(opts):
57 print """usage:
58 %s [options] [files or directories to process]
60 options:""" % sys.argv[0]
61 for short, long_, name, desc in opts:
62 print " -%s, --%-10s %s" %(short.rstrip(':'), long_.rstrip('='), desc)
64 common_opts = (
65 ('b:', 'base=', 'base', 'base directory for target files'),
66 ('p:', 'profile=', 'profile', 'profile to load settings from'),
67 ('f:', 'filename=', 'filename', 'format for target filenames'),
70 @parse_options(common_opts)
71 def rename(*args):
72 dir_list = scan(args)[1]
73 util.test_splits(dir_list)
74 onsplit = Config['onsplit']
75 for (dir_, files) in dir_list.items():
76 dir_p = util.fsdecode(dir_)
77 msg(consoleformat=u"from dir %(dir_p)s:",
78 format="enter: %(dir_)r", dir_=dir_, dir_p=dir_p, loglevel=INFO)
79 dstdirs = set()
80 moves = []
81 for file_ in files:
82 src = file_.filename
83 dst = util.fsencode(file_.format())
84 src_p = util.fsdecode(src)
85 dst_p = util.fsdecode(dst)
86 if src == dst:
87 msg(consoleformat=u" skipping %(src_p)s, already named correctly",
88 format="skip: %(src)r", src_p=srcp_p, src=src, loglevel=INFO)
89 continue
90 dstdir = os.path.split(dst)[0]
91 if dstdir not in dstdirs and dstdir != dir_:
92 try:
93 os.makedirs(dstdir)
94 except OSError, e:
95 if e.errno != errno.EEXIST or not os.path.isdir(dstdir):
96 raise
97 dstdirs.add(dstdir)
98 msg(consoleformat=u" %(src_p)s -> %(dst_p)s",
99 format="move: %(src)r, %(dst)r", src=src, dst=dst, src_p=src_p, dst_p=dst_p, loglevel=INFO)
100 util.move(src, dst)
101 if len(dstdirs) == 1:
102 dstdir = dstdirs.pop()
103 for file_ in os.listdir(dir_):
104 src = os.path.join(dir_, file_)
105 dst = os.path.join(dstdir, file_)
106 src_p = util.fsdecode(src)
107 dst_p = util.fsdecode(dst)
108 msg(consoleformat=u" %(src_p)s -> %(dst_p)s",
109 format="move: %(src)r, %(dst)r", src=src, dst=dst, src_p=src_p, dst_p=dst_p, loglevel=INFO)
110 util.move(src, dst)
111 while len(os.listdir(dir_)) == 0:
112 dir_p = util.fsdecode(dir_)
113 msg(consoleformat=u" remove empty directory: %(dir_p)s",
114 format="rmdir: %(dir_)r", dir_=dir_, dir_p=dir_p, loglevel=INFO)
115 try:
116 os.rmdir(dir_)
117 except Exception:
118 break
119 newdir = os.path.split(dir_)[0]
120 if newdir != dir_:
121 dir_ = newdir
122 else:
123 break
124 else:
125 if onsplit == 'warn':
126 msg(consoleformat=u"WARNING: tracks in %(dir_p)s were placed in different directories, other files may be left in the source directory",
127 format="split: %(dir_)r", dir_=dir_, dir_p=dir_p, loglevel=WARNING)
129 @parse_options(common_opts + (
130 ('t:', 'type=', 'type', 'type of audio to encode to'),
131 ('s:', 'preset=', 'preset', 'codec preset to use'),
132 ('e:', 'encopts=', 'encopts', 'encoder options to use'),
133 ('j:', 'jobs=', 'jobs', 'number of jobs to run'),
136 def sync(*args):
137 (album_list, dir_list) = scan(args)[:2]
138 targettids = scan(Config['base'])[2]
139 sync_sets(album_list.values(), targettids)
141 def replaygain_task_generator(album_list):
142 for key, album in album_list.items():
143 profiles = set()
144 for track in album:
145 profiles.add((
146 getattr(track, 'type_', None),
147 getattr(getattr(track, 'info', None), 'sample_rate', None),
148 getattr(getattr(track, 'info', None), 'channels', None)
150 if len(profiles) != 1:
151 continue
152 profile = profiles.pop()
153 if profile[1] not in (8000, 11025, 12000, 16000, 22050, 24, 32, 44100, 48000):
154 continue
155 codec = get_codec(profile[0])
156 if not codec or not codec._replaygain:
157 continue
158 if reduce(lambda x, y: x and y.has_replaygain(), album, True):
159 continue
160 msg(consoleformat=u"Adding replaygain values to %(albumtitle)s",
161 format="rg: %(tracks)r", albumtitle=album[0].meta.flat().get('album', '[unknown]'),
162 tracks=tuple(t.filename for t in album))
163 yield codec.add_replaygain([t.filename for t in album])
165 @parse_options(common_opts[:2] + (
166 ('j:', 'jobs=', 'jobs', 'number of jobs to run'),
169 def replaygain(*args):
170 if not args:
171 args = (Config['base'], )
172 (album_list) = scan(args)[0]
173 PoolTask(replaygain_task_generator(album_list)).run()
175 __all__ = []