]>
code.delx.au - bg-scripts/blob - randombg.py
11 from optparse
import OptionParser
13 logging
.basicConfig(format
="%(levelname)s: %(message)s")
19 except ImportError as e
:
20 logging
.critical("Missing libraries! Exiting...", exc_info
=1)
26 def filter_images(filenames
):
27 extensions
= ('.jpg', '.jpe', '.jpeg', '.png', '.gif', '.bmp')
28 for filename
in filenames
:
29 _
, ext
= os
.path
.splitext(filename
)
30 if ext
.lower() in extensions
:
33 class BaseFileList(object):
34 """Base file list implementation"""
39 def add_path(self
, path
):
40 self
.paths
.append(path
)
42 def store_cache(self
, filename
):
44 logging
.debug("Attempting to store cache")
45 fd
= open(filename
, 'wb')
46 pickle
.dump(self
, fd
, 2)
47 logging
.debug("Cache successfully stored")
48 except Exception as e
:
49 warning("Storing cache: %s" % e
)
51 def load_cache(self
, filename
):
53 logging
.debug("Attempting to load cache from: %s" % filename
)
56 fd
= open(filename
, 'rb')
59 if tmp
.__class
__ != self
.__class
__:
60 raise ValueError("Using different file list type")
63 if self
.paths
!= tmp
.paths
:
64 raise ValueError("Path list changed")
66 # Overwrite this object with the other
67 for attr
, value
in tmp
.__dict
__.items():
68 setattr(self
, attr
, value
)
72 except Exception as e
:
73 logging
.warning("Loading cache: %s" % e
)
76 def add_to_favourites(self
):
77 '''Adds the current image to the list of favourites'''
78 self
.favourites
.append(self
.get_current_image())
81 raise NotImplementedError()
83 def get_next_image(self
):
84 raise NotImplementedError()
86 def get_prev_image(self
):
87 raise NotImplementedError()
89 def get_current_image(self
):
90 raise NotImplementedError()
96 class RandomFileList(BaseFileList
):
98 super(RandomFileList
, self
).__init
__()
100 self
.last_image
= None
102 def scan_paths(self
):
103 for path
in self
.paths
:
104 for dirpath
, dirsnames
, filenames
in os
.walk(path
):
105 for filename
in filter_images(filenames
):
106 self
.list.append(os
.path
.join(dirpath
, filename
))
108 def add_path(self
, path
):
109 self
.paths
.append(path
)
110 logging
.debug('Added path "%s" to the list' % path
)
112 def get_next_image(self
):
113 n
= random
.randint(0, len(self
.list)-1)
114 self
.last_image
= self
.list[n
]
115 logging
.debug("Picked file '%s' from list" % self
.last_image
)
116 return self
.last_image
118 def get_current_image(self
):
120 return self
.last_image
122 return self
.get_next_image()
125 return len(self
.list) == 0
128 class AllRandomFileList(BaseFileList
):
130 super(AllRandomFileList
, self
).__init
__()
132 self
.imagePointer
= 0
134 # Scan the input directory, and then randomize the file list
135 def scan_paths(self
):
136 logging
.debug("Scanning paths")
139 for path
in self
.paths
:
140 logging
.debug('Scanning "%s"' % path
)
141 for dirpath
, dirsnames
, filenames
in os
.walk(path
):
142 for filename
in filter_images(filenames
):
143 logging
.debug('Adding file "%s"' % filename
)
144 self
.list.append(os
.path
.join(dirpath
, filename
))
146 random
.shuffle(self
.list)
148 def add_path(self
, path
):
149 self
.paths
.append(path
)
150 logging
.debug('Added path "%s" to the list' % path
)
152 def store_cache(self
, filename
):
154 fd
= open(filename
, 'wb')
155 pickle
.dump(self
, fd
, 2)
156 logging
.debug("Cache successfully stored")
157 except Exception as e
:
158 logging
.warning("Storing cache", exc_info
=1)
160 def get_current_image(self
):
161 return self
.list[self
.imagePointer
]
163 def __inc_in_range(self
, n
, amount
= 1, rangeMax
= None, rangeMin
= 0):
164 if rangeMax
== None: rangeMax
= len(self
.list)
166 return (n
+ amount
) % rangeMax
168 def get_next_image(self
):
169 self
.imagePointer
= self
.__inc
_in
_range
(self
.imagePointer
)
170 imageName
= self
.list[self
.imagePointer
]
171 logging
.debug("Picked file '%s' (pointer=%d) from list" % (imageName
, self
.imagePointer
))
174 def get_prev_image(self
):
175 self
.imagePointer
= self
.__inc
_in
_range
(self
.imagePointer
, amount
=-1)
176 imageName
= self
.list[self
.imagePointer
]
177 logging
.debug("Picked file '%s' (pointer=%d) from list" % (imageName
, self
.imagePointer
))
181 return len(self
.list) == 0
184 class FolderRandomFileList(BaseFileList
):
185 """A file list that will pick a file randomly within a directory. Each
186 directory has the same chance of being chosen."""
188 super(FolderRandomFileList
, self
).__init
__()
189 self
.directories
= {}
190 self
.last_image
= None
192 def scan_paths(self
):
195 def add_path(self
, path
):
196 logging
.debug('Added path "%s" to the list' % path
)
197 for dirpath
, dirs
, filenames
in os
.walk(path
):
198 logging
.debug('Scanning "%s" for images' % dirpath
)
199 if dirpath
in self
.directories
:
201 filenames
= list(filter_images(filenames
))
203 self
.directories
[dirpath
] = filenames
204 logging
.debug('Adding "%s" to "%s"' % (filenames
, dirpath
))
206 logging
.debug("No images found in '%s'" % dirpath
)
208 def get_next_image(self
):
209 directory
= random
.choice(self
.directories
.keys())
210 logging
.debug('directory: "%s"' % directory
)
211 filename
= random
.choice(self
.directories
[directory
])
212 logging
.debug('filename: "%s"' % filename
)
213 return os
.path
.join(directory
, filename
)
215 def get_current_image(self
):
217 return self
.last_image
219 return self
.get_next_image()
222 return len(self
.directories
.values()) == 0
225 class Cycler(object):
226 def init(self
, options
, paths
, oneshot
=False):
227 self
.cycle_time
= options
.cycle_time
228 self
.cache_filename
= options
.cache_filename
230 logging
.debug("Initialising wallchanger")
231 wallchanger
.init(options
.background_colour
, options
.convert
)
233 logging
.debug("Initialising file list")
234 if options
.all_random
:
235 self
.filelist
= AllRandomFileList()
236 elif options
.folder_random
:
237 self
.filelist
= FolderRandomFileList()
239 self
.filelist
= RandomFileList()
242 self
.filelist
.add_path(path
)
244 if self
.filelist
.load_cache(self
.cache_filename
):
245 logging
.debug("Loaded cache successfully")
247 logging
.debug("Could not load cache")
248 self
.filelist
.scan_paths()
250 if self
.filelist
.is_empty():
251 logging
.error("No images were found. Exiting...")
261 self
.filelist
.store_cache(self
.cache_filename
)
263 def find_files(self
, options
, paths
):
268 image
= self
.filelist
.get_next_image()
269 wallchanger
.set_image(image
)
273 if self
.task
is not None:
275 self
.task
= asyncsched
.schedule(self
.cycle_time
, next
)
276 logging
.debug("Reset timer for %s seconds" % self
.cycle_time
)
277 self
.filelist
.store_cache(self
.cache_filename
)
279 def cmd_reload(self
):
280 image
= self
.filelist
.get_current_image()
281 wallchanger
.set_image(image
)
285 image
= self
.filelist
.get_next_image()
286 wallchanger
.set_image(image
)
290 image
= self
.filelist
.get_prev_image()
291 wallchanger
.set_image(image
)
294 def cmd_rescan(self
):
295 self
.filelist
.scan_paths()
298 if self
.task
is not None:
305 def cmd_favourite(self
):
306 self
.filelist
.add_to_favourites()
308 class Server(asynchat
.async_chat
):
309 def __init__(self
, cycler
, sock
):
310 asynchat
.async_chat
.__init
__(self
, sock
)
313 self
.set_terminator(b
'\n')
315 def collect_incoming_data(self
, data
):
316 self
.ibuffer
.append(data
.decode("ascii"))
318 def found_terminator(self
):
319 line
= "".join(self
.ibuffer
).lower()
321 prefix
, cmd
= line
.split(None, 1)
323 logging
.debug('Bad line received "%s"' % line
)
325 if hasattr(self
.cycler
, "cmd_" + cmd
):
326 logging
.debug('Executing command "%s"' % cmd
)
327 getattr(self
.cycler
, "cmd_" + cmd
)()
329 logging
.debug('Unknown command received "%s"' % cmd
)
332 class SockHackWrap(object):
333 def __init__(self
, sock
, addr
):
336 def getpeername(self
):
338 def __getattr__(self
, key
):
339 return getattr(self
.__sock
, key
)
341 class Listener(asyncore
.dispatcher
):
342 def __init__(self
, socket_filename
, cycler
):
343 asyncore
.dispatcher
.__init
__(self
)
345 self
.create_socket(socket
.AF_UNIX
, socket
.SOCK_STREAM
)
346 self
.bind(socket_filename
)
347 self
.listen(2) # Backlog = 2
349 def handle_accept(self
):
350 sock
, addr
= self
.accept()
351 Server(self
.cycler
, SockHackWrap(sock
, addr
))
357 def do_server(options
, paths
):
359 sock
= socket
.socket(socket
.AF_UNIX
, socket
.SOCK_STREAM
)
360 sock
.connect(options
.socket_filename
)
361 print("Server is already running! Sending exit command.", file=sys
.stderr
)
362 sock
= sock
.makefile("w")
363 sock
.write("cmd exit\n")
365 except Exception as e
:
369 os
.unlink(options
.socket_filename
)
374 listener
= Listener(options
.socket_filename
, cycler
)
375 # Initialisation of Cycler delayed so we grab the socket quickly
376 cycler
.init(options
, paths
)
379 except KeyboardInterrupt:
383 def do_client(options
, args
):
386 sock
= socket
.socket(socket
.AF_UNIX
, socket
.SOCK_STREAM
)
387 sock
.connect(options
.socket_filename
)
388 sock
= sock
.makefile(mode
="w")
389 for i
, cmd
in enumerate(args
):
390 sock
.write("cmd %s\n" % cmd
)
391 if i
< len(args
) - 1:
392 time
.sleep(options
.cycle_time
)
395 def do_oneshot(options
, paths
):
397 cycler
.init(options
, paths
, oneshot
=True)
400 parser
= OptionParser(
401 description
= "Cycles through random background images.",
403 "\n(server) %prog [options] dir [dir2 ...]"
404 "\n(client) %prog [options] [next|prev|rescan|reload|pause] [...]"
405 "\nThe first instance to be run will be the server.\n"
407 parser
.add_option("-v", '-d', "--verbose", "--debug",
408 action
="count", dest
="verbose", default
=0,
409 help="Make the louder (good for debugging, or those who are curious)")
410 parser
.add_option("-b", "--background-colour",
411 action
="store", type="string", dest
="background_colour", default
="black",
412 help="Change the default background colour that is displayed if the image is not in the correct aspect ratio [Default: %default]")
413 parser
.add_option("--all-random",
414 action
="store_true", dest
="all_random", default
=False,
415 help="Make sure that all images have been displayed before repeating an image")
416 parser
.add_option("-1", "--oneshot",
417 action
="store_true", dest
="oneshot", default
=False,
418 help="Set one random image and terminate immediately.")
419 parser
.add_option("--folder-random",
420 action
="store_true", dest
="folder_random", default
=False,
421 help="Give each folder an equal chance of having an image selected from it")
422 parser
.add_option("--convert",
423 action
="store_true", dest
="convert", default
=False,
424 help="Do conversions using ImageMagick or PIL, don't rely on the window manager")
425 parser
.add_option("--cycle-time",
426 action
="store", type="int", default
=1800, dest
="cycle_time",
427 help="Cause the image to cycle every X seconds")
428 parser
.add_option("--socket",
429 action
="store", type="string", dest
="socket_filename", default
=os
.path
.expanduser('~/.randombg_socket'),
430 help="Location of the command/control socket.")
431 parser
.add_option("--cache-file",
432 action
="store", type="string", dest
="cache_filename", default
=os
.path
.expanduser('~/.randombg_cache'),
433 help="Stores the location of the last image to be loaded.")
434 parser
.add_option("--server",
435 action
="store_true", dest
="server", default
=False,
436 help="Run in server mode to listen for clients.")
440 parser
= build_parser()
441 options
, args
= parser
.parse_args(sys
.argv
[1:])
443 if options
.verbose
== 1:
444 logging
.getLogger().setLevel(logging
.INFO
)
445 elif options
.verbose
>= 2:
446 logging
.getLogger().setLevel(logging
.DEBUG
)
449 do_server(options
, args
)
453 do_oneshot(options
, args
)
457 do_client(options
, args
)
459 except Exception as e
:
460 print("Failed to connect to server:", e
, file=sys
.stderr
)
463 if __name__
== "__main__":