V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tuoniaoguoce
V2EX  ›  Android

开源的 4K 壁纸软件有哪些?

  •  
  •   tuoniaoguoce · 31 天前 · 3461 次点击
    毕竟不能浪费了这块 4K 的屏幕。
    21 条回复    2025-03-01 18:20:00 +08:00
    qinghuazs
        1
    qinghuazs  
       31 天前
    花见?
    Magicdove
        2
    Magicdove  
       31 天前
    我都到壁纸网站下载到本地看
    Frankcox
        3
    Frankcox  
       31 天前
    unsplash, wallhaven.cc
    julio867
        4
    julio867  
       31 天前
    以前逛 wallhaven ,现在定期到 unsplash 找自己喜欢的,然后批量下载,自己还专门写了一个浏览器扩展方便获取原图地址,然后迅雷批量下~~😄
    xiangshuaaaa
        5
    xiangshuaaaa  
       31 天前
    @julio867 扩展发布了嘛,白嫖下( bushi )
    julio867
        6
    julio867  
       31 天前
    @xiangshuaaaa 注册开发者比较麻烦,目前仅自己在本地使用~

    之前放了一个 wallhaven 的在 github 上:
    https://github.com/zoujia/WallhavenAssistant (写的不好,只保证了能用😅)

    unsplash 的抽空再把它放到 github 上吧~
    since2021
        7
    since2021  
       31 天前
    偶尔去 reddit 上自己找
    mohuani
        8
    mohuani  
       31 天前
    Weyeeep
        9
    Weyeeep  
       30 天前 via Android
    只用过 unsplash
    wfhtqp
        10
    wfhtqp  
       30 天前
    bing wallpaper 每天换新
    565656
        11
    565656  
       30 天前
    win11 自带了 设置 windows 聚焦每天自动换
    timothyye
        12
    timothyye  
       30 天前 via Android
    做过一个 bing wallpaper 的开源项目
    https://github.com/TimothyYe/bing-wallpaper
    fuyun
        13
    fuyun  
       30 天前
    https://www.ifuyun.com/wallpaper 除了壁纸,还有壁纸背后的故事,支持中英文,支持全文搜索。
    前台开源( https://github.com/ifuyun/ifuyun.com ),API 闭源。
    JensenQian
        14
    JensenQian  
       30 天前
    壁纸我用的 steam 上 19 块小红车
    不过不认识的别乱下,最近在闹赛博梅毒
    senzyo
        15
    senzyo  
       30 天前
    那得推荐一下 https://github.com/rocksdanister/lively ,完全能替代 Wallpaper Engine
    nrq
        16
    nrq  
       30 天前
    楼上的好东西,感谢分享
    LitterGopher
        17
    LitterGopher  
       29 天前   ❤️ 1
    pexels 和 unsplash 有提供 API, 你自己註冊一個開發者帳號(免費), 然後寫一個腳本自動獲取就可以了.

    我自己是使用 shell + crontab 實現定期獲取隨機圖片並設置爲桌面背景.
    zhj0326
        18
    zhj0326  
       29 天前
    unsplash
    1Z3KYa0qBLvei98o
        19
    1Z3KYa0qBLvei98o  
       28 天前
    import axios from 'axios';
    import fs from 'fs';
    import path from 'path';
    import os from 'os';
    import crypto from 'crypto';
    import { setWallpaper } from 'wallpaper';
    import readline from 'readline';
    import { SocksProxyAgent } from 'socks-proxy-agent';

    const UNSPLASH_API_URL = 'https://api.unsplash.com/photos/random';
    const ACCESS_KEY = 'Z_GQ0Jd3V27mew6lRc1MB-1ojS0e9s9W7B5FPN6XoZc'; // Replace with your Unsplash access key
    const INTERVAL = 5 * 60 * 1000; // 5 minutes
    const DOWNLOAD_TIMEOUT = 60 * 1000; // 1 minute
    const THREAD_COUNT = 4; // Number of threads for parallel download
    const RETRY_LIMIT = 3; // Retry limit for failed downloads
    const PROGRESS_TIMEOUT = 10 * 1000; // 10 seconds timeout for progress check
    const MIN_SPEED = 10; // Minimum speed in KB/s to consider as stalled

    // SOCKS5 Proxy setup
    const proxyUrl = 'socks5h://127.0.0.1:1080'; // Replace with your SOCKS5 proxy server address and port
    const agent = new SocksProxyAgent(proxyUrl);

    // Keywords list
    const originalKeywords = [
    'mountain', 'africa', 'middle east', 'snow', 'travel',
    'Caribbean', 'Hubble Space Telescope', 'roman',
    'Soviets', 'Aegean Sea',
    'Bahrain', 'Eritrea', 'Iran', 'Iraq', 'Israel',
    'Jordan', 'Kuwait', 'Lebanon', 'Oman', 'Qatar',
    'Saudi Arabia', 'Syria', 'United Arab Emirates', 'Yemen',
    // Europe
    'Albania', 'Andorra', 'Austria', 'Belarus', 'Belgium',
    'Bosnia and Herzegovina', 'Bulgaria', 'Iceland', 'Ireland',
    'Italy', 'Kosovo', 'Latvia', 'Lithuania', 'Luxembourg',
    'Malta', 'Monaco', 'Moldova', 'Norway', 'Netherlands',
    'Portugal', 'Romania', 'Russia', 'San Marino', 'Serbia',
    'Cyprus', 'Slovakia', 'Slovenia', 'Spain', 'Switzerland',
    'Ukraine', 'United Kingdom', 'Vatican City',
    // Asia
    'Afghanistan', 'United Arab Emirates', 'Armenia', 'China',
    'Georgia', 'India', 'Indonesia', 'Iran', 'Iraq',
    'Israel', 'Japan', 'Jordan', 'Kazakhstan', 'South Korea',
    'Kuwait', 'Kyrgyzstan', 'Lebanon', 'Maldives', 'Mongolia',
    'Myanmar', 'Nepal', 'Macau', 'Malaysia', 'Pakistan',
    'Philippines', 'Russia', 'Saudi Arabia', 'Singapore',
    'Sri Lanka', 'Syria', 'Tajikistan', 'Thailand', 'Timor-Leste',
    'Turkey', 'Turkmenistan', 'Uzbekistan', 'Yemen',
    // Caribbean and South America
    'Antigua and Barbuda', 'Bahamas', 'Barbados', 'Bolivia',
    'Colombia', 'Cuba', 'Dominica', 'Dominican Republic',
    'Grenada', 'Guyana', 'Haiti', 'Honduras', 'Jamaica',
    'Nicaragua', 'Paraguay', 'Peru', 'Saint Kitts and Nevis',
    'Saint Lucia', 'Saint Vincent and the Grenadines', 'Suriname',
    'Trinidad and Tobago', 'Venezuela'
    ];

    // Create a copy of the original keywords to keep track of unused ones
    let unusedKeywords = [...originalKeywords];

    // Function to generate a random hash
    function generateHash(length = 8) {
    return crypto.randomBytes(length).toString('hex');
    }

    // Function to get the path for temporary file
    function getTempFilePath(partIndex) {
    const tempDir = os.tmpdir();
    return path.join(tempDir, `wallpaper_part${partIndex}.tmp`);
    }

    // Function to get a random keyword
    function getRandomKeyword() {
    if (unusedKeywords.length === 0) {
    unusedKeywords = [...originalKeywords]; // Reset the list when all keywords have been used
    console.log('All keywords used, resetting keyword list.');
    }
    const randomIndex = Math.floor(Math.random() * unusedKeywords.length);
    const keyword = unusedKeywords[randomIndex];
    unusedKeywords.splice(randomIndex, 1); // Remove the keyword from the list
    return keyword;
    }

    async function downloadPart(photoUrl, start, end, partIndex, totalLength, downloadedParts, startTime) {
    let retryCount = 0;
    let lastProgress = 0;
    let progressTimer;
    const tempFilePath = getTempFilePath(partIndex);

    // Read previous progress
    if (fs.existsSync(tempFilePath)) {
    downloadedParts[partIndex] = fs.statSync(tempFilePath).size;
    }

    while (retryCount < RETRY_LIMIT) {
    try {
    let currentStart = start + downloadedParts[partIndex];

    const response = await axios.get(photoUrl, {
    headers: {
    'Range': `bytes=${currentStart}-${end}`,
    },
    responseType: 'arraybuffer',
    httpAgent: agent, // Apply the SOCKS5 agent
    httpsAgent: agent, // Apply the SOCKS5 agent
    onDownloadProgress: (progressEvent) => {
    const newBytes = progressEvent.loaded - downloadedParts[partIndex];
    downloadedParts[partIndex] += newBytes;
    updateProgress(downloadedParts, totalLength, startTime);

    const elapsedTime = (Date.now() - startTime) / 1000; // in seconds
    const speed = (newBytes / 1024 / elapsedTime).toFixed(2); // in KB/s

    // Reset progress timer on new data
    clearTimeout(progressTimer);
    progressTimer = setTimeout(() => {
    if (downloadedParts[partIndex] <= lastProgress || speed < MIN_SPEED) {
    console.log(`\nPart ${partIndex + 1} is stalled or too slow, retrying...`);
    retryCount++;
    if (retryCount >= RETRY_LIMIT) {
    throw new Error(`Failed to download part ${partIndex + 1} after ${RETRY_LIMIT} attempts.`);
    }
    lastProgress = downloadedParts[partIndex];
    downloadPart(photoUrl, start, end, partIndex, totalLength, downloadedParts, startTime);
    }
    }, PROGRESS_TIMEOUT);
    }
    });

    // Append data to temp file
    fs.writeFileSync(tempFilePath, response.data, { flag: 'a' });
    clearTimeout(progressTimer);
    break; // Exit the loop if download was successful

    } catch (error) {
    console.error(`Part ${partIndex + 1} failed, retrying... (${retryCount}/${RETRY_LIMIT})`);
    if (retryCount >= RETRY_LIMIT) {
    throw new Error(`Failed to download part ${partIndex + 1} after ${RETRY_LIMIT} attempts.`);
    }
    }
    }
    }

    async function downloadRandomPhoto() {
    console.log('Attempting to download a new wallpaper...');

    try {
    const keyword = getRandomKeyword(); // Select a random keyword
    console.log(`Using keyword: ${keyword}`);

    const response = await axios.get(UNSPLASH_API_URL, {
    headers: { Authorization: `Client-ID ${ACCESS_KEY}` },
    params: {
    query: keyword,
    },
    httpAgent: agent, // Apply the SOCKS5 agent
    httpsAgent: agent, // Apply the SOCKS5 agent
    });

    console.log('Received response from Unsplash API.');

    const photoUrl = response.data.urls.full;
    console.log(`Photo URL: ${photoUrl}`);

    const headResponse = await axios.head(photoUrl, { httpAgent: agent, httpsAgent: agent });
    const totalLength = parseInt(headResponse.headers['content-length'], 10);
    const tempDir = os.tmpdir();
    const hash = generateHash();
    const fileName = path.join(tempDir, `wallpaper_${hash}.jpg`);
    console.log(`Total size: ${totalLength} bytes`);
    console.log(`Saving photo to: ${fileName}`);

    const partSize = Math.ceil(totalLength / THREAD_COUNT);
    const downloadedParts = new Array(THREAD_COUNT).fill(0);
    let startTime = Date.now();

    const promises = [];

    for (let i = 0; i < THREAD_COUNT; i++) {
    const start = i * partSize;
    const end = Math.min(start + partSize - 1, totalLength - 1);

    console.log(`Downloading part ${i + 1}: bytes ${start}-${end}`);

    const promise = downloadPart(photoUrl, start, end, i, totalLength, downloadedParts, startTime);
    promises.push(promise);
    }

    await Promise.all(promises);

    // Combine all parts into a single file
    const writeStream = fs.createWriteStream(fileName);
    for (let i = 0; i < THREAD_COUNT; i++) {
    const tempFilePath = getTempFilePath(i);
    const data = fs.readFileSync(tempFilePath);
    writeStream.write(data);
    fs.unlinkSync(tempFilePath); // Delete part file after merging
    }
    writeStream.end();

    console.log('\nPhoto download complete. Setting wallpaper...');

    setWallpaper(fileName).then(() => {
    console.log('Wallpaper updated successfully!');
    }).catch(err => {
    console.error('Failed to set wallpaper:', err);
    });

    } catch (error) {
    console.error('Error in downloadRandomPhoto function:', error);
    }
    }

    function updateProgress(downloadedParts, totalLength, startTime) {
    const downloadedLength = downloadedParts.reduce((acc, val) => acc + val, 0);
    const percent = ((downloadedLength / totalLength) * 100).toFixed(2);

    const elapsedTime = (Date.now() - startTime) / 1000; // in seconds
    const speed = (downloadedLength / 1024 / elapsedTime).toFixed(2); // in KB/s

    readline.clearLine(process.stdout, 0);
    readline.cursorTo(process.stdout, 0);
    process.stdout.write(`Download progress: ${percent}% | Speed: ${speed} KB/s`);
    }

    downloadRandomPhoto();
    setInterval(() => {
    console.log('Starting new wallpaper update cycle...');
    downloadRandomPhoto();
    }, INTERVAL);
    1Z3KYa0qBLvei98o
        20
    1Z3KYa0qBLvei98o  
       28 天前
    自己去搞一个 access_key
    1Z3KYa0qBLvei98o
        21
    1Z3KYa0qBLvei98o  
       28 天前
    纯 ai 生成, 微调了一下
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2693 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 09:41 · PVG 17:41 · LAX 02:41 · JFK 05:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.