-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathplaylister.py
executable file
·124 lines (103 loc) · 3.68 KB
/
playlister.py
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
#!/usr/bin/env python3
from sys import argv, stderr
from os import chdir, getcwd
from os.path import isdir, basename, sep
from glob import iglob
initial_path = getcwd()
def make_playlist(dir):
"""
Creates the playlist for for a directory; if one exists, this function
will replace it.
"""
playlist_name = basename(dir.rstrip(sep)) + ".m3u"
print("Creating", playlist_name)
# change current director so that find_media_files produces
# paths relative to 'dir'
chdir(dir)
try:
with open(playlist_name, "w") as f:
for filename in find_media_files(dir):
f.write(filename)
f.write("\n")
finally:
chdir(initial_path)
def find_media_files(dir, extensions = ["mp3", "wma"]):
"""
Finds the paths to each media file in the directory. The produces
relative paths, relative to the current directory, and sorts them
too. Only files with the extensions given are returend.
"""
def make_pattern(ext):
"""
Creates the glob pattern for a particular extension; it
matches the file extension case insensitively, even on
Linux.
"""
def insensitive(ch):
"""
If 'ch' is a letter, returns a pattern that matches it
in either case. If not, returns it unchanged.
"""
if ch.isalpha():
return "[{}{}]".format(ch.lower(), ch.upper())
else:
return ch
return "**/*." + "".join((insensitive(c) for c in ext))
# use a set in case a file matches two patterns; we want each file
# once only.
files = set()
for ext in extensions:
for fn in iglob(make_pattern(ext), recursive=True):
files.add(fn)
return sorted_smartly(files)
def sorted_smartly(to_sort):
"""
Sorts the given list of strings alphabetically, but with
extra smarts to handle simple numbers. It effectly sorts them
as if they had been 0 padded so all numbers were the same
length; this makes them sort numerically.
"""
def split_digits(text):
"""
Splits a string into segments that cover every character; Digits
are placed in separate segments from everything else.
"""
current = ""
for c in text:
if current == "" or current[-1].isdigit() == c.isdigit():
current += c
else:
yield current
current = c
if current != "": yield current
# This is how long we will make all the numeric parts.
numbers_lengths = (len(part)
for file in to_sort
for part in split_digits(file)
if part[0].isdigit())
max_number_len = max(numbers_lengths)
def pad_part(part):
"""
Pads a part of the whole string to the maximum length. Only numeric
parts are padded; others are returned unchanged.
"""
if part[0].isdigit(): return part.rjust(max_number_len, "0")
else: return part
def pad_numbers(text):
"""
This breaks down the string given and pads any numbers
found, then reassambles it all.
"""
parts = (pad_part(part) for part in split_digits(text))
return "".join(parts)
return sorted(to_sort, key=pad_numbers)
# This just processes the command line, skipping the first
# entry. That's just the filename of this script anyway.
if len(argv) < 2:
print("Usage: playlister.py DIR1 DIR2 ...")
else:
for dir in argv[1:]:
if isdir(dir):
make_playlist(dir)
else:
print(dir, "is not a directory", file=stderr)