def validate(self, query): logger.info(lambda: "Validating Reddit query " + query) if not "/" in query: query = "https://www.reddit.com/r/%s" % query try: if not query.startswith("http://") and not query.startswith( "https://"): query = "http://" + query if not "//reddit.com" in query and not "//www.reddit.com" in query: return False, _("This does not seem to be a valid Reddit URL") dl = RedditDownloader(self, query) queue = dl.fill_queue() return ( query, None if len(queue) > 0 else _("We could not find any image submissions there."), ) except Exception: logger.exception( lambda: "Error while validating URL, probably no image posts for this URL" ) return query, _("We could not find any image submissions there.")
def browse(self, widget=None): try: self.chooser = Gtk.FileChooserDialog( _("Choose a folder"), parent=self.button.get_toplevel(), action=Gtk.FileChooserAction.SELECT_FOLDER, buttons=[ _("Cancel"), Gtk.ResponseType.CANCEL, _("OK"), Gtk.ResponseType.OK ], ) self.chooser.set_filename(self.folder) self.chooser.set_select_multiple(False) self.chooser.set_local_only(False) if self.chooser.run() == Gtk.ResponseType.OK: self.set_folder(self.chooser.get_filename()) try: if self.on_change: self.on_change() except Exception: logger.exception( lambda: "Exception during FolderChooser on_change:") finally: if self.chooser: try: self.chooser.destroy() self.chooser = None except Exception: logger.exception( lambda: "Exception during FolderChooser destroying:")
def validate(self, query): logger.info(lambda: "Validating ArtStation query " + query) if query.endswith("/"): query = query[:-1] if "artstation.com/artwork/" in query: logger.exception(lambda: "Error while validating URL, artwork url") return query, _("We cannot download individual artworks.") if "/" not in query: query = "https://www.artstation.com/%s" % query if not query.endswith(".rss"): query += ".rss" try: # normalize the URL to "https://artstation.com/artist" query = query.replace("http://", "https://") if not query.startswith("https://"): query = "https://" + query query = query.replace("//artstation.com", "//www.artstation.com") if "//www.artstation.com" not in query: return False, _( "This does not seem to be a valid ArtStation URL") dl = ArtStationDownloader(self, query) queue = dl.fill_queue() return ( query, None if len(queue) > 0 else _("We could not find any image submissions there."), ) except Exception: logger.exception( lambda: "Error while validating URL, probably no image posts for this URL" ) return query, _("We could not find any image submissions there.")
def _go(): self.thumbs_window = ThumbsWindow(screen=self.screen, position=options.position, breadth=options.breadth) try: icon = varietyconfig.get_data_file("media", "variety.svg") self.thumbs_window.set_icon_from_file(icon) except Exception: logger.exception(lambda: "Could not set thumbs window icon") if self.type == "history": title = _("Variety History") elif self.type == "downloads": title = _("Variety Recent Downloads") else: title = _("Variety Images") self.thumbs_window.set_title(title) self.thumbs_window.connect("clicked", self.on_click) def _on_close(window, event): self.hide(force=True) self.thumbs_window.connect("delete-event", _on_close) self.mark_active(self.active_file, self.active_position) self.thumbs_window.start(self.images)
def validate(self, config): try: url = self.UnsplashConfigurableDownloader(self, config).get_unsplash_api_url() data = Util.fetch_json(url) valid = "errors" not in data return config, None if valid else _("No images found") except UnsupportedConfig: return config, _("We do not support this type of URL") except Exception as e: if isinstance(e, HTTPError) and e.response.status_code == 404: return config, _("No images found") else: return config, _("Oops, this didn't work. Is the remote service up?")
def display_modes(self) -> List[DisplayMode]: return [ StaticDisplayMode( id="os", title=_( "[Legacy] Controlled via OS settings, not by Variety. Fast." ), description= _("Display mode is controlled by your OS Appearance settings and by the logic " "for your desktop environment in ~/.config/variety/scripts/set_wallpaper. " "Provides compatibility with past Variety versions."), set_wallpaper_param="os", ) ]
def validate(self, query): if not "/" in query: query = "https://www.reddit.com/r/%s" % query else: if not query.startswith("http://") and not query.startswith( "https://"): query = "https://" + query if not "//reddit.com" in query and not "//www.reddit.com" in query: return query, False, _( "This does not seem to be a valid Reddit URL") valid = RedditDownloader.validate(query, self.parent) return (query, None if valid else _("We could not find any image submissions there."))
def display_modes(self) -> List[DisplayMode]: modes = [ "centered", "scaled", "stretched", "zoom", "spanned", "wallpaper" ] return [ StaticDisplayMode( id="gnome-%s" % mode, title=_("[GNOME/Mate/Cinnamon] %s") % mode.capitalize(), description= _("Variety will instruct the desktop environment to use this mode when " "calling set_wallpaper, and will not itself scale the image, unless needed " "by other options, e.g. clock. "), set_wallpaper_param=mode, ) for mode in modes ]
def get_info(cls): return { "name": "Goodreads", "description": _("Fetches quotes from Goodreads.com"), "author": "Peter Levi", "version": "0.1", }
def get_info(cls): return { "name": "TheQuotationsPage.com", "description": _("Fetches quotes from TheQuotationsPage.com"), "author": "Peter Levi", "version": "0.1", }
class DesktopprDownloader(SimpleDownloader): DESCRIPTION = _("Random wallpapers from Desktoppr.co") @classmethod def get_info(cls): return { "name": "DesktopprDownloader", "description": DesktopprDownloader.DESCRIPTION, "author": "Peter Levi", "version": "0.1", } def get_source_type(self): return "desktoppr" def get_description(self): return DesktopprDownloader.DESCRIPTION def get_source_name(self): return "Desktoppr.co" def get_folder_name(self): return "Desktoppr" def fill_queue(self): response = Util.fetch_json("https://api.desktoppr.co/1/wallpapers/random") if response["response"]["review_state"] != "safe": logger.info(lambda: "Non-safe image returned by Desktoppr, skipping") return None origin_url = response["response"]["url"] image_url = response["response"]["image"]["url"] return [QueueItem(origin_url, image_url, {})]
def get_info(cls): return { "name": "UnsplashConfigurableSource", "description": _("Configurable source for fetching photos from Unsplash.com"), "author": "Peter Levi", "version": "0.1", }
def get_info(cls): return { "name": "RedditSource", "description": _("Configurable source for fetching images from Reddit"), "author": "Peter Levi", "version": "0.1", }
def create_rating_menu(file, main_window): def _set_rating_maker(rating): def _set_rating(widget, rating=rating): try: Util.set_rating(file, rating) main_window.on_rating_changed(file) except Exception: logger.exception(lambda: "Could not set EXIF rating") main_window.show_notification( _("Could not set EXIF rating")) return _set_rating try: actual_rating = Util.get_rating(file) except Exception: actual_rating = None rating_menu = Gtk.Menu() for rating in range(5, 0, -1): item = Gtk.CheckMenuItem("\u2605" * rating) item.set_draw_as_radio(True) item.set_active(actual_rating == rating) item.set_sensitive(not item.get_active()) item.connect("activate", _set_rating_maker(rating)) rating_menu.append(item) rating_menu.append(Gtk.SeparatorMenuItem.new()) unrated_item = Gtk.CheckMenuItem(_("Unrated")) unrated_item.set_draw_as_radio(True) unrated_item.set_active(actual_rating is None or actual_rating == 0) unrated_item.set_sensitive(not unrated_item.get_active()) unrated_item.connect("activate", _set_rating_maker(None)) rating_menu.append(unrated_item) rejected_item = Gtk.CheckMenuItem(_("Rejected")) rejected_item.set_draw_as_radio(True) rejected_item.set_active(actual_rating is not None and actual_rating < 0) rejected_item.set_sensitive(not rejected_item.get_active()) rejected_item.connect("activate", _set_rating_maker(-1)) rating_menu.append(rejected_item) rating_menu.show_all() return rating_menu
def _set_rating(widget, rating=rating): try: Util.set_rating(file, rating) main_window.on_rating_changed(file) except Exception: logger.exception(lambda: "Could not set EXIF rating") main_window.show_notification( _("Could not set EXIF rating"))
def validate(self, url): if not url.startswith("http://") and not url.startswith("https://"): url = "https://" + url valid = MediaRSSDownloader.validate(url) error = _( "This does not seem to be a valid Media RSS feed URL or there is no content there." ) return url, None if valid else error
def get_ui_instruction(self): return _( "Enter the name of an artist or paste the full URL of their " "artstation page with .rss extenstion or without it. \n" "Example: You may specify simply 'leimin' or " "<a href='https://www.artstation.com/leimin'>https://www.artstation.com/leimin</a>\n" "Example: Or direct it to the RSS: " "<a href='https://www.artstation.com/leimin.rss'>https://www.artstation.com/leimin.rss</a>" )
def get_info(cls): return { "name": "ArtStationSource", "description": _("Configurable source for fetching images from ArtStation"), "author": "Denis Gordeev", "version": "0.1", }
def get_info(cls): return { "name": "MediaRSSSource", "description": _("Configurable source for fetching images from MediaRSS feeds"), "author": "Peter Levi", "version": "0.1", }
def get_ui_instruction(self): return _( "Enter the name of a subreddit or paste the full URL of a subreddit or a " "<a href='http://reddit.com'>Reddit</a> user. You may specify sort order and time " "period if you wish. Variety will use posts to direct images or to Imgur pages " "within the first 100 submissions returned by Reddit.\n\n" "Example: You may specify simply 'comics' or " "<a href='http://www.reddit.com/r/comics'>http://www.reddit.com/r/comics</a>\n" "Example: Top posts from the month: " "<a href='http://www.reddit.com/r/comics/top/?sort=top&t=month'>http://www.reddit.com/r/comics/top/?sort=top&t=month</a>" )
def get_ui_instruction(self): return _( "We use the <a href='https://unsplash.com'>Unsplash</a> API to fetch random images " "that match the given search term or URL. The Unsplash API is rate-limited, so " "Variety fetches images from Unsplash sources as a reduced rate.\n" "\n" "Please specify either a search keyword, or the URL of a search result, user or " "collection.\n" "Example: <a href='https://unsplash.com/collections/3863203/desktop-wallpapers'>https://unsplash.com/collections/3863203/desktop-wallpapers</a>\n" "Example: <a href='https://unsplash.com/@pawel_czerwinski'>https://unsplash.com/@pawel_czerwinski</a>" )
class EarthDownloader(SimpleDownloader): DESCRIPTION = _("World Sunlight Map - live wallpaper from Die.net") @classmethod def get_info(cls): return { "name": "EarthDownloader", "description": EarthDownloader.DESCRIPTION, "author": "Peter Levi", "version": "0.1", } def get_source_type(self): return "earth" def get_description(self): return EarthDownloader.DESCRIPTION def get_source_name(self): return "Die.net" def get_folder_name(self): return "Earth" def get_source_location(self): return EARTH_ORIGIN_URL def get_refresh_interval_seconds(self): return 15 * 60 def download_one(self): logger.info( lambda: "Downloading world sunlight map from " + EARTH_ORIGIN_URL) downloaded = self.save_locally( EARTH_ORIGIN_URL, EARTH_IMAGE_URL, force_download=True, extra_metadata={"headline": "World Sunlight Map"}, ) final_path = os.path.join(self.target_folder, EARTH_FILENAME) shutil.move(downloaded, final_path) for f in os.listdir(self.target_folder): if f != EARTH_FILENAME and f.lower().endswith(".jpg"): os.unlink(os.path.join(self.target_folder, f)) return final_path def fill_queue(self): """ Not needed here """ return [] def on_variety_start_complete(self): if not os.path.exists(os.path.join(self.target_folder, EARTH_FILENAME)): self.download_one()
def get_ui_instruction(self): return _( "Please paste the URL of the Media RSS feed below. Please note that only Media RSS " "feeds are supported, not arbitrary RSS feeds. Media RSS feeds contain media:content " "tags linking directly to the actual image content. " "Some examples of sites that provide Media RSS feeds are: " "<a href='https://picasaweb.google.com/'>Picasa</a>, " "<a href='http://www.deviantart.com'>deviantART</a>, " "<a href='http://www.smugmug.com/browse/'>SmugMug</a>, " "<a href='http://www.flickr.com'>Flickr</a>, " "<a href='http://interfacelift.com'>InterfaceLIFT</a>.")
def get_ui_instruction(self): return _( "<a href='http://wallhaven.cc'>Wallhaven.cc</a> provides a variety of image search options. " "Below you can specify keywords to search for, or visit <a href='http://wallhaven.cc'>Wallhaven.cc</a>, " "setup your search criteria there, ensure you like the results, and paste the full Wallhaven URL " "in the box.\n" "\n" "If you specify keywords, the most liked safe-for-work images that match all of the keywords will " "be used. \n" "\n" "If you specify a Wallhaven URL, please choose the sorting criteria carefully - Variety regularly " "requests images, but uses only images from the first several hundred returned. Random or Date will " "mean this image source will have a longer 'lifetime' till it is exhausted. Favorites will provide " "better images and Relevance will provide closer matches when searching for phrases or colors." )
def get_info(cls): return { "name": "Local text files", "description": _("Displays quotes, defined in local text files.\n" "Put your own txt files in: {}pluginconfig/quotes/.\n" "The file format is:\n\nquote -- author\n.\nsecond quote -- another author\n.\netc...\n\n" "Example: http://rvelthuis.de/zips/quotes.txt").format( get_profile_path(expanded=False)), "author": "Peter Levi", "version": "0.1", }
class EarthviewDownloader(SimpleDownloader): DESCRIPTION = _("Google Earth View Wallpapers") ROOT_URL = "https://earthview.withgoogle.com/" @classmethod def get_info(cls): return { "name": "EarthviewDownloader", "description": EarthviewDownloader.DESCRIPTION, "author": "Peter Levi", "version": "0.1", } def get_description(self): return EarthviewDownloader.DESCRIPTION def get_source_type(self): return "earthview" def get_source_name(self): return "Earth View" def get_source_location(self): return self.ROOT_URL def fill_queue(self): queue = Util.fetch_json(DATA_URL) random.shuffle(queue) return queue def get_default_throttling(self): # throttle this source, as otherwise maps "overpower" all other types of images # with Variety's default settings, and we have no other way to control source "weights" return Throttling(max_downloads_per_hour=20, max_queue_fills_per_hour=None) def download_queue_item(self, item): region = item["Region"] filename = "{}{} (ID-{}).jpg".format( region + ", " if region and region != "-" else "", item["Country"], item["ID"]) origin_url = EarthviewDownloader.ROOT_URL + str(item["ID"]) image_url = item["Image URL"] if not image_url.startswith("http"): image_url = "https://" + image_url return self.save_locally(origin_url, image_url, local_filename=filename)
class NationalGeographicDownloader(SimpleDownloader): DESCRIPTION = _("National Geographic's photo of the day") ROOT_URL = "https://www.nationalgeographic.co.uk/photo-of-day" @classmethod def get_info(cls): return { "name": "NationalGeographicDownloader", "description": NationalGeographicDownloader.DESCRIPTION, "author": "Eric Rösch", "version": "0.1", } def get_description(self): return NationalGeographicDownloader.DESCRIPTION def get_source_type(self): return "natgeo" def get_source_name(self): return "National Geographic" def get_source_location(self): return self.ROOT_URL def fill_queue(self): queue = Util.fetch_json(DATA_URL) images = queue["result"]["pageContext"]["node"]["data"]["content"][ "images"] return images def download_queue_item(self, item): url = item["entity"]["mediaImage"]["url"] origin_url = NationalGeographicDownloader.ROOT_URL + "?image=" + splitext( basename(url))[0] image_url = "https://static.nationalgeographic.co.uk" + url author = item["entity"]["credit"] extra_metadata = { "author": author, "authorURL": "http://google.com/search?sourceid=variety&q=" + author, "headline": item["entity"]["mediaImage"]["alt"], "description": item["entity"]["caption"][3:-6], } return self.save_locally(origin_url, image_url, extra_metadata=extra_metadata)
def display_modes(self) -> List[DisplayMode]: return [ DisplayMode( id="smart", title= _("Smart: Variety picks best mode based on image size. Recommended." ), description= ("Variety uses the fast OS-provided Zoom mode for images that are close to " "screen proportions, uses 'Fit & pad with a blurred background' when the image " "proportions are significantly different - e.g. portraits on a horizontal " "screen, and uses the OS-provided tiling mode for very small images that would " "look bad resized."), fn=_smart_fn, ), StaticDisplayMode( id="zoom", title=_("Zoom to fill screen"), description= _("Image is zoomed in or out so that it fully fills your primary screen. " "Some parts of the image will be cut out if its resolution is different " "from the screen's. Slower than using native OS resizing options." ), set_wallpaper_param="zoom", imagemagick_cmd=IMAGEMAGICK_ZOOM, ), StaticDisplayMode( id="fill-with-black", title=_("Fit within screen, pad with black"), description=_( "Image is zoomed in or out so that it fully fits within your primary screen. " "The rest of the screen is filled with black. " "Slower than using native OS resizing options."), set_wallpaper_param="zoom", imagemagick_cmd=IMAGEMAGICK_FIT_WITH_BLACK, ), StaticDisplayMode( id="fill-with-blur", title=_( "Fit within screen, pad with a blurred background. Slower." ), description= _("Image is zoomed in or out so that it fully fits within your primary screen. " "The rest of the screen is a filled with blurred version of the image. " ), set_wallpaper_param="zoom", imagemagick_cmd=IMAGEMAGICK_FIT_WITH_BLUR, ), ]
def _check_quit(): global terminate if not terminate: GObject.timeout_add(1000, _check_quit) return logging.getLogger("variety").info("Terminating signal received, quitting...") safe_print( _("Terminating signal received, quitting..."), "Terminating signal received, quitting...", file=sys.stderr, ) global VARIETY_WINDOW if VARIETY_WINDOW: VARIETY_WINDOW.on_quit() Util.start_force_exit_thread(10)
class ChromeOSWallpapersDownloader(SimpleDownloader): DESCRIPTION = _("Chrome OS Wallpapers") @classmethod def get_info(cls): return { "name": "ChromeOsWallpapersDownloader", "description": ChromeOSWallpapersDownloader.DESCRIPTION, "author": "Peter Levi", "version": "0.1", } def get_description(self): return ChromeOSWallpapersDownloader.DESCRIPTION def get_source_type(self): return "chromeos" def get_source_name(self): return "Chrome OS Wallpapers" def get_source_location(self): return self.get_source_name() def fill_queue(self): manifest = Util.fetch_json(MANIFEST_URL) queue = manifest["wallpaper_list"] self.tags = manifest["tags"] random.shuffle(queue) return queue def download_queue_item(self, item): image_url = item["base_url"] + "_high_resolution.jpg" origin_url = item["dynamic_url"] extra_metadata = {} if "tags" in item: extra_metadata["keywords"] = [ self.tags[str(tag)] for tag in item["tags"] if str(tag) in self.tags ] if "author" in item: extra_metadata["author"] = item["author"] if "author_website" in item: extra_metadata["authorURL"] = item["author_website"] return self.save_locally(origin_url, image_url, extra_metadata=extra_metadata)