diff --git a/daily-wallpaper.py b/daily-wallpaper.py index 927df15..d8d5ea8 100644 --- a/daily-wallpaper.py +++ b/daily-wallpaper.py @@ -6,26 +6,47 @@ import importlib import logging import slugify import settings +import time +import croniter def main(): - logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) - config = settings.load_settings() + logging.basicConfig(level=config['general']['log_level'], format="%(asctime)s [%(levelname)s] %(message)s", force=True) logging.debug(f"Config: {config}") + chosen_providers = config['general']['provider'] + + # Daemon mode + if config['daemon']['daemon']: + while True: + for provider in chosen_providers: + download_with_provider(provider, config) + if config['daemon']['cron'] != "": + now = datetime.datetime.now() + cron = croniter.croniter(config['daemon']['cron'], now) + next_run = cron.get_next(datetime.datetime) + logging.info(f"Next run: {next_run}") + time.sleep((next_run - now).total_seconds()) + else: + time.sleep(config['daemon']['interval']) + # Download once + else: + for provider in chosen_providers: + download_with_provider(provider, config) + +def download_with_provider(provider_name, config): session = requests.Session() session.headers.update({ "User-Agent": config['general']['user_agent'] }) # Convenience variables, could be inlined - provider_name = config['general']['provider'] - provider_settings = config[config['general']['provider']] + provider_settings = config[provider_name] if provider_name in config else None download_location = os.path.abspath(os.path.expanduser(config['general']['location'])) # Load the provider module - provider = importlib.import_module(f"providers.{config['general']['provider']}") + provider = importlib.import_module(f"providers.{provider_name}") # Create an instance of the provider provider_obj = getattr(provider, provider_name.title())(provider_settings, session) @@ -33,17 +54,25 @@ def main(): image_url, image_title = provider_obj.get_image_info() logging.debug(f"Image URL: {image_url}") - # Download the image - image = session.get(image_url).content - + # Variables for the file path + date = datetime.datetime.now().strftime("%Y-%m-%d") + image_title = slugify.slugify(image_title) + file_path = f"{download_location}/{provider_name.title()}/{date} [{image_title}].jpg" + # Check if we should include the title in the filename + if not config['general']['include_title']: + file_path = f"{download_location}/{provider_name.title()}/{date}.jpg" + # Create the download location if it doesn't exist if not os.path.exists(download_location): os.mkdir(download_location) if not os.path.exists(f"{download_location}/{provider_name.title()}"): os.mkdir(f"{download_location}/{provider_name.title()}") + # Check if the file exists and if we should overwrite it + if os.path.exists(file_path) and not config['general']['overwrite']: + return - date = datetime.datetime.now().strftime("%Y-%m-%d") - image_title = slugify.slugify(image_title) - with open(f"{download_location}/{provider_name.title()}/{date} [{image_title}].jpg", "wb") as file: + # Download the image + image = session.get(image_url).content + with open(file_path, "wb") as file: file.write(image) if __name__ == "__main__": diff --git a/poetry.lock b/poetry.lock index 66cf0da..76a791d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -125,6 +125,21 @@ files = [ {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, ] +[[package]] +name = "croniter" +version = "5.0.1" +description = "croniter provides iteration for datetime object with cron like format" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.6" +files = [ + {file = "croniter-5.0.1-py2.py3-none-any.whl", hash = "sha256:eb28439742291f6c10b181df1a5ecf421208b1fc62ef44501daec1780a0b09e9"}, + {file = "croniter-5.0.1.tar.gz", hash = "sha256:7d9b1ef25b10eece48fdf29d8ac52f9b6252abff983ac614ade4f3276294019e"}, +] + +[package.dependencies] +python-dateutil = "*" +pytz = ">2021.1" + [[package]] name = "idna" version = "3.10" @@ -139,6 +154,20 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "python-slugify" version = "8.0.4" @@ -156,6 +185,17 @@ text-unidecode = ">=1.3" [package.extras] unidecode = ["Unidecode (>=1.1.1)"] +[[package]] +name = "pytz" +version = "2024.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, +] + [[package]] name = "requests" version = "2.32.3" @@ -177,6 +217,17 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + [[package]] name = "text-unidecode" version = "1.3" @@ -218,4 +269,4 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "cbcfeef2301c00347932493e38948d8930eb3e2a1cc65f1781d86d50614ac3f6" +content-hash = "65c5d981b2146aee4317e6d04fc32e949094e70be5dd7a26f44c76614a92ee33" diff --git a/providers/unsplash.py b/providers/unsplash.py index 4e15aa2..ff8394a 100644 --- a/providers/unsplash.py +++ b/providers/unsplash.py @@ -13,7 +13,7 @@ class Unsplash(Provider): super().__init__(settings, session) def get_image_info(self): - query = "https://unsplash.com/collections/1459961/photo-of-the-day-(archive)" + query = f"https://unsplash.com/collections/{self.settings['collection']}" logging.debug(f"Query: {query}") response = self.session.get(query).text diff --git a/pyproject.toml b/pyproject.toml index e947a99..6586cd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ requests = "^2.32" urllib3 = "^1.26" python-slugify = "^8.0" tomlkit = "^0.13.2" +croniter = "^5.0" [build-system] diff --git a/settings.py b/settings.py index 2677a58..7546db8 100644 --- a/settings.py +++ b/settings.py @@ -1,3 +1,4 @@ +import logging import os.path import tomlkit @@ -6,20 +7,14 @@ user_path = os.path.expanduser("~/.config/daily-wallpaper/config.toml") def default_settings(): general = tomlkit.table() - general.add("interval", 86400) - general["interval"].comment("Interval in seconds (24 hours)") - general.add("start", 7200) - general["start"].comment("Start time in seconds (2:00 AM)") general.add("location", "~/Pictures/Wallpapers") general["location"].comment("Download location") - general.add("provider", "bing") - general["provider"].comment("Which wallpaper provider to use") + general.add("provider", ["bing", "unsplash", "wikimedia"]) + general["provider"].comment("Which wallpaper provider to use, in order of preference (do no include if you don't want to download the file)") general.add("include_title", True) general.value.item("include_title").comment("Include image title in filename") general.add("set_wallpaper", False) general.value.item("set_wallpaper").comment("Set wallpaper after download") - general.add("daemon", False) - general.value.item("daemon").comment("Run as daemon (continuously in the background)") general.add("log", False) general.value.item("log").comment("Log to file, located in the download location") general.add("log_level", "INFO") @@ -30,6 +25,15 @@ def default_settings(): general["user_agent"].comment("User-Agent to use for requests, change to avoid being blocked or to comply with ToS") general.add(tomlkit.nl()) + daemon = tomlkit.table() + daemon.add("daemon", False) + daemon.value.item("daemon").comment("Run as daemon (continuously in the background)") + daemon.add("interval", 86400) + daemon["interval"].comment("Interval in seconds (24 hours)") + daemon.add("cron", "0 0 * * *") + daemon["cron"].comment("Cron expression, overrides interval") + daemon.add(tomlkit.nl()) + bing = tomlkit.table() bing.add("size", "UHD") bing["size"].comment('Image size, possible values: "UHD", "1920x1080"') @@ -56,6 +60,7 @@ def default_settings(): defaults = tomlkit.document() defaults.add("general", general) + defaults.add("daemon", daemon) defaults.add("bing", bing) defaults.add("unsplash", unsplash) defaults.add("wikimedia", wikimedia) @@ -63,6 +68,17 @@ def default_settings(): return defaults def load_settings(): + # TODO: Find a better way to do this + if os.environ.get("RAPID_DEVELOPMENT") is not None: + if os.path.exists(user_path): + os.remove(user_path) + if os.path.exists(local_path): + os.remove(local_path) + settings = default_settings() + with open(local_path, mode='w') as file: + tomlkit.dump(settings, file) + return settings + if os.path.exists(local_path): settings = tomlkit.parse(open(local_path, mode='r').read()) elif os.path.exists(user_path):