Last active
June 1, 2022 22:22
-
-
Save jbylund/5f307bef7f36438c05e74015e9f090ea to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
""" | |
let's try to do this without subprocess? | |
""" | |
# stdlib | |
import argparse | |
import datetime | |
import os | |
import shelve | |
# third party | |
import cachetools | |
# pylint: disable=import-outside-toplevel | |
def download_file(url, local_filename): | |
"""Download the given url to the local path""" | |
import requests | |
with requests.get(url, stream=True) as response: | |
response.raise_for_status() | |
with open(local_filename, "wb") as fh: | |
for chunk in response.iter_content(chunk_size=8192): | |
fh.write(chunk) | |
def unpack_image(filename, destdir): | |
"""Unpack all the images of the filename into destdir""" | |
from PIL import Image | |
import pillow_heif | |
heif_file = pillow_heif.read_heif(filename) | |
for idx, heif_frame in enumerate( | |
heif_file | |
): # you still can use it without iteration, like before. | |
pil_image = Image.frombytes( | |
heif_frame.mode, | |
heif_frame.size, | |
heif_frame.data, | |
"raw", # ??? | |
heif_frame.mode, | |
heif_frame.stride, | |
) | |
pil_image.save(f"{destdir}/{idx}.jpg", quality=95, format="jpeg") | |
def get_images(imagename): | |
"""Return the list of images for the given background""" | |
frames_dir = f"{imagename}/extracted" | |
os.makedirs(frames_dir, exist_ok=True) | |
# if the raw file doesn't exist we need to fetch it | |
rawpath = f"{imagename}/raw.heic" | |
if not os.path.isfile(rawpath): | |
url = get_sources()[imagename] | |
download_file(url, rawpath) | |
# now if the pieces haven't been extracted need to extract them | |
if not os.path.exists(f"{frames_dir}/0.jpg"): | |
unpack_image(rawpath, frames_dir) | |
images = [] | |
frames_dir = os.path.abspath(frames_dir) | |
for root, dirs, files in os.walk(frames_dir): | |
dirs[:] = [] # don't recurse | |
for ifile in files: | |
if ifile.endswith("jpg"): | |
images.append(os.path.join(root, ifile)) | |
return sorted(images) | |
class ShelveCache: | |
"""Simple cache backed by a shelve db""" | |
def __init__(self, filepath): | |
"""Construct the cache""" | |
self.filepath = filepath | |
self.backing = shelve.open(self.filepath) | |
def __getitem__(self, key): | |
"""Get an item from the cache""" | |
return self.backing[key] | |
def __setitem__(self, key, value): | |
"""Save an item in the cache""" | |
self.backing[key] = value | |
def identity(key): | |
"""Return the thing""" | |
return key | |
@cachetools.cached(cache=ShelveCache("/tmp/.brightness"), key=identity) | |
def get_image_brightness(filename): | |
"""Get the "brightness" of the given image""" | |
import subprocess | |
cmd = f"convert {filename} -resize 1x1! -format %[pixel:B] info:-".split() | |
res = subprocess.check_output(cmd).strip().decode() | |
return float(res.partition("(")[2].partition("%")[0]) | |
def get_sources(): | |
"""Get the possible images & urls""" | |
return { | |
"catalina": "https://cdn.dynamicwallpaper.club/wallpapers/v5y04cx6k9k/Catalina.heic", | |
"dome": "https://cdn.dynamicwallpaper.club/wallpapers/3mj0fahqhsv/Dome.heic", | |
"monterey": "https://cdn.dynamicwallpaper.club/wallpapers/la4wfuwtkg/macOS%20Monterey.heic", | |
} | |
def get_time_image_pairs(images): | |
"""Return a list of (timestamp, image) pairs spread evenly over the day""" | |
image_to_brightness = {image: get_image_brightness(image) for image in images} | |
by_brightness = sorted(image_to_brightness.items(), key=lambda x: x[-1]) | |
window = datetime.timedelta(hours=12) | |
step = window / (len(images) - 1) | |
time_to_image = [] | |
start_of_day = datetime.datetime.today().replace( | |
hour=0, minute=0, second=0, microsecond=0 | |
) | |
for imageidx, (image, _) in enumerate(by_brightness + by_brightness[-2::-1]): | |
time_to_image.append((start_of_day + imageidx * step, image)) | |
return time_to_image | |
def get_args(): | |
"""Argument parsing""" | |
sources = get_sources() | |
parser = argparse.ArgumentParser() | |
parser.add_argument("--binary", action="store_true") | |
parser.add_argument("--image", choices=sorted(sources), default=min(sources)) | |
return vars(parser.parse_args()) | |
def main(): | |
"""Update the live wallpaper""" | |
args = get_args() | |
# get the images | |
images = get_images(args["image"]) | |
# get the images spaced out by time of day | |
time_to_image = get_time_image_pairs(images) | |
current_time = datetime.datetime.now() | |
idx = 0 | |
for idx, (imagetime, _image) in enumerate(time_to_image): | |
if current_time < imagetime: | |
break | |
prev_ts, prev_path = time_to_image[idx - 1] | |
next_ts, next_path = time_to_image[idx] | |
pct_next = 100 * (current_time - prev_ts) / (next_ts - prev_ts) | |
pct_prev = 100 - pct_next | |
out_path = os.path.expanduser("~/Pictures/live_wallpaper.jpg") | |
if args["binary"]: | |
import shutil | |
if pct_prev < pct_next: | |
# use next | |
shutil.copy(next_path, out_path) | |
else: | |
# use prev | |
shutil.copy(prev_path, out_path) | |
else: | |
import subprocess | |
print(f"{pct_prev}% {prev_path} and {pct_next}% {next_path}") | |
swap_path = os.path.expanduser("~/Pictures/live_wallpaper.swp.jpg") | |
cmd = f"composite -blend {pct_prev} {prev_path} {next_path} {swap_path}".split() | |
subprocess.check_call(cmd) | |
os.replace(swap_path, out_path) | |
if "__main__" == __name__: | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment