1 # Copyright 2009, Erik Hahn
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 warnings
.filterwarnings("ignore", message
="the md5 module is deprecated; " + \
18 "use hashlib instead")
26 from optparse
import OptionParser
, OptionGroup
27 from tag_wrapper
import tag
, TagException
29 # Some extensions might be missing
31 ".asf", ".wma", ".wmv", # ASF, Windows Media
34 # Which extensions for Monkeysaudio?
36 ".mpc", ".mpp", ".mp+", # Musepack (APEv2)
37 ".oga", ".ogg", # Ogg Vorbis
38 ".tta", # True Audio (hopefully)
39 ".wv", ".wvc" # WavPack
42 def issupportedfile(name
):
43 for ext
in ACCEPTEXTS
:
44 if os
.path
.splitext(name
)[1] == ext
:
50 if not os
.path
.isdir(newdir
):
54 def newpath(file, pattern
):
55 # Create the tokens dictionary
58 # TODO: add tracknumber, disknumber and possibily compilation
59 for t
in ["title", "artist", "album artist", "album", "composer", "genre", "date"]:
64 # %album artist% is %artist% if nothing can be found in the tags
65 if tokens
["album artist"] is None:
66 tokens
["album artist"] = tokens
["artist"]
68 # Now replace all tokens by their values
71 if tokens
[i
] is not None:
72 if Options
["windows"]:
73 val
= sanitize_path(tokens
[i
][0])
76 pattern
= pattern
.replace(repl
, val
)
78 # Add the extension and return the new path
79 return pattern
+ os
.path
.splitext(file)[1]
82 def safeprint(string
):
84 Print string first trying to normally encode it, sending it to the
85 console in "raw" UTF-8 if it fails.
87 This is a workaround for Windows's broken console
91 except UnicodeEncodeError:
92 print string
.encode("utf-8")
95 def sanitize_path(string
):
96 # replaces all characters invalid in windows path and file names by '+'
97 for i
in ['/', '\\', ':', '*', '?', '"', '<', '>', '|']:
98 string
= string
.replace(i
, '+')
102 def files_to_move(base_dir
, pattern
):
103 # Figure out which files to move where
104 basepath
= path(base_dir
)
105 if Options
["recursive"]:
106 files
= basepath
.walkfiles()
108 files
= basepath
.files()
113 if issupportedfile(file):
115 t
= [ file, newpath(file, pattern
) ]
117 print "Error reading tags from " + file
119 files_return
.append(t
)
126 # Pseudo-enum for actions
133 usage
= "Usage: %prog [options] directory pattern"
134 version
= "Ordnung 0.1 alpha 2"
135 parser
= OptionParser(usage
=usage
, version
=version
)
136 parser
.add_option("-r", "--recursive", action
="store_true",
137 dest
="recursive", help="also move/copy files from sub-directories",
139 parser
.add_option("-w", "--windows", action
="store_true", dest
="windows",
140 help="generate Windows-compatible file and path names",
143 actions
= OptionGroup(parser
, "Possible actions")
144 actions
.add_option("--preview", "-p", action
="store_const",
145 const
=PREVIEW
, dest
="action", help="preview, don't make any changes (default)")
146 actions
.add_option("--copy", "-c", action
="store_const",
147 const
=COPY
, dest
="action", help="copy files")
148 actions
.add_option("--move", "-m", action
="store_const",
149 const
=MOVE
, dest
="action", help="move files")
150 actions
.add_option("--nothing", "-n", action
="store_const",
151 const
=MOVE
, dest
="action", help="do nothing (for debugging)")
152 parser
.add_option_group(actions
)
153 (options
, args
) = parser
.parse_args()
155 # Passing these values in chains of arguments would be error-prone
156 Options
["recursive"] = options
.recursive
157 # What about Win 9x and possibily other platforms?
159 Options
["windows"] = True
161 Options
["windows"] = options
.windows
166 print "You must specify a directory"
172 print "You must specify a pattern"
176 for i
in files_to_move(base_dir
=base
, pattern
=pattern
):
177 if Options
["windows"]:
178 # Windows doesn't accept trailing dots in filenames
182 tpath
= os
.path
.join(tpath
, (j
.rstrip('.')))
183 tfile
= os
.path
.join(tpath
, t
[-1])
185 tpath
= os
.path
.split(i
[1])[0]
188 if options
.action
== MOVE
:
190 shutil
.move(i
[0], tfile
)
191 elif options
.action
== COPY
:
193 shutil
.copy2(i
[0], tfile
)
194 elif options
.action
== PREVIEW
:
195 safeprint (i
[0] + " --> " + tfile
)
196 else: # options.action == NOTHING
200 if __name__
== "__main__":