root/hodgestar/PythonCode/CursesMediaBrowser/browse.py

Revision 322, 5.7 kB (checked in by simon, 4 years ago)

Curses browser for media on asfaloth.

  • Property svn:mime-type set to text/python-source
  • Property svn:eol-style set to native
Line 
1#!/usr/bin/env python
2
3import os
4import sys
5import curses
6import curses.wrapper
7import curses.ascii
8
9SOURCES = [
10    'cd.txt',
11    'Movies',
12    'Series',
13]
14
15def titles_from_file(filename):
16    """Return a list of (title, info) pairs from the filename.
17   
18       Each line of file represents a list of titles
19       separate by semi-colons.
20       
21       Returned info consists of the CD number (i.e. line in
22       the file) and full list of titles on that CD.
23       """
24    f = file(filename)
25    title_pairs = []
26    try:
27        for i, line in enumerate(f):
28            titles = [x.strip() for x in line.split(';')]
29            info = "CD #: %d\n" % i + "\n".join(titles)
30            for title in titles:
31                title_pairs.append((title, info))
32    finally:
33        f.close()
34    return title_pairs
35
36def titles_from_dir(dirname):
37    """Return a list of (title, info) pairs from the dirname.
38   
39       Each entry in dirname is a title.
40       None is returned for info.
41       """
42    title_pairs = []
43    def walker(arg, dirname, fnames):
44        """Walk directory, empty fnames so sub folders aren't processed."""
45        for entry in fnames:
46            title_pairs.append((entry, None))
47        del fnames[:]
48
49    os.path.walk(dirname, walker, None)
50    return title_pairs
51
52def compile_list(sources):
53    """Return a list of titles (title, info, src) triples."""
54    titles = []
55    for src in sources:
56        if os.path.isfile(src):
57            titles.extend([(title, info, src)
58                            for title, info in titles_from_file(src)])
59        elif os.path.isdir(src):
60            titles.extend([(title, info, src)
61                            for title, info in titles_from_dir(src)])
62    return titles
63
64def matches(search, title):
65    """Return true if title matches seach.
66   
67       To match, the characters in search must occur in the same
68       order in title.
69       """
70    pos = 0
71    title = title.lower()
72    for c in search:
73        pos = title.find(c, pos) + 1
74        if pos == 0:
75            return False
76    return True
77
78def setup_windows(window):
79    """Setup sub windows."""
80    h, w = window.getmaxyx()
81
82    search_h, search_lines = 0, 1
83    sep1_h = search_h + search_lines
84
85    info_lines = 10
86    sep2_h = h - info_lines -1
87    info_h = sep2_h + 1
88
89    list_h, list_lines = sep1_h + 1, sep2_h - sep1_h - 1
90
91    window.hline(sep1_h, 0, "-", w)
92    window.hline(sep2_h, 0, "-", w)
93
94    search_window = window.derwin(search_lines, w, search_h, 0)
95    list_window = window.derwin(list_lines, w, list_h, 0)
96    info_window = window.derwin(info_h, 0)
97
98    return search_window, list_window, info_window, list_lines
99
100def curses_gui(window, titles):
101    """GUI for curses."""
102
103    search_window, list_window, info_window, list_len = setup_windows(window)
104    list_window.scrollok(1)
105
106    KEY_UP = [ 259 ]
107    KEY_DOWN = [ 258 ]
108    KEY_BKSPC = [ 260, 263 ]
109
110    titles.sort()
111    all_titles = titles[:]
112
113    list_pos = [0]
114    cursor_offset = [0]
115    def list_populate():
116        list_window.erase()
117        highlight_pos = min(list_len, len(titles)) - cursor_offset[0]
118        for i, (title, info, src) in \
119              enumerate(reversed(titles[list_pos[0]+1:list_pos[0]+list_len])):
120            if i + 1 == highlight_pos:
121                list_window.insstr(title, curses.A_STANDOUT)
122            else:
123                list_window.insstr(title)
124            list_window.insertln()
125        if list_pos[0] < len(titles):
126            if highlight_pos == min(list_len, len(titles)):
127                list_window.insstr(titles[list_pos[0]][0], curses.A_STANDOUT)
128            else:
129                list_window.insstr(titles[list_pos[0]][0])
130        list_window.refresh()
131    def list_up():
132        if list_pos[0] > 0:
133            list_pos[0] -= 1
134        elif cursor_offset[0] > 0:
135            cursor_offset[0] -= 1
136        list_populate()
137        info_populate()
138    def list_down():
139        if list_pos[0] + list_len < len(titles):
140            list_pos[0] += 1
141        elif cursor_offset[0] + list_pos[0] < len(titles) - 1:
142            cursor_offset[0] += 1
143        list_populate()
144        info_populate()
145
146    def info_populate():
147        info_window.erase()
148        if titles:
149            title, info, src = titles[list_pos[0]+cursor_offset[0]]
150            if info:
151                for line in reversed(info.split("\n")):
152                    info_window.insstr(line)
153                    info_window.insertln()
154            info_window.insstr(" %s" % src)
155            info_window.insstr("Source:", curses.A_BOLD)
156            info_window.insertln()
157            info_window.insstr(" %s" % title)
158            info_window.insstr("Title:", curses.A_BOLD)
159        info_window.refresh()
160
161    search_key = [""]
162    def update_search(c):
163        list_pos[0] = 0
164        cursor_offset[0] = 0
165        if c is None:
166            pass
167        elif c in KEY_BKSPC:
168            search_key[0] = search_key[0][:-1]
169        else:
170            search_key[0] += chr(c).lower()
171        search_window.erase()
172        search_window.insstr(" " + search_key[0])
173        search_window.insstr("Search:", curses.A_BOLD)
174        search_window.refresh()
175        titles[:] = [t for t in all_titles if matches(search_key[0], t[0])]
176        list_populate()
177        info_populate()
178
179    c = None
180    update_search(None)
181    while c != ord('q'):
182        c = window.getch()
183        if c in KEY_UP:
184            list_up()
185        elif c in KEY_DOWN:
186            list_down()
187        elif curses.ascii.isalnum(c) or c in KEY_BKSPC:
188            update_search(c)
189        else:
190            pass
191
192def main(args):
193    """Allow browsing of titles."""
194    titles = compile_list(SOURCES)
195    curses.wrapper(curses_gui, titles)
196
197if __name__ == "__main__":
198    sys.exit(main(sys.argv))
Note: See TracBrowser for help on using the browser.