Tool QSP RPGM Unity HTML Java Ren'Py Flash Wolf RPG UltraFast adult games compressor v3.1.3.12

5.00 star(s) 3 Votes

For what compressor u want mass/bulk game compress feature?

  • RPGM MV/MZ

    Votes: 8 22.2%
  • HTML

    Votes: 2 5.6%
  • I want be able to bulk compress all engines (this is not planned since too hard to add)

    Votes: 1 2.8%
  • I want to compress all xp3 files in game folder (no u don't coz some games are encrypted)

    Votes: 3 8.3%
  • I don't want this feature at all so don't waste ur time on it, just enjoy ur summer time :)

    Votes: 3 8.3%
  • I have potato PC

    Votes: 13 36.1%
  • I have average PC

    Votes: 8 22.2%
  • I have fast PC

    Votes: 8 22.2%
  • I have super PC

    Votes: 2 5.6%

  • Total voters
    36
  • Poll closed .

MrCrisis

Newbie
Jul 21, 2018
23
3
Hi, thanks for your work. Here a little feature request:

Feature request descriptionUAGC version, settings screenshotOS, CPU, RAM, HDD/SDD (opt)Link to the game (req for bugrep)Link to specific file (optional)

Improve the "Renpy Compression Assistant" for video.


I'm doing a lot of AV1 (av1_nvenc) encoding tests using variable bitrate.
Would it be possible to add an option to keep the original file if the encoded version ends up being larger?


Ideally, it should be possible to specify a minimum size difference in percent (e.g., 10%) required to keep the encoded version.
In my example, if the encoded file isn't at least 10% smaller, the original file should be kept instead.

UAGC 3.1.3 v7Windows 11
Ryzen 3950x
64GB

Windows 11
Dual E5-2667 V4
512 GB
1752682673735.png

On a separate note, I’d recommend updating to the latest version of NConvert (apologies if it has already been done).
The new version is much more user-friendly when it comes to handling AVIF parameters.


1752683109713.png
 

megalol

Engaged Member
Modder
Compressor
Respected User
Apr 3, 2017
2,626
11,425
Hi, thanks for your work. Here a little feature request:

Feature request descriptionUAGC version, settings screenshotOS, CPU, RAM, HDD/SDD (opt)Link to the game (req for bugrep)Link to specific file (optional)

Improve the "Renpy Compression Assistant" for video.


I'm doing a lot of AV1 (av1_nvenc) encoding tests using variable bitrate.
Would it be possible to add an option to keep the original file if the encoded version ends up being larger?


Ideally, it should be possible to specify a minimum size difference in percent (e.g., 10%) required to keep the encoded version.
In my example, if the encoded file isn't at least 10% smaller, the original file should be kept instead.
UAGC 3.1.3 v7Windows 11
Ryzen 3950x
64GB

Windows 11
Dual E5-2667 V4
512 GB
View attachment 5048211

On a separate note, I’d recommend updating to the latest version of NConvert (apologies if it has already been done).
The new version is much more user-friendly when it comes to handling AVIF parameters.


View attachment 5048229
I dunno why u mentioned RenPy cmpr assistant since it already implemented long time inside main RenPy compressor, only problem that it doesn't have av1_nvenc yet (only SVT-AV1) like dedicated RenPy avif/AV1 compressor but I have plans to add it in future (I meant av1_nvenc in RenPy main compressor). 1752684026145.png
About nconvert for avif count it as depricated that left just for fun and u should use imagemagick instead (just untick nconvert at ur image) for avif since it's "much more user-friendly when it comes to handling AVIF parameters".
 
Last edited:
  • Heart
Reactions: MrCrisis

MrCrisis

Newbie
Jul 21, 2018
23
3
Thanks for the quick response!


Great news! I'm really looking forward to seeing this feature added for the AVIF/AV1 section.
If the "minimum ratio" option could also be applied to videos, that would be ideal.
(If I understood the description correctly, it's currently only used for images.)

Alright, regarding ImageMagick, i have to admit I preferred using NConvert,
mainly because setting the quality with a simple -q [0-100] felt much easier than using
-avif_quant_color X X X and -avif_quant_alpha X X X.

But no worries, i’ll push myself and make the switch to ImageMagick :LOL:.


EDIT:


Oh sorry, I just noticed the edit in your message.
My bad, I wasn’t aware that AV1/AVIF encoding was already available on the main ren'Py page.
From what I understand, everything will be moved to the main Ren'Py compression page, and the dedicated page will no longer be needed?

If I got it right, the main Ren'Py page already allows encoding to AVIF and AV1, it’s just missing AV1_NVENC support?
Also, can the main page update the Ren'Py engine the same way the dedicated one does? (maybe this 1752687031771.png )
 
Last edited:

megalol

Engaged Member
Modder
Compressor
Respected User
Apr 3, 2017
2,626
11,425
If I got it right, the main Ren'Py page already allows encoding to AVIF and AV1, it’s just missing AV1_NVENC support?
Also, can the main page update the Ren'Py engine the same way the dedicated one does? (maybe this View attachment 5048462 )
Yes. Yes (u need also select in UAGC settings PC for SDK building type) 1752698507918.png to re(build) Win/Lin version instead of MacOS one and enable this too 1752698751579.png . But u need it only if current RenPy game engine version doesn't supports avif/AV1. Also in UAGC settings exists feature to auto compress to avif/AV1 if RenPy engine version supports it.
 
Last edited:

megalol

Engaged Member
Modder
Compressor
Respected User
Apr 3, 2017
2,626
11,425
I've told AI for fun to write script for grease/tampermonkey that adds direct compressed link to OP, it's not ideal for games that has multiple chapters/seasons for example or if compressor posted compressed version before OP was updated but better than nothing, if OP has compressed word in OP it wouldn't do anything (to avoid useless f95zone server load). Problem is that ppl that could install this script usually have no problems with finding latest compressed version.
JavaScript:
// ==UserScript==
// @name         F95 – add “Compressed version” link
// @namespace    https://example.com/
// @version      1.7.0
// @description  Finds the newest, most relevant compressed build in the same thread and links it above the download section.
// @author       You
// @match        https://f95zone.to/*threads/*
// @grant        GM_xmlhttpRequest
// @connect      f95zone.to
// ==/UserScript==

/* -------------------------------------------------- */
/* ------------------  helpers  --------------------- */
/* -------------------------------------------------- */

/**
 * ROBUST PARSER (V6)
 * - Dynamically handles Chapter/Episode.
 * - Correctly parses version strings like "v.36072" and "v0.4".
 */
function parseVersionKey(text) {
    if (!text) return null;

    const cleanedText = text.replace(/[\[\]\(\)]/g, ' ');

    // Match Season + Chapter/Episode
    const seasonRegex = /s(?:eason)?\s*(\d+).*?(ch|ep|chapter|episode)\.?\s*(\d+)/i;
    let match = cleanedText.match(seasonRegex);
    if (match && match[1] && match[2] && match[3]) {
        const season = parseInt(match[1], 10);
        const typeStr = match[2].toLowerCase();
        const number = parseInt(match[3], 10);
        const displayType = typeStr.startsWith('ch') ? 'Ch.' : 'Ep.';
        return { key: season + number / 100, valueStr: `S${season} ${displayType} ${number}` };
    }

    // Match standalone Chapter/Episode
    const standaloneRegex = /\b(ch|ep|chapter|episode)\.?\s*(\d+)/i;
    match = cleanedText.match(standaloneRegex);
    if (match && match[1] && match[2]) {
        const typeStr = match[1].toLowerCase();
        const number = parseInt(match[2], 10);
        const displayType = typeStr.startsWith('ch') ? 'Ch.' : 'Ep.';
        return { key: 1 + number / 100, valueStr: `${displayType} ${number}` };
    }

    // Flexible regex for standard versions like "v.123", "v 123", "v-123", and "v123"
    const versionRegex = /v(?:ersion)?[\s._-]?(\d[\d.]*)/i;
    match = cleanedText.match(versionRegex);
    if (match && match[1]) {
        return { key: parseFloat(match[1]), valueStr: `v${match[1]}` };
    }

    return null;
}


function createCompressedVersionLink(label, href) {
    const link = document.createElement('a');
    link.href = href;
    link.textContent = label;
    link.target = '_blank';
    link.rel = "noopener noreferrer";
    link.style.cssText = `
        display: inline-block; margin: 10px 0 12px 0; padding: 8px 14px;
        background-color: #ba4545; color: white; font-weight: bold;
        font-size: 14px; text-decoration: none; border-radius: 4px; cursor: pointer;
    `;
    link.title = 'Go to compressed version post';
    return link;
}

function insertLinkAboveDownloadDiv(firstPost, element) {
    const candidates = firstPost.querySelectorAll('div[style*="text-align: center"]');
    for (const div of candidates) {
        if (div.textContent.trim().toUpperCase().includes('DOWNLOAD')) {
            div.parentNode.insertBefore(element, div);
            return true;
        }
    }
    const firstSpoiler = firstPost.querySelector('.bbCodeSpoiler');
    if (firstSpoiler) {
        firstSpoiler.parentNode.insertBefore(element, firstSpoiler);
    } else {
        firstPost.insertBefore(element, firstPost.firstChild);
    }
    return false;
}

function isGameReleasePost(post) {
    if (!post) return false;
    const centerDivs = post.querySelectorAll('div[style*="text-align: center"]');
    for (const div of centerDivs) {
        if (div.textContent.trim().toUpperCase().includes('DOWNLOAD')) return true;
    }
    const spoilerButtons = post.querySelectorAll('.bbCodeSpoiler-button');
    for (const button of spoilerButtons) {
        const buttonText = button.textContent.trim().toUpperCase();
        if (buttonText.includes('DOWNLOAD') || buttonText.includes('LINKS')) return true;
    }
    return false;
}

function looksLikeQuote(snippet) {
    if (!snippet) return false;
    const s = snippet.trim().toLowerCase();
    return s.includes('said:') || s.startsWith('quote') || s.startsWith('spoiler');
}

function looksLikeRequest(text) {
    if (!text) return false;
    const s = text.trim().toLowerCase();
    const requestKeywords = [
        'request', 'looking for', 'anyone has', 'does anyone have',
        'can someone', 'anyone have', 'does anyone know', 'need', 'searching for'
    ];
    return requestKeywords.some(keyword => s.includes(keyword));
}

/* -------------------------------------------------- */
/* --------  CORE: search-result processing  -------- */
/* -------------------------------------------------- */

// **MODIFIED:** Now returns the entire best candidate object {url, date, versionInfo}
function findBestMatchingCompressedPost(html) {
    const doc = new DOMParser().parseFromString(html, 'text/html');
    const items = [...doc.querySelectorAll('li.block-row, li.js-inlineModContainer')];

    if (!items.length) {
        console.warn('findBestMatchingCompressedPost(): no <li> items found in search results.');
        return null;
    }
    console.log("DEBUG: Found search result items =", items.length);

    const compressedRegex = /(compressed|compress|archive|\.zip|\.rar|\.7z)/i;
    const releaseIndicatorRegex = /(mega|pixeldrain|workupload|buzzheavier|datanodes|akirabox|win|linux|mac|gb|mb)/i;

    const candidates = items.map((li, idx) => {
        const contentRow = li.querySelector('.contentRow-main, .contentRow');
        if (!contentRow) return null;

        let postLinkElem = li.querySelector('.contentRow-minor a[href*="/post-"]');
        if (!postLinkElem) {
            postLinkElem = contentRow.querySelector('h3.contentRow-title a[href]');
        }
        if (!postLinkElem) return null;
        const postUrl = postLinkElem.href;

        const snippetElem = contentRow.querySelector('.contentRow-snippet');
        const snippetText = snippetElem ? snippetElem.innerText.trim() : '';

        if (!compressedRegex.test(snippetText) || !releaseIndicatorRegex.test(snippetText) || looksLikeQuote(snippetText) || looksLikeRequest(snippetText)) {
            return null;
        }

        const candidateVersionInfo = parseVersionKey(snippetText);
        if (!candidateVersionInfo) return null;

        // **REMOVED**: The strict version matching filter has been removed.
        // We now find the newest compressed post, regardless of its version,
        // to handle cases where the compressed version is slightly older.

        const timeElem = li.querySelector('time.u-dt');
        if (!timeElem || !timeElem.getAttribute('datetime')) return null;
        const dateObj = new Date(timeElem.getAttribute('datetime'));
        if (isNaN(dateObj.getTime())) return null;

        console.log(`%cDEBUG [${idx}] Candidate FOUND: URL=${postUrl}, Version=${candidateVersionInfo.valueStr}`, 'color: lightgreen; font-weight: bold;');
        // **MODIFIED**: Return the full object with version info
        return { url: postUrl, date: dateObj, versionInfo: candidateVersionInfo };
    }).filter(Boolean);

    if (!candidates.length) {
        console.log("DEBUG: No valid candidates found after filtering.");
        return null;
    }

    // Sort by newest date first to get the most recent release.
    candidates.sort((a, b) => b.date - a.date);
    // **MODIFIED**: Return the entire best candidate object
    return candidates[0];
}


/* -------------------------------------------------- */
/* -----------  helper: POST form search  ----------- */
/* -------------------------------------------------- */

function postSearchUsingPageForm(searchQuery) {
    return new Promise((resolve, reject) => {
        const forms = document.querySelectorAll('form[action="/search/search"]');
        let searchForm = null;
        for (const form of forms) {
            if (form.querySelector('select[name="constraints"]')) {
                searchForm = form;
                break;
            }
        }
        if (!searchForm) {
            return reject(new Error("Could not find a search form with a 'constraints' dropdown. The page structure may have changed."));
        }
        const formData = new FormData(searchForm);
        formData.set('keywords', searchQuery);
        const searchUrl = searchForm.action;
        GM_xmlhttpRequest({
            method: "POST", url: searchUrl, data: formData,
            headers: { "User-Agent": navigator.userAgent, "Referer": window.location.href },
            onload: r => {
                if (r.status >= 200 && r.status < 300) resolve(r.responseText);
                else reject(new Error(`HTTP ${r.status}: ${r.statusText}`));
            },
            onerror: err => reject(new Error('Network error during GM_xmlhttpRequest')),
        });
    });
}

/* -------------------------------------------------- */
/* -------------------  main  ----------------------- */
/* -------------------------------------------------- */

window.addEventListener('load', () => {
    (async function main() {
        try {
            const firstPost = document.querySelector('.message-threadStarterPost.message--post.js-post');

            if (!firstPost || !isGameReleasePost(firstPost) || /compressed/i.test(firstPost.textContent)) {
                return;
            }

            const searchQuery = 'compressed';
            const html = await postSearchUsingPageForm(searchQuery);

            // **MODIFIED**: Receive the full object, not just a URL
            const bestCandidate = findBestMatchingCompressedPost(html);

            // **MODIFIED**: Check for the object itself
            if (!bestCandidate) {
                const warn = document.createElement('span');
                warn.textContent = 'No matching compressed version found.';
                warn.style.cssText = 'color:#ba4545; display:block; margin:10px 0 12px;';
                insertLinkAboveDownloadDiv(firstPost, warn);
                return;
            }

            // **MODIFIED**: Use the version from the *found post* for the label
            const versionStr = bestCandidate.versionInfo ? bestCandidate.versionInfo.valueStr : 'Link';
            const linkLabel = `Compressed Version (${versionStr})`;

            // **MODIFIED**: Use the URL and label from the bestCandidate object
            const link = createCompressedVersionLink(linkLabel, bestCandidate.url);
            insertLinkAboveDownloadDiv(firstPost, link);

        } catch (e) {
            console.error("F95 Compressed Link Script Error:", e);
        }
    })();
});
1752706894678.png
P.S. Bug report for this script should contain game thread link and script version and make sure that you read about know bugs first.
 
Last edited:

master861

Well-Known Member
Nov 4, 2022
1,226
1,574
Are you going to update rpakit and rpatool?Because renpy 8.3.x and higher are using rpa 4 and both tools only are wokring with rpa 3 and lower.
 

megalol

Engaged Member
Modder
Compressor
Respected User
Apr 3, 2017
2,626
11,425
Are you going to update rpakit and rpatool?Because renpy 8.3.x and higher are using rpa 4 and both tools only are wokring with rpa 3 and lower.
No, unless u make proper bug report with game examples since nobody reported any problems before with it and I've never heard about rpa v4 so maybe it's just some custom made rpa type by some developer (I've tested with latest SDK v8.4.0 and it still creates v3 rpa that was compressed and packed back to rpa by UAGC just fine with default settings, game works fine after compression).
 

master861

Well-Known Member
Nov 4, 2022
1,226
1,574
Yes i checked it again its are custom rpa archive and the game is to small for compression and only for one game making are custom tool.If more game using custom rpa's then maybe it is worth but for now i dont thing so.I take are look if i can change that for me.Its based on are rpa 3 .

class RPA40(RPA3):
"""A slightly custom variant of RPA-3.0."""

name = "RPA-4.0"
header = b"RPA-4.0"
 
Last edited:

megalol

Engaged Member
Modder
Compressor
Respected User
Apr 3, 2017
2,626
11,425
(install at UAGC v3.1.3 v13):
For RenPy (main, not bulk) compressor added support for AV1 NVENC video encoder (make sure first that your GPU and RenPy engine version supports AV1). Check compressed video size feature now checks if compressed videos size at least 10% smaller than original.
 

EVIL555

Member
Oct 27, 2022
171
115
(install at UAGC v3.1.3 v13):
For RenPy (main, not bulk) compressor added support for AV1 NVENC video encoder (make sure first that your GPU and RenPy engine version supports AV1). Check compressed video size feature now checks if compressed videos size at least 10% smaller than original.
and how to update?
 
5.00 star(s) 3 Votes