1
#!/usr/bin/env python
2
# vim: set fileencoding=utf-8 tabstop=4 shiftwidth=4 expandtab smartindent:
3
u"""
4
5
Make songs list
6
---------------
7
8
Create a PDF file with the list of Karaoke songs available to Performous.
9
10
Performous: http://performous.org/
11
12
.. :Authors:
13
       Aurélien Bompard <aurelien@bompard.org> <http://aurelien.bompard.org>
14
15
.. :License:
16
       GNU GPL v3 or later
17
"""
18
19
import os
20
import sys
21
import tempfile
22
import optparse
23
import atexit
24
import re
25
from pprint import pprint
26
27
import PIL
28
29
from reportlab.platypus import BaseDocTemplate, Paragraph, Spacer, Image, Table, TableStyle, Frame, PageTemplate
30
from reportlab.lib.styles import ParagraphStyle
31
from reportlab.lib import colors
32
from reportlab.rl_config import defaultPageSize
33
from reportlab.lib.units import inch
34
35
36
defaultPageSize = (defaultPageSize[1], defaultPageSize[0])
37
PAGE_HEIGHT=defaultPageSize[1]
38
PAGE_WIDTH=defaultPageSize[0]
39
MARGIN = 8
40
COLSEP = 5
41
42
songs = []
43
44
def get_songs(cover_size, dpi):
45
    if opts.input:
46
        available_songs = scan_file()
47
    else:
48
        available_songs = scan_folders()
49
    for index, available_song in enumerate(available_songs):
50
        song = process_file(available_song, cover_size, dpi)
51
        if song:
52
            songs.append(song)
53
        sys.stdout.write("\rAdding song %d/%d"
54
                         % (index+1, len(available_songs)))
55
        sys.stdout.flush()
56
    print
57
    print "Building PDF..."
58
    songs.sort(cmp=sort_songs)
59
60
def sort_songs(x, y):
61
    key_x = x["artist"]
62
    key_y = y["artist"]
63
    for ignore in ["The ", "Les "]:
64
        if key_x.startswith(ignore):
65
            key_x = key_x[len(ignore):]
66
        if key_y.startswith(ignore):
67
            key_y = key_y[len(ignore):]
68
    if key_x == key_y:
69
        key_x = x["title"]
70
        key_y = y["title"]
71
    return cmp(key_x, key_y)
72
73
def scan_folders():
74
    songs_dirs = ["/usr/share/performous/songs",
75
                  "/usr/local/share/games/ultrastar/songs",
76
                  "/usr/local/share/games/fretsonfire/data/songs",
77
                  "/usr/share/games/ultrastar/songs",
78
                  "/usr/share/games/fretsonfire/data/songs",
79
                  "~/.ultrastar/songs",
80
                  "~/.fretsonfire/songs",
81
                 ]
82
    available_songs = []
83
    for songs_dir in songs_dirs:
84
        songs_dir = os.path.expanduser(songs_dir)
85
        if not os.path.exists(songs_dir):
86
            continue
87
        for root, dirs, files in os.walk(songs_dir):
88
            for cur_file in files:
89
                if not cur_file.lower().endswith(".txt"):
90
                    continue
91
                available_songs.append(os.path.join(root, cur_file))
92
    return available_songs
93
94
def scan_file():
95
    available_songs = []
96
    songs_file = open(opts.input, "r")
97
    for line in songs_file:
98
        song_file = line.strip()
99
        if not song_file.lower().endswith(".txt"):
100
            continue
101
        if not os.path.exists(song_file):
102
            print >>sys.stderr, "WARNING: file %s in the input file " \
103
                               % song_file + "cannot be found !"
104
            continue
105
        available_songs.append(song_file)
106
    songs_file.close()
107
    return available_songs
108
109
def process_file(filepath, cover_size, dpi):
110
    song = {}
111
    file_h = open(filepath, "r")
112
    for line in file_h:
113
        for tag in ["artist", "title", "cover", "background", "video"]:
114
            if line.startswith("#%s:" % tag.upper()):
115
                song[tag] = ":".join(line.split(":")[1:]).strip()
116
    file_h.close()
117
    if "title" not in song:
118
        song["title"] = ""
119
    for tag in ["cover", "background"]:
120
        if tag not in song:
121
            continue
122
        song["image"] = os.path.realpath(os.path.join(
123
                        os.path.dirname(filepath), song[tag]))
124
        if not os.path.exists(song["image"]):
125
            del song["image"]
126
            continue
127
        # Resize the image
128
        tmpimg, tmpimg_path = tempfile.mkstemp(
129
                                prefix="mksonglist-", suffix=".jpg")
130
        img = PIL.Image.open(song["image"])
131
        img.thumbnail((cover_size * dpi, cover_size * dpi), PIL.Image.ANTIALIAS)
132
        img.save(os.fdopen(tmpimg, "w"), "JPEG")
133
        atexit.register(os.remove, tmpimg_path)
134
        song["image"] = tmpimg_path
135
        break
136
    if "video" in song:
137
        videofile = os.path.join(os.path.dirname(filepath), song["video"])
138
        if not os.path.exists(videofile):
139
            del song["video"]
140
    return song
141
142
def filter_songs(karaoke):
143
    filter_re = re.compile("(.*)\s+\(.*\)")
144
    for song in songs[:]:
145
        title = song["title"]
146
        if not karaoke and title.count("(Karaoke)"):
147
            songs.remove(song)
148
            continue
149
        filter_match = filter_re.match(title)
150
        if filter_match:
151
            short_title = filter_match.group(1)
152
            if short_title in [ s["title"] for s in songs ]:
153
                songs.remove(song)
154
                continue
155
156
def make_pdf(output, title, cover_size):
157
    doc = BaseDocTemplate(
158
                output, title=title,
159
                pagesize=defaultPageSize,
160
          )
161
    col_width = (PAGE_WIDTH - 2 * MARGIN - 2 * COLSEP) / 3
162
    col_height = PAGE_HEIGHT - 2 * MARGIN
163
    vspace_left = col_height % (cover_size + 2) # used to bottom-align the 2nd and 3rd columns
164
    vspace_left_left = (col_height - 2*cover_size) % (cover_size + 2) # used to bottom-align the left column
165
    frame_left = Frame(MARGIN, MARGIN, col_width, col_height,
166
                       leftPadding=0, rightPadding=0,
167
                       topPadding=vspace_left_left + 2*cover_size,
168
                       bottomPadding=0, id="left", showBoundary=0)
169
    frame_center = Frame(MARGIN + col_width + COLSEP, MARGIN,
170
                         col_width, col_height, leftPadding=0,
171
                         rightPadding=0, topPadding=vspace_left,
172
                         bottomPadding=0, id="center")
173
    frame_right = Frame(PAGE_WIDTH - MARGIN - col_width, MARGIN,
174
                        col_width, col_height, leftPadding=0,
175
                        rightPadding=0, topPadding=vspace_left,
176
                        bottomPadding=0, id="left")
177
    doc.addPageTemplates(PageTemplate("threecols", frames=[frame_left, frame_center, frame_right]))
178
    Story = []
179
    text_style = ParagraphStyle(name="labels", fontSize=12)
180
    table_style = TableStyle([
181
        ("VALIGN", (0,0), (-1,-1), "MIDDLE"),
182
        #("LEADING", (0,0), (-1,-1), 1),
183
        ("TOPPADDING", (0,0), (-1,-1), 1),
184
        ("BOTTOMPADDING", (0,0), (-1,-1), 1),
185
        ("LEFTPADDING", (0,0), (0,-1), 0),
186
        ("RIGHTPADDING", (0,0), (-1,-1), 0),
187
        ("LEFTPADDING", (1,0), (1,-1), 5),
188
        #("BOX", (0,0), (-1,-1), 0.25, colors.gray),
189
    ])
190
    table_contents = []
191
    for song in songs:
192
        try:
193
            image = None
194
            text = "%s - %s" % (song["artist"], song["title"])
195
            text = text.replace("&", "&amp;")
196
            if "video" in song:
197
                text = "<i>%s</i>" % text
198
            label = Paragraph(text, text_style)
199
            if "image" in song:
200
                image = Image(song["image"], cover_size, cover_size)
201
                #image.drawHeight = cover_size *I.drawHeight / I.drawWidth
202
                #image.drawWidth = cover_size
203
            else:
204
                image = Spacer(1, cover_size)
205
            row = [image, label]
206
            table_contents.append(row)
207
        except Exception:
208
            print >>sys.stderr, "Error when printing %s" % repr(song)
209
            raise
210
    Story.append(Table(table_contents,
211
                       colWidths=(cover_size + 2, col_width - (cover_size + 2)),
212
                       style=table_style))
213
    doc.build(Story)
214
215
def parse_opts():
216
    #usage = "usage: %prog [options]"
217
    parser = optparse.OptionParser()
218
    parser.add_option("-o", "--output", dest="output", default="songs.pdf",
219
                      help="Output file name (default: %default)")
220
    parser.add_option("-t", "--title", dest="title", default="Songs",
221
                      help="PDF title (default: %default)")
222
    parser.add_option("-k", "--karaoke", dest="karaoke", action="store_true",
223
                      help="List the 'karaoke' versions (default: False)")
224
    parser.add_option("-c", "--cover-size", dest="cover_size", default=32,
225
                      type="int", help="Cover size (default: %default pt)")
226
    parser.add_option("--dpi", dest="dpi", default=96,
227
                  help="Image resolution in Dots Per Inch (default: %default)")
228
    parser.add_option("-i", "--input", dest="input", metavar="FILE",
229
                      help="Read the song list from this file instead of "
230
                           "scanning the folders")
231
    opts, args = parser.parse_args()
232
    if len(args) != 0:
233
        parser.error("No arguments allowed")
234
    if opts.input and not os.path.exists(opts.input):
235
        parser.error("The file %s does not exist" % opts.input)
236
    return opts
237
238
if __name__ == '__main__':
239
    opts = parse_opts()
240
    get_songs(opts.cover_size, opts.dpi)
241
    #pprint(songs)
242
    filter_songs(opts.karaoke)
243
    make_pdf(opts.output, opts.title, opts.cover_size)
244
    print "PDF written to", opts.output