Skip to content

Instantly share code, notes, and snippets.

@xunil154
Last active September 20, 2018 06:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xunil154/5bcd2ae80cb9e87dd0a880c98d60765d to your computer and use it in GitHub Desktop.
Save xunil154/5bcd2ae80cb9e87dd0a880c98d60765d to your computer and use it in GitHub Desktop.
Wallpaper chooser script. Looks for wallpapers that match your current X11 aspect ratio
#!/usr/bin/env python2.7
"""
Dependencies:
* Python Image Library (PIL)
* Xlib
* feh (system command)
This script crawls recursivly through the directory provided as the first
argument and looks for any files matching the extensions: jpg, png, jpeg, tiff
The aspect ratio of each image is then calculated
The current aspect ratio of the root X11 window is then computed
A search is then ran looking for images that match the current aspect ratio
within a 20% error
A wallpaper is then randomly selected and applied
https://github.com/xunil154
"""
import sys
import subprocess
import os
import random
import bisect
try:
from PIL import Image
except:
print("Python library 'PIL' not found - must be present to work")
sys.exit(1)
try:
from Xlib import display
except:
print("Python library 'Xlib' not found - must be present to work")
sys.exit(1)
if os.system('which feh > /dev/null') != 0:
print("System command 'feh' not found - must be present to work")
sys.exit(1)
EXTENSIONS = ["jpg", "png", "jpeg", "tiff"]
WPs = []
_processed = set()
FIT_THRESHOLD = 5.5 # Ignore any wallpapers below this threshold
class WP(object):
image = None
path = None
aspect = 0.0000001
resolution = 0
fit = 0
def __init__(self, path):
self.path = path
self.image = Image.open(self.path)
size = self.image.size
self.resolution = size[0] * size[1]
self.aspect = float(max(size)) / float(min(size)) # aspect ratio
# returns abs match, 0 means perfect match
def fit(self, t_width, t_height):
# Aspect plays a more important role than resolution
# t_aspect will always be >= 1
t_aspect = float(max(t_width, t_height)) / float(min(t_width,t_height))
fit_aspect = abs(t_aspect - self.aspect)
fit_aspect_r = 1 - (1 / float(max(t_aspect, self.aspect)) / float(min(t_aspect,self.aspect)))
self.fit = fit_aspect + 5 # Add one to leave room for resolution tuning
# Resolution is used to reduce that extra 1 added above
# This is only done if our image resolution is greater than the target
# It also takes into account how close it is to the aspect ratio
# e.g a high res 16:9 tripple monitor should not modify the fit at all
# for a single 16:9 monitor because the aspect ratio is so different
t_resolution = t_width * t_height
if t_resolution < self.resolution:
fit_res = 5 - ((t_resolution - self.resolution) / float(t_resolution))
fit_res_mod = 1 / (fit_res / (fit_aspect + 0.00000001))
self.fit = self.fit - fit_res_mod
return self.fit
def register_wp(image):
if image in _processed:
return
wp = WP(image)
WPs.append(wp)
_processed.add(image)
def set_wp(image):
cmd=["/usr/bin/feh","--bg-center","--no-xinerama", image]
cmd=["/usr/bin/feh","--bg-max","--no-xinerama", image]
cmd=["/usr/bin/feh","--bg-fill","--no-xinerama", image]
subprocess.Popen(cmd)
def get_screen_size():
d = display.Display()
s = d.screen()
w = s.width_in_pixels
h = s.height_in_pixels
return w, h
def get_possible_wallpapers():
t_w, t_h = get_screen_size()
possible = [x for x in WPs if x.fit(t_w, t_h) <= FIT_THRESHOLD]
possible.sort(key=lambda x: -1 * x.fit)
#for wp in possible:
# print("WP: %s:\t%f\t%dx%d\t%d\t%d" % (wp.path, wp.fit, wp.image.size[0], wp.image.size[1], wp.resolution, t_w * t_h))
return possible
# Random weighted choice
def pick_wp(choices):
# Normalize
# [ 0.2, 0.3, 1, 2, 4 ]
# [ 1/0.2, 1/0.3, 1/1, 1/2, 1/4 ]
# [ 5, 3.3, 1, 0.5, 0.25 ]
norm = [1-(1/x.fit) for x in choices]
norm_max = max(norm)
norm = [x * 1/norm_max for x in norm]
running_total = sum(norm)
rnd = random.random() * running_total
for i, w in enumerate(norm):
rnd -= w
if rnd < 0:
#print("Picked position %d, weight %f, fit %f" %( i, w, choices[i].fit))
#print(choices[i].path)
return choices[i]
return None
def process_dir(path):
wallpapers = []
for root, dirs, files in os.walk(basedir):
for f in files:
for e in EXTENSIONS:
if e in f.lower():
wallpapers.append(os.path.join(root, f))
for wp in wallpapers:
register_wp(wp)
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: %s <path>"% sys.argv[0])
sys.exit(1)
basedir = sys.argv[1]
process_dir(basedir)
available = get_possible_wallpapers() # look within 20%
# Pick using an inverse log function to priortize the higher matches
wp = pick_wp(available)
set_wp(wp.path)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment