tuoniaoguoce
V2EX  ›  Android

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

  •  
  •   tuoniaoguoce · Feb 27, 2025 · 8511 views
    This topic created in 614 days ago, the information mentioned may be changed or developed.
    毕竟不能浪费了这块 4K 的屏幕。
    21 replies  •  2025-03-01 18:20:00 +08:00
    dongmian2019
    dongmian2019
    Feb 27, 2025
    1
    花见?
    Magicdove
    Magicdove
    Feb 27, 2025
    2
    我都到壁纸网站下载到本地看
    Frankcox
    Frankcox
    Feb 27, 2025
    3
    unsplash, wallhaven.cc
    julio867
    julio867
    Feb 27, 2025
    4
    以前逛 wallhaven ,现在定期到 unsplash 找自己喜欢的,然后批量下载,自己还专门写了一个浏览器扩展方便获取原图地址,然后迅雷批量下~~😄
    xiangshuaaaa
    xiangshuaaaa
    Feb 27, 2025
    5
    @julio867 扩展发布了嘛,白嫖下( bushi )
    julio867
    julio867
    Feb 27, 2025
    6
    @xiangshuaaaa 注册开发者比较麻烦,目前仅自己在本地使用~

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

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

    我自己是使用 shell + crontab 實現定期獲取隨機圖片並設置爲桌面背景.
    zhj0326
    zhj0326
    Mar 1, 2025
    18
    unsplash
    1Z3KYa0qBLvei98o
    1Z3KYa0qBLvei98o
    Mar 1, 2025
    19
    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
    1Z3KYa0qBLvei98o
    Mar 1, 2025
    20
    自己去搞一个 access_key
    1Z3KYa0qBLvei98o
    1Z3KYa0qBLvei98o
    Mar 1, 2025
    21
    纯 ai 生成, 微调了一下
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   3856 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 66ms · UTC 04:11 · PVG 12:11 · LAX 20:11 · JFK 23:11
    ♥ Do have faith in what you're doing.