Created
March 21, 2021 21:46
-
-
Save Michcioperz/5d1cd5e94e516b3cfa347f2317d8f931 to your computer and use it in GitHub Desktop.
dynamic background chooser (ve means violet evergarden)
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 nix-shell | |
#!nix-shell -i python3 -p "python3.withPackages (ps: [ps.astral ps.pytz])" | |
import dataclasses | |
import datetime | |
import enum | |
import functools | |
import itertools | |
import math | |
import subprocess | |
import time | |
import astral | |
import astral.sun | |
import pytz | |
class Phase(enum.Enum): | |
DAWN = "dawn" | |
SUNRISE = "sunrise" | |
SUNSET = "sunset" | |
DUSK = "dusk" | |
@dataclasses.dataclass | |
class Vampire: | |
timezone: str = "Europe/Warsaw" | |
city: astral.LocationInfo = astral.LocationInfo( | |
"Lublin", "Poland", timezone, 51.24, 22.57 | |
) | |
def sun(self, day: datetime.date) -> astral.sun.sun: | |
return astral.sun.sun(self.city.observer, date=day, tzinfo=self.city.timezone) | |
@functools.cached_property | |
def now(self) -> datetime.datetime: | |
return pytz.timezone(self.timezone).localize(datetime.datetime.now()) | |
@property | |
def today(self) -> datetime.date: | |
return self.now.date() | |
@property | |
def yesterday(self) -> datetime.date: | |
return self.today - datetime.timedelta(days=1) | |
@property | |
def tomorrow(self) -> datetime.date: | |
return self.today + datetime.timedelta(days=1) | |
def the_last( | |
self, dt1: datetime.datetime, dt2: datetime.datetime | |
) -> datetime.datetime: | |
later, earlier = (dt1, dt2) if dt1 > dt2 else (dt2, dt1) | |
assert earlier < self.now | |
return later if later < self.now else earlier | |
def the_next( | |
self, dt1: datetime.datetime, dt2: datetime.datetime | |
) -> datetime.datetime: | |
later, earlier = (dt1, dt2) if dt1 > dt2 else (dt2, dt1) | |
assert later > self.now | |
return earlier if earlier > self.now else later | |
def next_suns( | |
self, key: ["dawn", "sunrise", "sunset", "dusk"] | |
) -> datetime.datetime: | |
return self.the_next(self.sun(self.today)[key], self.sun(self.tomorrow)[key]) | |
def last_suns( | |
self, key: ["dawn", "sunrise", "sunset", "dusk"] | |
) -> datetime.datetime: | |
return self.the_last(self.sun(self.today)[key], self.sun(self.yesterday)[key]) | |
def phase(self) -> (Phase, Phase): | |
phases = [Phase.DAWN, Phase.SUNRISE, Phase.SUNSET, Phase.DUSK] | |
right = sorted((self.next_suns(phase.value), phase) for phase in phases)[0][1] | |
left = phases[(phases.index(right) - 1) % len(phases)] | |
return (left, right) | |
def progress(self) -> float: | |
left, right = self.phase() | |
left_dt, right_dt = self.last_suns(left.value), self.next_suns(right.value) | |
return (self.now - left_dt) / (right_dt - left_dt) | |
@dataclasses.dataclass | |
class Criterium: | |
phases: (Phase, Phase) | |
path_fstring: str | |
indices: [int] | |
def try_match(self, lr: (Phase, Phase)) -> bool: | |
return self.phases == lr | |
def choose(self, progress: float) -> str: | |
return self.path_fstring.format( | |
self.indices[math.floor(progress * len(self.indices))] | |
) | |
criteria = [ | |
Criterium( | |
phases=(Phase.DAWN, Phase.SUNRISE), | |
path_fstring="/home/michcioperz/ve-lapses/ep01-sunrise-{:02d}.jpg", | |
indices=range(1, 87 + 1), | |
), | |
Criterium( | |
phases=(Phase.SUNRISE, Phase.SUNSET), | |
path_fstring="/home/michcioperz/ve-lapses/ep01-day-{:02d}.jpg", | |
indices=range(1, 70 + 1), | |
), | |
Criterium( | |
phases=(Phase.SUNSET, Phase.DUSK), | |
path_fstring="/home/michcioperz/ve-lapses/ep01-day-{:02d}.jpg", | |
indices=range(71, 97 + 1), | |
), | |
Criterium( | |
phases=(Phase.DUSK, Phase.DAWN), | |
path_fstring="/home/michcioperz/ve-lapses/ep01-nightskyspinny-{:02d}.jpg", | |
indices=range(1, 75 + 1), | |
), | |
] | |
def choose() -> str: | |
v = Vampire() | |
phase = v.phase() | |
progress = v.progress() | |
for crit in criteria: | |
if crit.try_match(phase): | |
return crit.choose(progress) | |
assert False | |
def daemon(): | |
last = None | |
while True: | |
pick = choose() | |
if last != pick: | |
last = pick | |
print(datetime.datetime.now(), pick) | |
subprocess.check_call(["feh", "--bg-scale", pick]) | |
time.sleep(60) | |
if __name__ == "__main__": | |
daemon() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment