mirror of
https://github.com/anufrievroman/waypaper.git
synced 2024-09-11 16:26:14 +03:00
Implementing support for webp and other formats
This resolves the issue #19. Also there is improved translation handling
This commit is contained in:
parent
26371745f0
commit
3cd2f7ff9c
4
setup.py
4
setup.py
@ -17,8 +17,8 @@ setuptools.setup(
|
|||||||
"waypaper = waypaper.__main__:run"
|
"waypaper = waypaper.__main__:run"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
install_requires=["PyGObject", "importlib_metadata", "platformdirs"],
|
install_requires=["PyGObject", "importlib_metadata", "platformdirs", "Pillow"],
|
||||||
version='0.0.2',
|
version='2.0.3',
|
||||||
python_requires='>3.9',
|
python_requires='>3.9',
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 4 - Beta",
|
||||||
|
@ -10,25 +10,26 @@ from waypaper.changer import change_wallpaper
|
|||||||
from waypaper.common import get_random_file
|
from waypaper.common import get_random_file
|
||||||
from waypaper.aboutdata import AboutData
|
from waypaper.aboutdata import AboutData
|
||||||
from waypaper.options import FILL_OPTIONS, BACKEND_OPTIONS
|
from waypaper.options import FILL_OPTIONS, BACKEND_OPTIONS
|
||||||
|
from waypaper.translations import English, German, French, Russian, Polish, Chinese
|
||||||
|
|
||||||
aboutData = AboutData()
|
about = AboutData()
|
||||||
cf = Config()
|
cf = Config()
|
||||||
|
|
||||||
if cf.lang == "de":
|
if cf.lang == "de":
|
||||||
from waypaper.translations import German as txt
|
txt = German()
|
||||||
elif cf.lang == "fr":
|
elif cf.lang == "fr":
|
||||||
from waypaper.translations import French as txt
|
txt = French()
|
||||||
elif cf.lang == "ru":
|
elif cf.lang == "ru":
|
||||||
from waypaper.translations import Russian as txt
|
txt = Russian()
|
||||||
elif cf.lang == "pl":
|
elif cf.lang == "pl":
|
||||||
from waypaper.translations import Polish as txt
|
txt = Polish()
|
||||||
elif cf.lang == "zh":
|
elif cf.lang == "zh":
|
||||||
from waypaper.translations import Chinese as txt
|
txt = Chinese()
|
||||||
else:
|
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)
|
epilog = txt.msg_info)
|
||||||
parser.add_argument("-v", "--version", help=txt.msg_arg_help, action="store_true")
|
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")
|
parser.add_argument("--restore", help=txt.msg_arg_rest, action="store_true")
|
||||||
@ -59,7 +60,7 @@ def run():
|
|||||||
|
|
||||||
# Print the version and quit:
|
# Print the version and quit:
|
||||||
if args.version:
|
if args.version:
|
||||||
print(f"{aboutData.applicationName()} v.{aboutData.applicationVersion()}")
|
print(f"{about.applicationName()} v.{about.applicationVersion()}")
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
# Start GUI:
|
# Start GUI:
|
||||||
|
@ -7,6 +7,7 @@ import shutil
|
|||||||
import gi
|
import gi
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from platformdirs import user_cache_path
|
from platformdirs import user_cache_path
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
from waypaper.aboutdata import AboutData
|
from waypaper.aboutdata import AboutData
|
||||||
from waypaper.changer import change_wallpaper
|
from waypaper.changer import change_wallpaper
|
||||||
@ -18,9 +19,22 @@ gi.require_version("Gtk", "3.0")
|
|||||||
from gi.repository import Gtk, GdkPixbuf, Gdk, GLib
|
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):
|
def cache_image(image_path, cachedir):
|
||||||
"""Resize and cache images using gtk library"""
|
"""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()
|
aspect_ratio = pixbuf.get_width() / pixbuf.get_height()
|
||||||
scaled_width = 240
|
scaled_width = 240
|
||||||
scaled_height = int(scaled_width / aspect_ratio)
|
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 = Gtk.ToggleButton(label=self.txt.msg_subfolders)
|
||||||
self.include_subfolders_checkbox.set_active(self.cf.include_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.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:
|
# Create a backend dropdown menu:
|
||||||
self.backend_option_combo = Gtk.ComboBoxText()
|
self.backend_option_combo = Gtk.ComboBoxText()
|
||||||
@ -95,7 +109,7 @@ class App(Gtk.Window):
|
|||||||
active_num = 0
|
active_num = 0
|
||||||
self.backend_option_combo.set_active(active_num)
|
self.backend_option_combo.set_active(active_num)
|
||||||
self.backend_option_combo.connect("changed", self.on_backend_option_changed)
|
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:
|
# Create a fill option dropdown menu:
|
||||||
self.fill_option_combo = Gtk.ComboBoxText()
|
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.append_text(capitalized_option)
|
||||||
self.fill_option_combo.set_active(0)
|
self.fill_option_combo.set_active(0)
|
||||||
self.fill_option_combo.connect("changed", self.on_fill_option_changed)
|
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:
|
# Create a color picker:
|
||||||
self.color_picker_button = Gtk.ColorButton()
|
self.color_picker_button = Gtk.ColorButton()
|
||||||
@ -113,7 +127,7 @@ class App(Gtk.Window):
|
|||||||
rgba_color.parse(self.cf.color)
|
rgba_color.parse(self.cf.color)
|
||||||
self.color_picker_button.set_rgba(rgba_color)
|
self.color_picker_button.set_rgba(rgba_color)
|
||||||
self.color_picker_button.connect("color-set", self.on_color_set)
|
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:
|
# Create a sort option dropdown menu:
|
||||||
self.sort_option_combo = Gtk.ComboBoxText()
|
self.sort_option_combo = Gtk.ComboBoxText()
|
||||||
@ -122,23 +136,23 @@ class App(Gtk.Window):
|
|||||||
active_num = SORT_OPTIONS.index(self.cf.sort_option)
|
active_num = SORT_OPTIONS.index(self.cf.sort_option)
|
||||||
self.sort_option_combo.set_active(active_num)
|
self.sort_option_combo.set_active(active_num)
|
||||||
self.sort_option_combo.connect("changed", self.on_sort_option_changed)
|
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:
|
# Create exit button:
|
||||||
self.exit_button = Gtk.Button(label=self.txt.msg_exit)
|
self.exit_button = Gtk.Button(label=self.txt.msg_exit)
|
||||||
self.exit_button.connect("clicked", self.on_exit_clicked)
|
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:
|
# Create refresh button:
|
||||||
self.refresh_button = Gtk.Button(label=self.txt.msg_refresh)
|
self.refresh_button = Gtk.Button(label=self.txt.msg_refresh)
|
||||||
self.refresh_button.connect("clicked", self.on_refresh_clicked)
|
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:
|
# Create random button:
|
||||||
self.random_button = Gtk.Button(label=self.txt.msg_random)
|
self.random_button = Gtk.Button(label=self.txt.msg_random)
|
||||||
self.random_button.connect("clicked", self.on_random_clicked)
|
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:
|
# Create a box to contain the bottom row of buttons with margin:
|
||||||
self.bottom_button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=20)
|
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.append_text(monitor)
|
||||||
self.monitor_option_combo.set_active(0)
|
self.monitor_option_combo.set_active(0)
|
||||||
self.monitor_option_combo.connect("changed", self.on_monitor_option_changed)
|
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:
|
# Add it to the row of buttons:
|
||||||
self.options_box.pack_start(self.monitor_option_combo, False, False, 0)
|
self.options_box.pack_start(self.monitor_option_combo, False, False, 0)
|
||||||
@ -250,7 +264,7 @@ class App(Gtk.Window):
|
|||||||
def process_images(self):
|
def process_images(self):
|
||||||
"""Load images from the selected folder, resize them, and arrange into a grid"""
|
"""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()
|
self.sort_images()
|
||||||
|
|
||||||
# Show caching label:
|
# Show caching label:
|
||||||
@ -346,22 +360,23 @@ class App(Gtk.Window):
|
|||||||
def on_choose_folder_clicked(self, widget):
|
def on_choose_folder_clicked(self, widget):
|
||||||
"""Choosing the folder of images, saving the path, and reloading images"""
|
"""Choosing the folder of images, saving the path, and reloading images"""
|
||||||
self.choose_folder()
|
self.choose_folder()
|
||||||
|
self.cf.save()
|
||||||
|
|
||||||
|
|
||||||
def on_include_subfolders_toggled(self, toggle):
|
def on_include_subfolders_toggled(self, toggle):
|
||||||
"""On chosing to include subfolders"""
|
"""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()
|
threading.Thread(target=self.process_images).start()
|
||||||
|
|
||||||
|
|
||||||
def on_fill_option_changed(self, combo):
|
def on_fill_option_changed(self, combo):
|
||||||
"""Save fill parameter when it was changed"""
|
"""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):
|
def on_monitor_option_changed(self, combo):
|
||||||
"""Save monitor parameter when it was changed"""
|
"""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):
|
def on_sort_option_changed(self, combo):
|
||||||
|
@ -3,15 +3,16 @@
|
|||||||
import os
|
import os
|
||||||
import random
|
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"""
|
"""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()
|
ext = os.path.splitext(file_path)[1].lower()
|
||||||
return ext in image_extensions
|
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"""
|
"""Get a list of file paths depending of weather we include subfolders and how deep we scan"""
|
||||||
image_paths = []
|
image_paths = []
|
||||||
for root, directories, files in os.walk(root_folder):
|
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:
|
if current_depth > depth:
|
||||||
continue
|
continue
|
||||||
for filename in files:
|
for filename in files:
|
||||||
if has_image_extension(filename):
|
if has_image_extension(filename, backend):
|
||||||
image_paths.append(os.path.join(root, filename))
|
image_paths.append(os.path.join(root, filename))
|
||||||
return image_paths
|
return image_paths
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ class Config:
|
|||||||
"""User configuration loaded from the config.ini file"""
|
"""User configuration loaded from the config.ini file"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.image_folder = user_pictures_path()
|
self.image_folder = user_pictures_path()
|
||||||
self.selected_wallpaper = None
|
self.selected_wallpaper = ""
|
||||||
self.selected_monitor = "All"
|
self.selected_monitor = "All"
|
||||||
self.fill_option = "fill"
|
self.fill_option = "fill"
|
||||||
self.sort_option = "name"
|
self.sort_option = "name"
|
||||||
@ -21,39 +21,19 @@ class Config:
|
|||||||
self.color = "#ffffff"
|
self.color = "#ffffff"
|
||||||
self.lang = "en"
|
self.lang = "en"
|
||||||
self.monitors = [self.selected_monitor]
|
self.monitors = [self.selected_monitor]
|
||||||
self.wallpaper = str()
|
self.wallpaper = []
|
||||||
self.include_subfolders = False
|
self.include_subfolders = False
|
||||||
self.aboutData = AboutData()
|
self.aboutData = AboutData()
|
||||||
self.cachePath = user_cache_path(self.aboutData.applicationName())
|
self.cachePath = user_cache_path(self.aboutData.applicationName())
|
||||||
self.configPath = user_config_path(self.aboutData.applicationName())
|
self.configPath = user_config_path(self.aboutData.applicationName())
|
||||||
self.config_file = self.configPath / "config.ini"
|
self.config_file = self.configPath / "config.ini"
|
||||||
|
|
||||||
# Create all directories we use here
|
# Create config and cache folders:
|
||||||
# Create config folder:
|
|
||||||
self.configPath.mkdir(parents=True, exist_ok=True)
|
self.configPath.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# Create cache folder:
|
|
||||||
self.cachePath.mkdir(parents=True,exist_ok=True)
|
self.cachePath.mkdir(parents=True,exist_ok=True)
|
||||||
|
|
||||||
self.read()
|
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):
|
def read(self):
|
||||||
"""Load data from the config.ini or use default if it does not exists"""
|
"""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:
|
if self.monitors_str is not None:
|
||||||
self.monitors = [str(monitor) for monitor in self.monitors_str.split(",")]
|
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:
|
if self.wallpaper_str is not None:
|
||||||
self.wallpaper = [str(paper) for paper in self.wallpaper_str.split(",")]
|
self.wallpaper = [str(paper) for paper in self.wallpaper_str.split(",")]
|
||||||
|
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""Update the parameters and save them to the configuration file"""
|
"""Update the parameters and save them to the configuration file"""
|
||||||
|
|
||||||
|
@ -6,3 +6,11 @@ SORT_DISPLAYS = {
|
|||||||
"namerev": "Name ↑",
|
"namerev": "Name ↑",
|
||||||
"date": "Date ↓",
|
"date": "Date ↓",
|
||||||
"daterev": "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'],
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user