Add bing_wallpaper sub-suite (downloader + daily wallpaper) + README section (v1.4.0)
This commit is contained in:
@@ -67,6 +67,22 @@ A secure password hashing utility using **Argon2id** (2025 recommended parameter
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🖼️ Bing Wallpaper
|
||||||
|
|
||||||
|
A self-contained sub-suite for Bing wallpaper tools (in the `bing_wallpaper/` folder):
|
||||||
|
- **Bing Wallpaper Downloader** — batch download Bing daily wallpapers (async, resume)
|
||||||
|
- **Daily Wallpaper** — daily desktop wallpaper rotation from a folder
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd bing_wallpaper
|
||||||
|
python bing_wallpaper_downloader.py # download Bing wallpapers
|
||||||
|
python daily_wallpaper.py # rotate desktop wallpaper daily
|
||||||
|
```
|
||||||
|
|
||||||
|
See `bing_wallpaper/README.md` for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 📁 Project Structure
|
## 📁 Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -78,6 +94,10 @@ Messy-Little-Gadgets/
|
|||||||
├── music_downloader.py # Music download GUI tool
|
├── music_downloader.py # Music download GUI tool
|
||||||
├── argon2_password_hasher.py # Argon2 password hasher
|
├── argon2_password_hasher.py # Argon2 password hasher
|
||||||
├── image_resolution_scanner.py # Image resolution scanner
|
├── image_resolution_scanner.py # Image resolution scanner
|
||||||
|
├── bing_wallpaper/ # 🖼️ Bing wallpaper tools
|
||||||
|
│ ├── bing_wallpaper_downloader.py
|
||||||
|
│ ├── daily_wallpaper.py
|
||||||
|
│ └── README.md
|
||||||
└── README.md # This document
|
└── README.md # This document
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# 🖼️ Bing Wallpaper Suite
|
||||||
|
|
||||||
|
> A small suite of **Bing wallpaper** tools: batch download Bing daily wallpapers, and rotate them as your desktop wallpaper daily.
|
||||||
|
|
||||||
|
This folder contains two standalone tools for Bing wallpapers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧩 Tools
|
||||||
|
|
||||||
|
### ⬇️ Bing Wallpaper Downloader — `bing_wallpaper_downloader.py`
|
||||||
|
Batch download Bing daily wallpapers from `peapix.com`.
|
||||||
|
- ⚡ Async concurrent download (aiohttp, 50 concurrent)
|
||||||
|
- 🔄 Resume: skips already-downloaded files
|
||||||
|
- 📈 Progress & speed reporting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install aiohttp aiofiles
|
||||||
|
python bing_wallpaper_downloader.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔁 Daily Wallpaper — `daily_wallpaper.py`
|
||||||
|
Rotate your Windows desktop wallpaper daily from a folder of images.
|
||||||
|
- 📁 Reads wallpaper directory from config (prompts on first run)
|
||||||
|
- 🔄 Cycles through images day by day (rotates at the end)
|
||||||
|
- 🖥️ Sets wallpaper via Win32 API
|
||||||
|
- 📄 Creates an `ok_<timestamp>.txt` run record
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python daily_wallpaper.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
bing_wallpaper/
|
||||||
|
├── bing_wallpaper_downloader.py # Batch download Bing wallpapers
|
||||||
|
├── daily_wallpaper.py # Daily wallpaper rotation
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
Part of **Messy-Little-Gadgets** (MIT License).
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Bing Wallpaper Downloader
|
||||||
|
============================================================
|
||||||
|
Batch download Bing daily wallpapers from peapix.com.
|
||||||
|
- Async concurrent download (aiohttp)
|
||||||
|
- Resume: skips already-downloaded files
|
||||||
|
- Progress & stats reporting
|
||||||
|
|
||||||
|
Usage: python bing_wallpaper_downloader.py
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import asyncio
|
||||||
|
import aiohttp
|
||||||
|
import aiofiles
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
# Global state
|
||||||
|
downloaded_urls = set()
|
||||||
|
downloaded_ids = set()
|
||||||
|
semaphore = None
|
||||||
|
print_lock = asyncio.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def load_existing_files(download_dir="bing_images"):
|
||||||
|
"""Load existing files to avoid re-downloading."""
|
||||||
|
global downloaded_urls, downloaded_ids
|
||||||
|
if not os.path.exists(download_dir):
|
||||||
|
return
|
||||||
|
|
||||||
|
for filename in os.listdir(download_dir):
|
||||||
|
if filename.endswith('.jpg'):
|
||||||
|
# Extract ID from filename
|
||||||
|
match = re.search(r'(\d{5})', filename)
|
||||||
|
if match:
|
||||||
|
downloaded_ids.add(int(match.group(1)))
|
||||||
|
downloaded_urls.add(filename)
|
||||||
|
|
||||||
|
print(f"Loaded {len(downloaded_ids)} existing images")
|
||||||
|
|
||||||
|
|
||||||
|
async def download_image(session, url, download_dir="bing_images"):
|
||||||
|
"""Download and save an image asynchronously."""
|
||||||
|
if url in downloaded_urls:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
|
'Referer': 'https://peapix.com/'
|
||||||
|
}
|
||||||
|
|
||||||
|
async with session.get(url, headers=headers, timeout=30) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Check content size
|
||||||
|
content_length = resp.headers.get('content-length')
|
||||||
|
if content_length and int(content_length) < 5 * 1024:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Generate filename and path
|
||||||
|
filename = os.path.basename(urlparse(url).path)
|
||||||
|
filepath = os.path.join(download_dir, filename)
|
||||||
|
|
||||||
|
# Skip if file already exists
|
||||||
|
if os.path.exists(filepath):
|
||||||
|
downloaded_urls.add(url)
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
# Stream download and save
|
||||||
|
async with aiofiles.open(filepath, 'wb') as f:
|
||||||
|
async for chunk in resp.content.iter_chunked(8192): # 8KB per chunk
|
||||||
|
await f.write(chunk)
|
||||||
|
|
||||||
|
# Validate file size (reject error pages)
|
||||||
|
if os.path.getsize(filepath) < 5 * 1024:
|
||||||
|
os.remove(filepath)
|
||||||
|
return None
|
||||||
|
|
||||||
|
downloaded_urls.add(url)
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def process_one_page(session, page_id, download_dir="bing_images"):
|
||||||
|
"""Process a single page: extract the image URL and download it."""
|
||||||
|
# Skip if already downloaded
|
||||||
|
if page_id in downloaded_ids:
|
||||||
|
return "skipped"
|
||||||
|
|
||||||
|
page_url = f"https://peapix.com/bing/{page_id}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get page HTML
|
||||||
|
async with session.get(page_url, headers=headers, timeout=15) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
return None
|
||||||
|
html = await resp.text()
|
||||||
|
|
||||||
|
# Extract image URL
|
||||||
|
pattern = r'<img[^>]+src="(https://img\.peapix\.com/[^"]+_1280\.jpg)"'
|
||||||
|
matches = re.findall(pattern, html)
|
||||||
|
|
||||||
|
if not matches:
|
||||||
|
return None
|
||||||
|
|
||||||
|
img_url = matches[0]
|
||||||
|
original_url = img_url.replace("_1280", "")
|
||||||
|
|
||||||
|
# Skip if URL already downloaded
|
||||||
|
if original_url in downloaded_urls:
|
||||||
|
return "skipped"
|
||||||
|
|
||||||
|
# Show download status
|
||||||
|
async with print_lock:
|
||||||
|
print(f"[{page_id}] 📥 Downloading...")
|
||||||
|
|
||||||
|
# Download the image
|
||||||
|
result = await download_image(session, original_url, download_dir)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
downloaded_ids.add(page_id)
|
||||||
|
async with print_lock:
|
||||||
|
filename = os.path.basename(result)
|
||||||
|
file_size = os.path.getsize(result) / 1024
|
||||||
|
print(f"[{page_id}] ✅ Saved: {filename} ({file_size:.1f}KB)")
|
||||||
|
else:
|
||||||
|
async with print_lock:
|
||||||
|
print(f"[{page_id}] ❌ Download failed")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
async with print_lock:
|
||||||
|
print(f"[{page_id}] ⏰ Timeout")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
async with print_lock:
|
||||||
|
print(f"[{page_id}] ❌ Error: {str(e)[:30]}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def worker(session, task_id, download_dir, results):
|
||||||
|
"""Worker coroutine controlled by the semaphore."""
|
||||||
|
async with semaphore:
|
||||||
|
result = await process_one_page(session, task_id, download_dir)
|
||||||
|
results[task_id] = result
|
||||||
|
|
||||||
|
|
||||||
|
async def async_batch_download(start_id=51418, end_id=56807, max_concurrent=50, download_dir="bing_images"):
|
||||||
|
"""Main async batch download function."""
|
||||||
|
os.makedirs(download_dir, exist_ok=True)
|
||||||
|
|
||||||
|
load_existing_files(download_dir)
|
||||||
|
|
||||||
|
global semaphore
|
||||||
|
semaphore = asyncio.Semaphore(max_concurrent)
|
||||||
|
|
||||||
|
total = end_id - start_id + 1
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"📊 Task: {start_id} -> {end_id} ({total} items)")
|
||||||
|
print(f"⚡ Concurrency: {max_concurrent}")
|
||||||
|
print(f"📁 Save dir: {download_dir}")
|
||||||
|
print(f"💾 Existing: {len(downloaded_ids)}")
|
||||||
|
print("=" * 60)
|
||||||
|
print()
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
results = {}
|
||||||
|
tasks = list(range(start_id, end_id + 1))
|
||||||
|
|
||||||
|
connector = aiohttp.TCPConnector(
|
||||||
|
limit=max_concurrent * 2,
|
||||||
|
limit_per_host=max_concurrent,
|
||||||
|
ttl_dns_cache=300
|
||||||
|
)
|
||||||
|
timeout = aiohttp.ClientTimeout(total=30, connect=10)
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
|
||||||
|
task_list = [
|
||||||
|
worker(session, task_id, download_dir, results)
|
||||||
|
for task_id in tasks
|
||||||
|
]
|
||||||
|
|
||||||
|
batch_size = 100
|
||||||
|
completed = 0
|
||||||
|
|
||||||
|
for i in range(0, len(task_list), batch_size):
|
||||||
|
batch = task_list[i:i + batch_size]
|
||||||
|
await asyncio.gather(*batch, return_exceptions=True)
|
||||||
|
|
||||||
|
completed += len(batch)
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
speed = completed / elapsed if elapsed > 0 else 0
|
||||||
|
|
||||||
|
async with print_lock:
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
print(f"📈 Progress: {completed}/{total} ({completed/total*100:.1f}%)")
|
||||||
|
print(f"⚡ Speed: {speed:.1f} items/sec")
|
||||||
|
print(f"⏱️ Elapsed: {elapsed:.1f}s")
|
||||||
|
print(f"{'='*60}\n")
|
||||||
|
|
||||||
|
# Stats
|
||||||
|
success_count = 0
|
||||||
|
skipped_count = 0
|
||||||
|
failed_ids = []
|
||||||
|
|
||||||
|
for task_id, result in results.items():
|
||||||
|
if result == "skipped":
|
||||||
|
skipped_count += 1
|
||||||
|
elif result:
|
||||||
|
success_count += 1
|
||||||
|
else:
|
||||||
|
failed_ids.append(task_id)
|
||||||
|
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("📊 Download summary:")
|
||||||
|
print(f" ✅ Success: {success_count}")
|
||||||
|
print(f" ⏭️ Skipped (exists): {skipped_count}")
|
||||||
|
print(f" ❌ Failed: {len(failed_ids)}")
|
||||||
|
if failed_ids:
|
||||||
|
print(f" 🔴 Failed IDs (first 20): {failed_ids[:20]}")
|
||||||
|
print(f" ⏱️ Total time: {elapsed:.1f}s")
|
||||||
|
print(f" 📈 Avg speed: {total/elapsed:.1f} items/sec")
|
||||||
|
print(f" 📁 Save dir: {os.path.abspath(download_dir)}")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
|
||||||
|
def batch_download(start_id=51418, end_id=56807, max_concurrent=50, download_dir="bing_images"):
|
||||||
|
"""Synchronous entry point."""
|
||||||
|
asyncio.run(async_batch_download(
|
||||||
|
start_id=start_id,
|
||||||
|
end_id=end_id,
|
||||||
|
max_concurrent=max_concurrent,
|
||||||
|
download_dir=download_dir
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
batch_download(
|
||||||
|
start_id=51418,
|
||||||
|
end_id=56807,
|
||||||
|
max_concurrent=50,
|
||||||
|
download_dir="bing_images"
|
||||||
|
)
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Daily Wallpaper
|
||||||
|
============================================================
|
||||||
|
Rotate the Windows desktop wallpaper daily from a folder of images.
|
||||||
|
- Reads wallpaper directory from config (or prompts on first run)
|
||||||
|
- Cycles through images day by day (rotates when reaching the end)
|
||||||
|
- Sets the wallpaper via Win32 API
|
||||||
|
- Creates an ok_<timestamp>.txt run record
|
||||||
|
|
||||||
|
Usage: python daily_wallpaper.py
|
||||||
|
"""
|
||||||
|
import ctypes
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
import sys
|
||||||
|
|
||||||
|
STATE_FILE = "wallpaper_state.json"
|
||||||
|
CONFIG_FILE = "wallpaper_config.json"
|
||||||
|
|
||||||
|
|
||||||
|
def get_script_dir():
|
||||||
|
"""Return the script/exe directory."""
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
return os.path.dirname(os.path.abspath(sys.executable))
|
||||||
|
else:
|
||||||
|
return os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
def get_today():
|
||||||
|
return datetime.now().strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
|
||||||
|
def get_timestamp():
|
||||||
|
"""Return a current timestamp string."""
|
||||||
|
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
|
||||||
|
|
||||||
|
def create_ok_file(wallpaper_dir, wallpaper_file, index, total):
|
||||||
|
"""Create an ok_<timestamp>.txt file recording the run status."""
|
||||||
|
script_dir = get_script_dir()
|
||||||
|
timestamp = get_timestamp()
|
||||||
|
ok_file_path = os.path.join(script_dir, f"ok_{timestamp}.txt")
|
||||||
|
|
||||||
|
content = f"""Wallpaper updated
|
||||||
|
Run time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||||
|
Wallpaper dir: {wallpaper_dir}
|
||||||
|
Today's wallpaper: {wallpaper_file}
|
||||||
|
Index: {index}/{total}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(ok_file_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(content)
|
||||||
|
print(f"✅ Run record saved: {ok_file_path}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Failed to save run record: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_wallpaper_dir():
|
||||||
|
"""Read the wallpaper path from config; prompt & save on first run."""
|
||||||
|
script_dir = get_script_dir()
|
||||||
|
config_path = os.path.join(script_dir, CONFIG_FILE)
|
||||||
|
|
||||||
|
# Try reading the JSON config
|
||||||
|
if os.path.exists(config_path):
|
||||||
|
try:
|
||||||
|
with open(config_path, 'r', encoding='utf-8') as f:
|
||||||
|
config = json.load(f)
|
||||||
|
path = config.get('wallpaper_dir', '')
|
||||||
|
if path and os.path.exists(path):
|
||||||
|
print(f"✅ Read path from config: {path}")
|
||||||
|
return path
|
||||||
|
else:
|
||||||
|
print(f"⚠️ Config path is invalid: {path}")
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print("⚠️ Config file corrupted, will re-configure")
|
||||||
|
else:
|
||||||
|
print(f"📄 Config file not found: {config_path}, first-time config")
|
||||||
|
|
||||||
|
# No config or invalid path: prompt the user
|
||||||
|
while True:
|
||||||
|
path = input("📁 Enter wallpaper directory path: ").strip().strip('"')
|
||||||
|
|
||||||
|
if os.path.exists(path):
|
||||||
|
config = {
|
||||||
|
'wallpaper_dir': path,
|
||||||
|
'last_updated': get_today()
|
||||||
|
}
|
||||||
|
with open(config_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(config, f, ensure_ascii=False, indent=2)
|
||||||
|
print(f"✅ Config saved: {config_path}")
|
||||||
|
return path
|
||||||
|
else:
|
||||||
|
print(f"❌ Directory does not exist: {path}, please re-enter")
|
||||||
|
|
||||||
|
|
||||||
|
def get_start_date(wallpaper_dir):
|
||||||
|
"""Read the start date from the state JSON file."""
|
||||||
|
state_path = os.path.join(wallpaper_dir, STATE_FILE)
|
||||||
|
|
||||||
|
if os.path.exists(state_path):
|
||||||
|
try:
|
||||||
|
with open(state_path, 'r', encoding='utf-8') as f:
|
||||||
|
state = json.load(f)
|
||||||
|
return state.get('start_date')
|
||||||
|
except (json.JSONDecodeError, KeyError):
|
||||||
|
print("⚠️ State file corrupted, will re-initialize")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def save_start_date(wallpaper_dir, start_date):
|
||||||
|
"""Save the start date to a JSON file."""
|
||||||
|
state_path = os.path.join(wallpaper_dir, STATE_FILE)
|
||||||
|
state = {
|
||||||
|
'start_date': start_date,
|
||||||
|
'last_updated': get_today()
|
||||||
|
}
|
||||||
|
with open(state_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(state, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_files(wallpaper_dir):
|
||||||
|
"""Return all image files in the directory, sorted by name."""
|
||||||
|
files = []
|
||||||
|
for f in os.listdir(wallpaper_dir):
|
||||||
|
if f.lower().endswith(('.jpg', '.jpeg', '.png', '.webp', '.bmp')):
|
||||||
|
files.append(f)
|
||||||
|
files.sort()
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
def get_next_wallpaper(wallpaper_dir):
|
||||||
|
"""Calculate which wallpaper to use today."""
|
||||||
|
files = get_image_files(wallpaper_dir)
|
||||||
|
|
||||||
|
if not files:
|
||||||
|
print("❌ No images found in wallpaper directory")
|
||||||
|
return None, 0, 0
|
||||||
|
|
||||||
|
total = len(files)
|
||||||
|
today = get_today()
|
||||||
|
today_dt = datetime.strptime(today, "%Y-%m-%d")
|
||||||
|
|
||||||
|
# Read the start date
|
||||||
|
start_date = get_start_date(wallpaper_dir)
|
||||||
|
|
||||||
|
# First run: record today as the start date
|
||||||
|
if start_date is None:
|
||||||
|
start_date = today
|
||||||
|
current_index = 1
|
||||||
|
print(f"📝 First run, start date: {start_date}")
|
||||||
|
else:
|
||||||
|
# Calculate days since the start date
|
||||||
|
start_dt = datetime.strptime(start_date, "%Y-%m-%d")
|
||||||
|
days_diff = (today_dt - start_dt).days
|
||||||
|
current_index = days_diff + 1
|
||||||
|
print(f"📅 Start: {start_date}, Today: {today}, Day {current_index}")
|
||||||
|
|
||||||
|
# Rotate if past the total
|
||||||
|
if current_index > total:
|
||||||
|
current_index = ((current_index - 1) % total) + 1
|
||||||
|
print(f"🔄 Looped, current index: {current_index}")
|
||||||
|
|
||||||
|
# Save the start date
|
||||||
|
save_start_date(wallpaper_dir, start_date)
|
||||||
|
|
||||||
|
wallpaper_file = files[current_index - 1]
|
||||||
|
wallpaper_path = os.path.join(wallpaper_dir, wallpaper_file)
|
||||||
|
|
||||||
|
print(f"📸 Today's wallpaper: {wallpaper_file} ({current_index}/{total})")
|
||||||
|
return wallpaper_path, current_index, total
|
||||||
|
|
||||||
|
|
||||||
|
def set_wallpaper(image_path):
|
||||||
|
"""Set the desktop wallpaper."""
|
||||||
|
abs_path = os.path.abspath(image_path)
|
||||||
|
|
||||||
|
SPI_SETDESKWALLPAPER = 20
|
||||||
|
SPIF_UPDATEINIFILE = 0x01
|
||||||
|
SPIF_SENDWININICHANGE = 0x02
|
||||||
|
SPIF_FLAGS = SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE
|
||||||
|
|
||||||
|
try:
|
||||||
|
ctypes.windll.user32.SystemParametersInfoW(
|
||||||
|
SPI_SETDESKWALLPAPER,
|
||||||
|
0,
|
||||||
|
abs_path,
|
||||||
|
SPIF_FLAGS
|
||||||
|
)
|
||||||
|
print(f"✅ Wallpaper set: {os.path.basename(image_path)}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Failed to set wallpaper: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=" * 50)
|
||||||
|
print("🖼️ Daily Wallpaper Rotation")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Get wallpaper directory
|
||||||
|
wallpaper_dir = get_wallpaper_dir()
|
||||||
|
print(f"📁 Wallpaper dir: {wallpaper_dir}")
|
||||||
|
|
||||||
|
# Get image count
|
||||||
|
files = get_image_files(wallpaper_dir)
|
||||||
|
print(f"📊 Total wallpapers: {len(files)}")
|
||||||
|
|
||||||
|
# Calculate and set today's wallpaper
|
||||||
|
wallpaper_path, current_index, total = get_next_wallpaper(wallpaper_dir)
|
||||||
|
|
||||||
|
if wallpaper_path and os.path.exists(wallpaper_path):
|
||||||
|
if set_wallpaper(wallpaper_path):
|
||||||
|
# After setting, create the ok_<timestamp>.txt file
|
||||||
|
wallpaper_file = os.path.basename(wallpaper_path)
|
||||||
|
create_ok_file(wallpaper_dir, wallpaper_file, current_index, total)
|
||||||
|
else:
|
||||||
|
print("❌ Wallpaper file does not exist")
|
||||||
|
|
||||||
|
print("=" * 50)
|
||||||
|
print("👋 Done!")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user