Implementing support for webp and other formats

This resolves the issue #19. Also there is improved translation handling
This commit is contained in:
Roman 2023-12-31 17:36:19 +01:00
parent 26371745f0
commit 3cd2f7ff9c
6 changed files with 59 additions and 53 deletions

View File

@ -17,8 +17,8 @@ setuptools.setup(
"waypaper = waypaper.__main__:run"
]
},
install_requires=["PyGObject", "importlib_metadata", "platformdirs"],
version='0.0.2',
install_requires=["PyGObject", "importlib_metadata", "platformdirs", "Pillow"],
version='2.0.3',
python_requires='>3.9',
classifiers=[
"Development Status :: 4 - Beta",

View File

@ -10,25 +10,26 @@ from waypaper.changer import change_wallpaper
from waypaper.common import get_random_file
from waypaper.aboutdata import AboutData
from waypaper.options import FILL_OPTIONS, BACKEND_OPTIONS
from waypaper.translations import English, German, French, Russian, Polish, Chinese
aboutData = AboutData()
about = AboutData()
cf = Config()
if cf.lang == "de":
from waypaper.translations import German as txt
txt = German()
elif cf.lang == "fr":
from waypaper.translations import French as txt
txt = French()
elif cf.lang == "ru":
from waypaper.translations import Russian as txt
txt = Russian()
elif cf.lang == "pl":
from waypaper.translations import Polish as txt
txt = Polish()
elif cf.lang == "zh":
from waypaper.translations import Chinese as txt
txt = Chinese()
else:
from waypaper.translations import English as txt
txt = English()
parser = argparse.ArgumentParser(prog = aboutData.applicationName(), description = txt.msg_desc,
parser = argparse.ArgumentParser(prog = about.applicationName(), description = txt.msg_desc,
epilog = txt.msg_info)
parser.add_argument("-v", "--version", help=txt.msg_arg_help, action="store_true")
parser.add_argument("--restore", help=txt.msg_arg_rest, action="store_true")
@ -59,7 +60,7 @@ def run():
# Print the version and quit:
if args.version:
print(f"{aboutData.applicationName()} v.{aboutData.applicationVersion()}")
print(f"{about.applicationName()} v.{about.applicationVersion()}")
exit(0)
# Start GUI:

View File

@ -7,6 +7,7 @@ import shutil
import gi
from pathlib import Path
from platformdirs import user_cache_path
from PIL import Image
from waypaper.aboutdata import AboutData
from waypaper.changer import change_wallpaper
@ -18,9 +19,22 @@ gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GdkPixbuf, Gdk, GLib
def read_webp_image(image_path):
"""Read webp images using Pillow library and convert it to pixbuf format"""
img = Image.open(image_path)
data = img.tobytes()
width, height = img.size
pixbuf = GdkPixbuf.Pixbuf.new_from_data(data, GdkPixbuf.Colorspace.RGB, False, 8, width, height, width * 3)
return pixbuf
def cache_image(image_path, cachedir):
"""Resize and cache images using gtk library"""
pixbuf = GdkPixbuf.Pixbuf.new_from_file(str(image_path))
ext = os.path.splitext(image_path)[1].lower()
if ext == ".webp":
pixbuf = read_webp_image(str(image_path))
else:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(str(image_path))
aspect_ratio = pixbuf.get_width() / pixbuf.get_height()
scaled_width = 240
scaled_height = int(scaled_width / aspect_ratio)
@ -79,7 +93,7 @@ class App(Gtk.Window):
self.include_subfolders_checkbox = Gtk.ToggleButton(label=self.txt.msg_subfolders)
self.include_subfolders_checkbox.set_active(self.cf.include_subfolders)
self.include_subfolders_checkbox.connect("toggled", self.on_include_subfolders_toggled)
self.include_subfolders_checkbox.set_tooltip_text(TIP_SUBFOLDER)
self.include_subfolders_checkbox.set_tooltip_text(self.txt.tip_subfolder)
# Create a backend dropdown menu:
self.backend_option_combo = Gtk.ComboBoxText()
@ -95,7 +109,7 @@ class App(Gtk.Window):
active_num = 0
self.backend_option_combo.set_active(active_num)
self.backend_option_combo.connect("changed", self.on_backend_option_changed)
self.backend_option_combo.set_tooltip_text(TIP_BACKEND)
self.backend_option_combo.set_tooltip_text(self.txt.tip_backend)
# Create a fill option dropdown menu:
self.fill_option_combo = Gtk.ComboBoxText()
@ -104,7 +118,7 @@ class App(Gtk.Window):
self.fill_option_combo.append_text(capitalized_option)
self.fill_option_combo.set_active(0)
self.fill_option_combo.connect("changed", self.on_fill_option_changed)
self.fill_option_combo.set_tooltip_text(TIP_FILL)
self.fill_option_combo.set_tooltip_text(self.txt.tip_fill)
# Create a color picker:
self.color_picker_button = Gtk.ColorButton()
@ -113,7 +127,7 @@ class App(Gtk.Window):
rgba_color.parse(self.cf.color)
self.color_picker_button.set_rgba(rgba_color)
self.color_picker_button.connect("color-set", self.on_color_set)
self.color_picker_button.set_tooltip_text(TIP_COLOR)
self.color_picker_button.set_tooltip_text(self.txt.tip_color)
# Create a sort option dropdown menu:
self.sort_option_combo = Gtk.ComboBoxText()
@ -122,23 +136,23 @@ class App(Gtk.Window):
active_num = SORT_OPTIONS.index(self.cf.sort_option)
self.sort_option_combo.set_active(active_num)
self.sort_option_combo.connect("changed", self.on_sort_option_changed)
self.sort_option_combo.set_tooltip_text(TIP_SORTING)
self.sort_option_combo.set_tooltip_text(self.txt.tip_sorting)
# Create exit button:
self.exit_button = Gtk.Button(label=self.txt.msg_exit)
self.exit_button.connect("clicked", self.on_exit_clicked)
self.exit_button.set_tooltip_text(TIP_EXIT)
self.exit_button.set_tooltip_text(self.txt.tip_exit)
# Create refresh button:
self.refresh_button = Gtk.Button(label=self.txt.msg_refresh)
self.refresh_button.connect("clicked", self.on_refresh_clicked)
self.refresh_button.set_tooltip_text(TIP_REFRESH)
self.refresh_button.set_tooltip_text(self.txt.tip_refresh)
# Create random button:
self.random_button = Gtk.Button(label=self.txt.msg_random)
self.random_button.connect("clicked", self.on_random_clicked)
self.random_button.set_tooltip_text(TIP_RANDOM)
self.random_button.set_tooltip_text(self.txt.tip_random)
# Create a box to contain the bottom row of buttons with margin:
self.bottom_button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=20)
@ -199,7 +213,7 @@ class App(Gtk.Window):
self.monitor_option_combo.append_text(monitor)
self.monitor_option_combo.set_active(0)
self.monitor_option_combo.connect("changed", self.on_monitor_option_changed)
self.monitor_option_combo.set_tooltip_text(TIP_DISPLAY)
self.monitor_option_combo.set_tooltip_text(self.txt.tip_display)
# Add it to the row of buttons:
self.options_box.pack_start(self.monitor_option_combo, False, False, 0)
@ -250,7 +264,7 @@ class App(Gtk.Window):
def process_images(self):
"""Load images from the selected folder, resize them, and arrange into a grid"""
self.image_paths = get_image_paths(self.cf.image_folder, self.cf.include_subfolders, depth=1)
self.image_paths = get_image_paths(self.cf.backend, self.cf.image_folder, self.cf.include_subfolders, depth=1)
self.sort_images()
# Show caching label:
@ -346,22 +360,23 @@ class App(Gtk.Window):
def on_choose_folder_clicked(self, widget):
"""Choosing the folder of images, saving the path, and reloading images"""
self.choose_folder()
self.cf.save()
def on_include_subfolders_toggled(self, toggle):
"""On chosing to include subfolders"""
cf.include_subfolders = toggle.get_active()
self.cf.include_subfolders = toggle.get_active()
threading.Thread(target=self.process_images).start()
def on_fill_option_changed(self, combo):
"""Save fill parameter when it was changed"""
cf.fill_option = combo.get_active_text()
self.cf.fill_option = combo.get_active_text()
def on_monitor_option_changed(self, combo):
"""Save monitor parameter when it was changed"""
cf.selected_monitor = combo.get_active_text()
self.cf.selected_monitor = combo.get_active_text()
def on_sort_option_changed(self, combo):

View File

@ -3,15 +3,16 @@
import os
import random
from waypaper.options import IMAGE_EXTENSIONS
def has_image_extension(file_path):
def has_image_extension(file_path, backend):
"""Check if the file has image extension"""
image_extensions = ['.gif', '.jpg', '.jpeg', '.png']
image_extensions = IMAGE_EXTENSIONS[backend]
ext = os.path.splitext(file_path)[1].lower()
return ext in image_extensions
def get_image_paths(root_folder, include_subfolders=False, depth=None):
def get_image_paths(backend, root_folder, include_subfolders=False, depth=None):
"""Get a list of file paths depending of weather we include subfolders and how deep we scan"""
image_paths = []
for root, directories, files in os.walk(root_folder):
@ -22,7 +23,7 @@ def get_image_paths(root_folder, include_subfolders=False, depth=None):
if current_depth > depth:
continue
for filename in files:
if has_image_extension(filename):
if has_image_extension(filename, backend):
image_paths.append(os.path.join(root, filename))
return image_paths

View File

@ -13,7 +13,7 @@ class Config:
"""User configuration loaded from the config.ini file"""
def __init__(self):
self.image_folder = user_pictures_path()
self.selected_wallpaper = None
self.selected_wallpaper = ""
self.selected_monitor = "All"
self.fill_option = "fill"
self.sort_option = "name"
@ -21,39 +21,19 @@ class Config:
self.color = "#ffffff"
self.lang = "en"
self.monitors = [self.selected_monitor]
self.wallpaper = str()
self.wallpaper = []
self.include_subfolders = False
self.aboutData = AboutData()
self.cachePath = user_cache_path(self.aboutData.applicationName())
self.configPath = user_config_path(self.aboutData.applicationName())
self.config_file = self.configPath / "config.ini"
# Create all directories we use here
# Create config folder:
# Create config and cache folders:
self.configPath.mkdir(parents=True, exist_ok=True)
# Create cache folder:
self.cachePath.mkdir(parents=True,exist_ok=True)
self.read()
def create(self):
"""Create a default config.ini file if it does not exist"""
config = configparser.ConfigParser()
config["Settings"] = {
"folder": str(self.image_folder),
"fill": str(self.fill_option),
"sort": str(self.sort_option),
"backend": str(self.backend),
"color": str(self.color),
"language": str(self.lang),
"subfolders": str(self.include_subfolders),
"wallpaper": str(self.selected_wallpaper),
"monitors": str(self.selected_monitor),
}
with open(self.config_file, "w") as configfile:
config.write(configfile)
def read(self):
"""Load data from the config.ini or use default if it does not exists"""
@ -75,10 +55,11 @@ class Config:
if self.monitors_str is not None:
self.monitors = [str(monitor) for monitor in self.monitors_str.split(",")]
self.wallpaper_str = config.get("Settings", "wallpaper", fallback=self.wallpaper, raw=True)
self.wallpaper_str = config.get("Settings", "wallpaper", fallback="", raw=True)
if self.wallpaper_str is not None:
self.wallpaper = [str(paper) for paper in self.wallpaper_str.split(",")]
def save(self):
"""Update the parameters and save them to the configuration file"""

View File

@ -6,3 +6,11 @@ SORT_DISPLAYS = {
"namerev": "Name ↑",
"date": "Date ↓",
"daterev": "Date ↑"}
IMAGE_EXTENSIONS = {
BACKEND_OPTIONS[0]: ['.gif', '.jpg', '.jpeg', '.png'],
BACKEND_OPTIONS[1]: ['.gif', '.jpg', '.jpeg', '.png', '.webp', '.bmp', '.pnm', '.tiff'],
BACKEND_OPTIONS[2]: ['.gif', '.jpg', '.jpeg', '.png', '.bmp', '.pnm', '.tiff'],
BACKEND_OPTIONS[3]: ['.gif', '.jpg', '.jpeg', '.png'],
}