From 8296fe342d40447358a45be0089e7297b5fc68aa Mon Sep 17 00:00:00 2001 From: Zarg <62082797+Zaarrg@users.noreply.github.com> Date: Tue, 7 Jan 2025 05:04:50 +0100 Subject: [PATCH] animejanai support - Adjusted mpv config path to protable_config to support animejanai - Adjusted readme to the new path - Fixed installer not removing webengine cached data - Added build scripts for animejanai and anime4k - Now Anime4k is included by default in stremio - For Animejanai it must be provided as release after making the custom build using the build script --- README.md | 26 +- build/build_anime4k.js | 197 ++++++++++ build/build_animejanai.js | 353 ++++++++++++++++++ build/deploy_windows.js | 2 +- src/player/mpv.cpp | 11 +- utils/mpv/mpv.conf | 46 --- utils/mpv/shaders/KrigBilateral.glsl | 211 ----------- utils/mpv/shaders/SSimSuperRes.glsl | 203 ---------- utils/windows/installer/windows-installer.nsi | 1 + 9 files changed, 584 insertions(+), 466 deletions(-) create mode 100644 build/build_anime4k.js create mode 100644 build/build_animejanai.js delete mode 100644 utils/mpv/mpv.conf delete mode 100644 utils/mpv/shaders/KrigBilateral.glsl delete mode 100644 utils/mpv/shaders/SSimSuperRes.glsl diff --git a/README.md b/README.md index c99148c..0197807 100644 --- a/README.md +++ b/README.md @@ -61,10 +61,10 @@ Enhance your Stremio experience by customizing the MPV player settings. Below ar - 📁 **`mpv.conf` Location** - The ``mpv.conf`` file can be found in the following location: - - **Installation Path:** ``%localAppData%\Programs\LNV\Stremio-5\mpv.conf`` - - **Shaders Folder:** Located within the installation directory ``..\Stremio-5\shaders``. + - **Installation Path:** ``%localAppData%\Programs\LNV\Stremio-5\portable_config\mpv.conf`` + - **Shaders Folder:** Located within the installation directory ``..\Stremio-5\portable_config\shaders``. -> **⏳ Note:** Any other configuration files can be just dropped into ``%localAppData%\Programs\LNV\Stremio-5\`` as this is the mpv ``config-dir`` like ``input.conf`` +> **⏳ Note:** Any other configuration files can be just dropped into ``%localAppData%\Programs\LNV\Stremio-5\portable_config`` as this is the mpv ``config-dir`` like ``input.conf`` - **🎹 Usage example in `input.conf` using Anime4k:** ```shell @@ -80,6 +80,26 @@ Enhance your Stremio experience by customizing the MPV player settings. Below ar ``` > **⏳ Note:** Some keys might not work as key presses are converted from js event.codes to literal values for mpv +## 🔍 **Mpv Upscalers** + +- 🎥 **Anime4k** + - ✅ Included by default. + - 🔢 Use `CTRL+1` - `CTRL+6` to enable shaders. + - ❌ Use `CTRL+0` to disable. + +- 🎨 **AnimeJaNai** + - ❌ Not included by default. + - 📥 Download from the **Stremio-Desktop-v5** [release tab](https://github.com/Zaarrg/stremio-desktop-v5) the adjusted version for Stremio. + - 🛠️ **Changes made:** + - Removed `mpvnet.exe` as Stremio is used as the player. + - Adjusted `mpv.conf` to work with Stremio. + - Adjusted `input.conf` to work with Stremio. + +- 🚀 **Nvidia RTX and Intel VSR Scaling** + - 🔜 Coming soon! + + + ## 📚 **Guide / Docs** If you want to build this app yourself, check the “docs” folder in this repository for setup instructions and additional information. diff --git a/build/build_anime4k.js b/build/build_anime4k.js new file mode 100644 index 0000000..57994d5 --- /dev/null +++ b/build/build_anime4k.js @@ -0,0 +1,197 @@ +#!/usr/bin/env node + +/** + * build_anime4k.js + * + * This script performs the following: (Needed for deploy_windows to include Anime4k in installer) + * 1. Determines the latest Anime4K version from bloc97/Anime4K releases. + * 2. Downloads the corresponding GLSL_Windows_High-end.zip from Tama47/Anime4K. + * 3. Auto-detects 7z.exe on the system. + * 4. Saves the downloaded zip as anime4k-High-end.zip in utils/mpv. + * 5. Extracts the zip into the anime4k folder. + * 6. Cleans up temporary files. + * + * Usage: + * node build_anime4k.js + */ + +const fs = require('fs'); +const path = require('path'); +const https = require('https'); +const { execSync } = require('child_process'); +const os = require('os'); + +// Configuration +const BLOC97_API_URL = 'https://api.github.com/repos/bloc97/Anime4K/releases/latest'; +const TEMP_DIR = path.join(os.tmpdir(), 'anime4k_build_temp'); +const OUTPUT_DIR = path.resolve(__dirname, '..', 'utils', 'mpv'); +const OUTPUT_ZIP_NAME = 'anime4k-High-end.zip'; +const EXTRACTION_DIR = path.join(OUTPUT_DIR, 'anime4k'); + +// Common 7z.exe installation paths on Windows +const COMMON_7Z_PATHS = [ + path.join(process.env.PROGRAMFILES || 'C:\\Program Files', '7-Zip', '7z.exe'), + path.join(process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)', '7-Zip', '7z.exe'), + path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), '7-Zip', '7z.exe') +]; + +// Maximum number of redirects to follow +const MAX_REDIRECTS = 5; + +// Helper Functions + +function httpsGet(url, headers = {}, redirectCount = 0) { + return new Promise((resolve, reject) => { + if (redirectCount > MAX_REDIRECTS) return reject(new Error('Too many redirects')); + + const options = { + headers: { + 'User-Agent': 'Node.js Script', + ...headers + } + }; + https.get(url, options, (res) => { + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + return resolve(httpsGet(res.headers.location, headers, redirectCount + 1)); + } + if (res.statusCode !== 200) { + res.resume(); + return reject(new Error(`Request Failed. Status Code: ${res.statusCode}`)); + } + let data = ''; + res.setEncoding('utf8'); + res.on('data', chunk => data += chunk); + res.on('end', () => resolve(data)); + }).on('error', e => reject(e)); + }); +} + +function downloadFile(url, dest, headers = {}, redirectCount = 0) { + return new Promise((resolve, reject) => { + if (redirectCount > MAX_REDIRECTS) return reject(new Error('Too many redirects')); + + const options = { + headers: { + 'User-Agent': 'Node.js Script', + ...headers + } + }; + + https.get(url, options, (res) => { + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + console.log(`Redirecting to ${res.headers.location}`); + return resolve(downloadFile(res.headers.location, dest, headers, redirectCount + 1)); + } + if (res.statusCode !== 200) { + res.resume(); + return reject(new Error(`Failed to get '${url}' (${res.statusCode})`)); + } + + const totalSize = parseInt(res.headers['content-length'], 10); + let downloadedSize = 0; + const file = fs.createWriteStream(dest); + res.pipe(file); + + res.on('data', chunk => { + downloadedSize += chunk.length; + if (totalSize) { + const percent = ((downloadedSize / totalSize) * 100).toFixed(2); + process.stdout.write(`Downloading... ${percent}%\r`); + } else { + process.stdout.write(`Downloading... ${downloadedSize} bytes\r`); + } + }); + + file.on('finish', () => { + file.close(() => { + process.stdout.write('\n'); + resolve(); + }); + }); + + file.on('error', err => { + fs.unlink(dest, () => reject(err)); + }); + }).on('error', err => reject(err)); + }); +} + +function execCommand(command, cwd = process.cwd()) { + try { + execSync(command, { stdio: 'inherit', cwd }); + } catch (error) { + throw new Error(`Command failed: ${command}\n${error.message}`); + } +} + +function commandExists(command) { + try { + execSync(`where ${command}`, { stdio: 'ignore' }); + return true; + } catch { + return false; + } +} + +function find7zExecutable() { + if (commandExists('7z')) { + console.log('Found 7z.exe in PATH.'); + return '7z'; + } + for (const potentialPath of COMMON_7Z_PATHS) { + if (fs.existsSync(potentialPath)) { + console.log(`Found 7z.exe at: ${potentialPath}`); + return `"${potentialPath}"`; + } + } + throw new Error('7z.exe not found. Please install 7-Zip.'); +} + +// Main Build Function +(async function buildAnime4K() { + try { + console.log('=== Build Anime4K Script Started ==='); + + const sevenZipPath = find7zExecutable(); + + if (fs.existsSync(TEMP_DIR)) fs.rmSync(TEMP_DIR, { recursive: true, force: true }); + fs.mkdirSync(TEMP_DIR, { recursive: true }); + console.log(`Created temporary directory at ${TEMP_DIR}`); + + console.log('Fetching latest Anime4K version information...'); + const releaseData = await httpsGet(BLOC97_API_URL); + const releaseJson = JSON.parse(releaseData); + const version = releaseJson.tag_name; + console.log(`Latest version: ${version}`); + + const downloadUrl = `https://github.com/Tama47/Anime4K/releases/download/${version}/GLSL_Windows_High-end.zip`; + const downloadedFilePath = path.join(TEMP_DIR, 'GLSL_Windows_High-end.zip'); + + console.log(`Downloading GLSL_Windows_High-end.zip for version ${version}...`); + await downloadFile(downloadUrl, downloadedFilePath); + console.log(`Downloaded to ${downloadedFilePath}`); + + // Ensure output directory exists + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); + + const outputZipPath = path.join(OUTPUT_DIR, OUTPUT_ZIP_NAME); + fs.copyFileSync(downloadedFilePath, outputZipPath); + console.log(`Saved zip as ${outputZipPath}`); + + // Extract the zip to the anime4k folder + fs.mkdirSync(EXTRACTION_DIR, { recursive: true }); + console.log(`Extracting ${outputZipPath} to ${EXTRACTION_DIR}...`); + execCommand(`${sevenZipPath} x "${outputZipPath}" -o"${EXTRACTION_DIR}" -y`); + console.log('Extraction complete.'); + + // Cleanup + console.log(`Cleaning up temporary files at ${TEMP_DIR}...`); + fs.rmSync(TEMP_DIR, { recursive: true, force: true }); + console.log('Cleanup complete.'); + + console.log('=== Build Anime4K Script Completed Successfully ==='); + } catch (error) { + console.error('Error during build:', error.message); + process.exit(1); + } +})(); diff --git a/build/build_animejanai.js b/build/build_animejanai.js new file mode 100644 index 0000000..a290594 --- /dev/null +++ b/build/build_animejanai.js @@ -0,0 +1,353 @@ +#!/usr/bin/env node + +/** + * build_animejanai.js + * + * This script performs the following: + * 1. Downloads the latest 'full-package' .7z release from Animejanai GitHub. + * 2. Extracts the archive. + * 3. Deletes specified files and folders. + * 4. Modifies configuration files as per stremio requirements. + * 5. Repackages the modified files into a new .7z archive with maximum compression. + * 6. Places the final archive in utils/mpv. + * 7. Cleans up temporary files. + * + * Usage: + * node build_animejanai.js + */ + +const fs = require('fs'); +const path = require('path'); +const https = require('https'); +const { execSync } = require('child_process'); +const os = require('os'); + +// Configuration +const GITHUB_API_URL = 'https://api.github.com/repos/the-database/mpv-upscale-2x_animejanai/releases/latest'; +const TEMP_DIR = path.join(os.tmpdir(), 'animejanai_build_temp'); +const OUTPUT_DIR = path.resolve(__dirname, '..', 'utils', 'mpv'); +const OUTPUT_FILENAME_TEMPLATE = 'stremio-animejanai-{version}.7z'; + +// Files and directories to delete +const FILES_TO_DELETE = [ + 'libmpv-2.dll', + 'libmpvnet.pdb', + 'MediaInfo.dll', + 'mpvnet.com', + 'mpvnet.dll.config', + 'mpvnet.exe', + 'mpvnet.pdb', + 'NGettext.Wpf.pdb' +]; + +const FOLDERS_TO_DELETE = [ + 'Locale' +]; + +// Configuration for input.conf replacement +const NEW_INPUT_CONF_CONTENT = ` +Ctrl+E show-text "Launching AnimeJaNaiConfEditor..."; run "~~\\..\\animejanai\\AnimeJaNaiConfEditor.exe" #menu: AnimeJaNai > Launch AnimeJaNaiConfEditor +Ctrl+J script-binding "show_animejanai_stats" #menu: AnimeJaNai > Toggle AnimeJaNai Stats + +) show-text "2x_AnimeJaNai_V3 Off"; apply-profile upscale-off; +Ctrl+0 show-text "2x_AnimeJaNai_V3 Off"; apply-profile upscale-off; +SHIFT+1 show-text "2x_AnimeJaNai_V3 Quality"; apply-profile upscale-on-quality; +SHIFT+2 show-text "2x_AnimeJaNai_V3 Balanced"; apply-profile upscale-on-balanced; +SHIFT+3 show-text "2x_AnimeJaNai_V3 Performance"; apply-profile upscale-on-performance; +Ctrl+1 show-text "2x_AnimeJaNai_V3 Custom Profile 1"; apply-profile upscale-on-1; +Ctrl+2 show-text "2x_AnimeJaNai_V3 Custom Profile 2"; apply-profile upscale-on-2; +Ctrl+3 show-text "2x_AnimeJaNai_V3 Custom Profile 3"; apply-profile upscale-on-3; +Ctrl+4 show-text "2x_AnimeJaNai_V3 Custom Profile 4"; apply-profile upscale-on-4; +Ctrl+5 show-text "2x_AnimeJaNai_V3 Custom Profile 5"; apply-profile upscale-on-5; +Ctrl+6 show-text "2x_AnimeJaNai_V3 Custom Profile 6"; apply-profile upscale-on-6; +Ctrl+7 show-text "2x_AnimeJaNai_V3 Custom Profile 7"; apply-profile upscale-on-7; +Ctrl+8 show-text "2x_AnimeJaNai_V3 Custom Profile 8"; apply-profile upscale-on-8; +Ctrl+9 show-text "2x_AnimeJaNai_V3 Custom Profile 9"; apply-profile upscale-on-9; +`; + +// Lines to delete from mpv.conf +const LINES_TO_DELETE_IN_MPV_CONF = [ + 'save-position-on-quit=yes', + 'watch-later-options=start', + 'reset-on-next-file=pause' +]; + +// Common 7z.exe installation paths on Windows +const COMMON_7Z_PATHS = [ + path.join(process.env.PROGRAMFILES || 'C:\\Program Files', '7-Zip', '7z.exe'), + path.join(process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)', '7-Zip', '7z.exe'), + path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), '7-Zip', '7z.exe') +]; + +// Maximum number of redirects to follow +const MAX_REDIRECTS = 5; + +// Function to make HTTPS GET requests with GitHub API headers +function httpsGet(url, headers = {}, redirectCount = 0) { + return new Promise((resolve, reject) => { + if (redirectCount > MAX_REDIRECTS) { + return reject(new Error('Too many redirects')); + } + + const options = { + headers: { + 'User-Agent': 'Node.js Script', + ...headers + } + }; + https.get(url, options, (res) => { + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + // Handle redirects + resolve(httpsGet(res.headers.location, headers, redirectCount + 1)); + return; + } + if (res.statusCode !== 200) { + reject(new Error(`Request Failed. Status Code: ${res.statusCode}`)); + res.resume(); // Consume response data to free up memory + return; + } + let data = ''; + res.setEncoding('utf8'); + res.on('data', (chunk) => data += chunk); + res.on('end', () => resolve(data)); + }).on('error', (e) => reject(e)); + }); +} + +// Function to download a file from a URL, handling redirects and showing progress +function downloadFile(url, dest, headers = {}, redirectCount = 0) { + return new Promise((resolve, reject) => { + if (redirectCount > MAX_REDIRECTS) { + return reject(new Error('Too many redirects')); + } + + const options = { + headers: { + 'User-Agent': 'Node.js Script', + ...headers + } + }; + + https.get(url, options, (res) => { + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + // Handle redirects + console.log(`Redirecting to ${res.headers.location}`); + downloadFile(res.headers.location, dest, headers, redirectCount + 1).then(resolve).catch(reject); + return; + } + + if (res.statusCode !== 200) { + reject(new Error(`Failed to get '${url}' (${res.statusCode})`)); + res.resume(); + return; + } + + const totalSize = parseInt(res.headers['content-length'], 10); + let downloadedSize = 0; + + const file = fs.createWriteStream(dest); + res.pipe(file); + + res.on('data', (chunk) => { + downloadedSize += chunk.length; + if (totalSize) { + const percent = ((downloadedSize / totalSize) * 100).toFixed(2); + process.stdout.write(`Downloading... ${percent}%\r`); + } else { + process.stdout.write(`Downloading... ${downloadedSize} bytes\r`); + } + }); + + file.on('finish', () => { + file.close(() => { + process.stdout.write('\n'); + resolve(); + }); + }); + + file.on('error', (err) => { + fs.unlink(dest, () => reject(err)); + }); + }).on('error', (err) => { + reject(err); + }); + }); +} + +// Function to execute a shell command synchronously +function execCommand(command, cwd = process.cwd()) { + try { + execSync(command, { stdio: 'inherit', cwd }); + } catch (error) { + throw new Error(`Command failed: ${command}\n${error.message}`); + } +} + +// Function to check if a command exists +function commandExists(command) { + try { + execSync(`where ${command}`, { stdio: 'ignore' }); + return true; + } catch { + return false; + } +} + +// Function to find 7z.exe in common installation paths +function find7zExecutable() { + // First, check if 7z is in PATH + if (commandExists('7z')) { + console.log('Found 7z.exe in PATH.'); + return '7z'; + } + + // Search in common installation directories + for (const potentialPath of COMMON_7Z_PATHS) { + if (fs.existsSync(potentialPath)) { + console.log(`Found 7z.exe at: ${potentialPath}`); + return `"${potentialPath}"`; // Quote the path in case it contains spaces + } + } + + // If not found, throw an error + throw new Error('7z.exe not found. Please install 7-Zip and ensure 7z.exe is in your PATH or installed in a common directory.'); +} + +// Main Build Function +(async function buildAnimeJanai() { + try { + console.log('=== Build AnimeJaNai Script Started ==='); + + // Locate 7z.exe + const sevenZipPath = find7zExecutable(); + + // Create temporary directory + if (fs.existsSync(TEMP_DIR)) { + fs.rmSync(TEMP_DIR, { recursive: true, force: true }); + } + fs.mkdirSync(TEMP_DIR, { recursive: true }); + console.log(`Created temporary directory at ${TEMP_DIR}`); + + // Step 1: Fetch latest release info from GitHub + console.log('Fetching latest release information from GitHub...'); + const releaseData = await httpsGet(GITHUB_API_URL); + const releaseJson = JSON.parse(releaseData); + const version = releaseJson.tag_name || 'latest'; + console.log(`Latest version: ${version}`); + + // Step 2: Find the 'full-package' .7z asset + const assets = releaseJson.assets; + const fullPackageAsset = assets.find(asset => asset.name.includes('full-package') && asset.name.endsWith('.7z')); + + if (!fullPackageAsset) { + throw new Error("No 'full-package' .7z asset found in the latest release."); + } + + const downloadUrl = fullPackageAsset.browser_download_url; + const assetName = fullPackageAsset.name; + const downloadedFilePath = path.join(TEMP_DIR, assetName); + + console.log(`Downloading asset: ${assetName}`); + await downloadFile(downloadUrl, downloadedFilePath); + console.log(`Downloaded to ${downloadedFilePath}`); + + // Step 3: Extract the .7z archive + const extractDir = path.join(TEMP_DIR, 'extracted'); + fs.mkdirSync(extractDir, { recursive: true }); + console.log(`Extracting ${downloadedFilePath} to ${extractDir}...`); + execCommand(`${sevenZipPath} x "${downloadedFilePath}" -o"${extractDir}" -y`, TEMP_DIR); + console.log('Extraction complete.'); + + // Step 4: Identify the root directory inside the extracted folder + const extractedItems = fs.readdirSync(extractDir); + let rootDir = extractDir; + + if (extractedItems.length === 1 && fs.lstatSync(path.join(extractDir, extractedItems[0])).isDirectory()) { + rootDir = path.join(extractDir, extractedItems[0]); + console.log(`Detected root directory: ${rootDir}`); + } else { + console.log('No single root directory detected. Proceeding with extracted contents.'); + } + + // Step 5: Delete specified files + console.log('Deleting specified files...'); + FILES_TO_DELETE.forEach(file => { + const filePath = path.join(rootDir, file); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + console.log(`Deleted file: ${filePath}`); + } else { + console.log(`File not found (skipped): ${filePath}`); + } + }); + + // Step 6: Delete specified folders + console.log('Deleting specified folders...'); + FOLDERS_TO_DELETE.forEach(folder => { + const folderPath = path.join(rootDir, folder); + if (fs.existsSync(folderPath)) { + fs.rmSync(folderPath, { recursive: true, force: true }); + console.log(`Deleted folder: ${folderPath}`); + } else { + console.log(`Folder not found (skipped): ${folderPath}`); + } + }); + + // Step 7: Modify portable_config/input.conf + const portableConfigDir = path.join(rootDir, 'portable_config'); + const inputConfPath = path.join(portableConfigDir, 'input.conf'); + + if (fs.existsSync(inputConfPath)) { + fs.writeFileSync(inputConfPath, NEW_INPUT_CONF_CONTENT, 'utf8'); + console.log(`Modified input.conf at ${inputConfPath}`); + } else { + console.warn(`input.conf not found at ${inputConfPath}. Skipping modification.`); + } + + // Step 8: Modify mpv.conf + const mpvConfPath = path.join(portableConfigDir, 'mpv.conf'); // Corrected path + + if (fs.existsSync(mpvConfPath)) { + let mpvConfContent = fs.readFileSync(mpvConfPath, 'utf8'); + + // Replace vo={something} with vo=libmpv + mpvConfContent = mpvConfContent.replace(/^vo=.*/m, 'vo=libmpv'); + + // Remove specified lines + LINES_TO_DELETE_IN_MPV_CONF.forEach(line => { + const regex = new RegExp(`^${line}$`, 'm'); + mpvConfContent = mpvConfContent.replace(regex, ''); + }); + + // Write the modified content back + fs.writeFileSync(mpvConfPath, mpvConfContent, 'utf8'); + console.log(`Modified mpv.conf at ${mpvConfPath}`); + } else { + console.warn(`mpv.conf not found at ${mpvConfPath}. Skipping modification.`); + } + + // Step 9: Repack the modified files into a new .7z archive + const outputVersion = version.startsWith('v') ? version.slice(1) : version; + const outputFilename = OUTPUT_FILENAME_TEMPLATE.replace('{version}', outputVersion); + const outputFilePath = path.join(OUTPUT_DIR, outputFilename); + + // Ensure output directory exists + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); + + console.log(`Packing modified files into ${outputFilePath} with maximum compression...`); + + // Change working directory to rootDir to ensure files are added directly + execCommand(`${sevenZipPath} a -t7z "${outputFilePath}" * -mx=9`, rootDir); + console.log(`Packaged archive created at ${outputFilePath}`); + + // Step 10: Cleanup temporary directory + console.log(`Cleaning up temporary files at ${TEMP_DIR}...`); + fs.rmSync(TEMP_DIR, { recursive: true, force: true }); + console.log('Cleanup complete.'); + + console.log('=== Build AnimeJaNai Script Completed Successfully ==='); + } catch (error) { + console.error('Error during build:', error.message); + process.exit(1); + } +})(); diff --git a/build/deploy_windows.js b/build/deploy_windows.js index ff3f444..79608df 100644 --- a/build/deploy_windows.js +++ b/build/deploy_windows.js @@ -29,7 +29,7 @@ const NODE_EXE = path.join(SOURCE_DIR, 'utils', 'windows', 'node.exe'); const DS_FOLDER = path.join(SOURCE_DIR, 'utils', 'windows', 'DS'); const STREMIO_RUNTIME_EXE = path.join(SOURCE_DIR, 'utils', 'windows', 'stremio-runtime.exe'); const FFMPEG_FOLDER = path.join(SOURCE_DIR, 'utils', 'windows', 'ffmpeg'); -const MPV_FOLDER = path.join(SOURCE_DIR, 'utils', 'mpv'); +const MPV_FOLDER = path.join(SOURCE_DIR, 'utils', 'mpv', 'anime4k'); // Default Paths const DEFAULT_OPENSSL_BIN = 'C:\\Program Files\\OpenSSL-Win64\\bin'; diff --git a/src/player/mpv.cpp b/src/player/mpv.cpp index 33ee311..2e48c8d 100644 --- a/src/player/mpv.cpp +++ b/src/player/mpv.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -142,8 +143,14 @@ void MpvObject::initialize_mpv() { // terminal=yes brings us all the terminal logs; on windows it's much better with winpty (https://github.com/mpv-player/mpv/blob/master/DOCS/compile-windows.md) mpv_set_option_string(mpv, "terminal", "yes"); mpv_set_option_string(mpv, "msg-level", "all=v"); - // Set mpv.conf path to appDirPath. Can be used by users to apply mpv profiles using Conditional auto profiles with profile-cond property - QString configPath = QCoreApplication::applicationDirPath(); + // Set mpv.conf path to appDirPath/portable_config. Can be used by users to apply mpv profiles using Conditional auto profiles with profile-cond property + QString appDirPath = QCoreApplication::applicationDirPath(); + QString configPath = appDirPath + QDir::separator() + "portable_config"; + bool success = QDir().mkpath(configPath); + if (!success) { + qWarning("Failed to create portable_config directory."); + } + // Set the "config-dir" option for mpv mpv_set_option_string(mpv, "config-dir", configPath.toUtf8().constData()); mpv_set_option_string(mpv, "config", "yes"); diff --git a/utils/mpv/mpv.conf b/utils/mpv/mpv.conf deleted file mode 100644 index 9151fe1..0000000 --- a/utils/mpv/mpv.conf +++ /dev/null @@ -1,46 +0,0 @@ -[stremio-default] -# Default Stremio Profile uncomment below line to customize / overwrite defaullt settings -# profile-cond=width >= 1 -profile-desc=Stremio Profile -# Shorten probe size and analyzeduration to reduce initial demux overhead. (Faster playback start) -demuxer-lavf-probesize=524288 -demuxer-lavf-analyzeduration=0.5f -# Increase max bytes/packets to allow for 60s cache-secs. Default 150mb/75mb -demuxer-max-bytes=300000000 -demuxer-max-packets=150000000 -# Buffer / Cache Settings -cache=yes -cache-pause=no -cache-secs=60 # Limited by demuxer-max-bytes -# More threads for decoding -vd-lavc-threads=0 -ad-lavc-threads=0 - -[stremio-anime] -# Example profile following this guide https://kokomins.wordpress.com/2019/10/14/mpv-config-guide/#advanced-video-scaling-config -profile-desc=Anime Profile -# See mpv docs for conditional auto profiles https://mpv.io/manual/stable/#conditional-auto-profiles .To enable uncomment the line below: -# profile-cond=filename ~= "(?i)anime|fansub|horriblesubs|ttga|lulu|ozr|thighs|mtbb|ember|lostyears|breeze|yuisubs|almighty|bigfoot|neohevc|cleo|judas|anime time|golumpa|legion|nokou|usagi|av1ary|yameii|metaljerk|gbr|sam|scy|salieri|anipakku|cbm|db|valenciano|suki desu|hakata ramen|trix|jacobswaggedup|sokudo" -profile=gpu-hq -# Deband filter. Always turn on for anime. -deband=yes # Default values are 1:64:16:48 - -# Deband parameters configuration. -deband-iterations=2 # Range 1-16. -deband-threshold=35 # Range 0-4096. -deband-range=20 # Range 1-64. -deband-grain=5 # Range 0-4096. - -dither-depth=auto - -volume=100 -volume-max=100 - -demuxer-mkv-subtitle-preroll=yes -sub-auto=fuzzy - -# Source https://gist.github.com/igv/2364ffa6e81540f29cb7ab4c9bc05b6b -glsl-shader="~~/shaders/SSimSuperResMitchell.glsl" -scale=ewa_lanczossharp -# Source https://github.com/awused/dotfiles/blob/master/mpv/.config/mpv/shaders/KrigBilateral.glsl -glsl-shader="~~/shaders/KrigBilateral.glsl" # High quality chroma upscaler. \ No newline at end of file diff --git a/utils/mpv/shaders/KrigBilateral.glsl b/utils/mpv/shaders/KrigBilateral.glsl deleted file mode 100644 index b257c86..0000000 --- a/utils/mpv/shaders/KrigBilateral.glsl +++ /dev/null @@ -1,211 +0,0 @@ -// KrigBilateral by Shiandow -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 3.0 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library. - -//!HOOK CHROMA -//!BIND HOOKED -//!BIND LUMA -//!SAVE LOWRES_Y -//!WIDTH LUMA.w -//!WHEN CHROMA.w LUMA.w < -//!DESC KrigBilateral Downscaling Y pass 1 - -#define lumaOffset (-vec2(0.0, 0.0)*LUMA_size*CHROMA_pt) - -#define axis 1 - -#define Kernel(x) (1. - abs(x)) - -vec4 hook() { - // Calculate bounds - float low = ceil((LUMA_pos - 0.5*CHROMA_pt) * LUMA_size - lumaOffset - 0.5)[axis]; - float high = floor((LUMA_pos + 0.5*CHROMA_pt) * LUMA_size - lumaOffset - 0.5)[axis]; - - float W = 0.0; - vec4 avg = vec4(0); - vec2 pos = LUMA_pos; - - for (float k = low; k <= high; k++) { - pos[axis] = LUMA_pt[axis] * (k - lumaOffset[axis] + 0.5); - float rel = (pos[axis] - LUMA_pos[axis])*CHROMA_size[axis]; - float w = Kernel(rel); - - vec4 y = textureLod(LUMA_raw, pos, 0.0).xxxx * LUMA_mul; - y.y *= y.y; - avg += w * y; - W += w; - } - avg /= W; - avg.y = abs(avg.y - pow(avg.x, 2.0)); - return avg; -} - -//!HOOK CHROMA -//!BIND HOOKED -//!BIND LOWRES_Y -//!SAVE LOWRES_Y -//!WHEN CHROMA.w LUMA.w < -//!DESC KrigBilateral Downscaling Y pass 2 - -#define lumaOffset (-vec2(0.0, 0.0)*LOWRES_Y_size*CHROMA_pt) - -#define axis 0 - -#define Kernel(x) (1. - abs(x)) - -vec4 hook() { - // Calculate bounds - float low = ceil((LOWRES_Y_pos - 0.5*CHROMA_pt) * LOWRES_Y_size - lumaOffset - 0.5)[axis]; - float high = floor((LOWRES_Y_pos + 0.5*CHROMA_pt) * LOWRES_Y_size - lumaOffset - 0.5)[axis]; - - float W = 0.0; - vec4 avg = vec4(0); - vec2 pos = LOWRES_Y_pos; - - for (float k = low; k <= high; k++) { - pos[axis] = LOWRES_Y_pt[axis] * (k - lumaOffset[axis] + 0.5); - float rel = (pos[axis] - LOWRES_Y_pos[axis])*CHROMA_size[axis]; - float w = Kernel(rel); - - vec4 y = textureLod(LOWRES_Y_raw, pos, 0.0).xxxx * LOWRES_Y_mul; - y.y *= y.y; - avg += w * y; - W += w; - } - avg /= W; - avg.y = abs(avg.y - pow(avg.x, 2.0)) + LOWRES_Y_texOff(0).y; - return avg; -} - -//!HOOK CHROMA -//!BIND HOOKED -//!BIND LUMA -//!BIND LOWRES_Y -//!WIDTH LUMA.w -//!HEIGHT LUMA.h -//!WHEN CHROMA.w LUMA.w < -//!OFFSET ALIGN -//!DESC KrigBilateral Upscaling UV - -// -- Convenience -- -#define sqr(x) dot(x,x) -#define bitnoise 1.0/(2.0*255.0) -#define noise 0.05//5.0*bitnoise -#define chromaOffset vec2(0.0, 0.0) - -// -- Window Size -- -#define taps 3 -#define even (float(taps) - 2.0 * floor(float(taps) / 2.0) == 0.0) -#define minX int(1.0-ceil(float(taps)/2.0)) -#define maxX int(floor(float(taps)/2.0)) - -#define Kernel(x) (cos(acos(-1.0)*(x)/float(taps))) // Hann kernel - -// -- Input processing -- -#define GetY(coord) LOWRES_Y_tex(LOWRES_Y_pt*(pos+coord+vec2(0.5))).xy -#define GetUV(coord) CHROMA_tex(CHROMA_pt*(pos+coord+vec2(0.5))).xy - -#define N (taps*taps - 1) - -#define M(i,j) Mx[min(i,j)*N + max(i,j) - min(i,j)*(min(i,j)+1)/2] - -#define C(i,j) (inversesqrt(1.0 + (X[i].y + X[j].y)/localVar) * exp(-0.5*(sqr(X[i].x - X[j].x)/(localVar + X[i].y + X[j].y) + sqr((coords[i] - coords[j])/radius))) + (X[i].x - y) * (X[j].x - y) / localVar) -#define c(i) (inversesqrt(1.0 + X[i].y/localVar) * exp(-0.5*(sqr(X[i].x - y)/(localVar + X[i].y) + sqr((coords[i] - offset)/radius)))) - -vec4 hook() { - vec2 pos = CHROMA_pos * HOOKED_size - chromaOffset - vec2(0.5); - vec2 offset = pos - (even ? floor(pos) : round(pos)); - pos -= offset; - - vec2 coords[N+1]; - vec4 X[N+1]; - float y = LUMA_texOff(0).x; - vec4 total = vec4(0); - - coords[0] = vec2(-1,-1); coords[1] = vec2(-1, 0); coords[2] = vec2(-1, 1); - coords[3] = vec2( 0,-1); coords[4] = vec2( 0, 1); coords[5] = vec2( 1,-1); - coords[6] = vec2( 1, 0); coords[7] = vec2( 1, 1); coords[8] = vec2( 0, 0); - - for (int i=0; i 6) b[N-1-i] -= M(N-1-i, 1) * b[1]; - if(i > 5) b[N-1-i] -= M(N-1-i, 2) * b[2]; - if(i > 4) b[N-1-i] -= M(N-1-i, 3) * b[3]; - if(i > 3) b[N-1-i] -= M(N-1-i, 4) * b[4]; - if(i > 2) b[N-1-i] -= M(N-1-i, 5) * b[5]; - if(i > 1) b[N-1-i] -= M(N-1-i, 6) * b[6]; - if(i > 0) b[N-1-i] -= M(N-1-i, 7) * b[7]; - - b[N-1-i] /= M(N-1-i, N-1-i); - interp += b[N-1-i] * (X[N-1-i] - X[N]); - } - - return interp.zwxx; -} diff --git a/utils/mpv/shaders/SSimSuperRes.glsl b/utils/mpv/shaders/SSimSuperRes.glsl deleted file mode 100644 index a8feaac..0000000 --- a/utils/mpv/shaders/SSimSuperRes.glsl +++ /dev/null @@ -1,203 +0,0 @@ -// SSimSuperRes by Shiandow -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 3.0 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library. - -//!HOOK POSTKERNEL -//!BIND HOOKED -//!SAVE LOWRES -//!HEIGHT NATIVE_CROPPED.h -//!WHEN NATIVE_CROPPED.h OUTPUT.h < -//!COMPONENTS 4 -//!DESC SSSR Downscaling I - -#define axis 1 - -#define offset vec2(0,0) - -#define MN(B,C,x) (x < 1.0 ? ((2.-1.5*B-(C))*x + (-3.+2.*B+C))*x*x + (1.-(B)/3.) : (((-(B)/6.-(C))*x + (B+5.*C))*x + (-2.*B-8.*C))*x+((4./3.)*B+4.*C)) -#define Kernel(x) MN(0.334, 0.333, abs(x)) -#define taps 2.0 - -#define Luma(rgb) dot(rgb*rgb, vec3(0.2126, 0.7152, 0.0722)) - -vec4 hook() { - float low = ceil((HOOKED_pos - taps/input_size) * HOOKED_size - offset - 0.5)[axis]; - float high = floor((HOOKED_pos + taps/input_size) * HOOKED_size - offset - 0.5)[axis]; - - float W = 0.0; - vec4 avg = vec4(0); - vec2 pos = HOOKED_pos; - vec4 tex; - - for (float k = low; k <= high; k++) { - pos[axis] = HOOKED_pt[axis] * (k - offset[axis] + 0.5); - float rel = (pos[axis] - HOOKED_pos[axis])*input_size[axis]; - float w = Kernel(rel); - - tex.rgb = textureLod(HOOKED_raw, pos, 0.0).rgb * HOOKED_mul; - tex.a = Luma(tex.rgb); - avg += w * tex; - W += w; - } - avg /= W; - - return vec4(avg.rgb, max(abs(avg.a - Luma(avg.rgb)), 5e-7)); -} - -//!HOOK POSTKERNEL -//!BIND LOWRES -//!SAVE LOWRES -//!WIDTH NATIVE_CROPPED.w -//!HEIGHT NATIVE_CROPPED.h -//!WHEN NATIVE_CROPPED.w OUTPUT.w < -//!COMPONENTS 4 -//!DESC SSSR Downscaling II - -#define axis 0 - -#define offset vec2(0,0) - -#define MN(B,C,x) (x < 1.0 ? ((2.-1.5*B-(C))*x + (-3.+2.*B+C))*x*x + (1.-(B)/3.) : (((-(B)/6.-(C))*x + (B+5.*C))*x + (-2.*B-8.*C))*x+((4./3.)*B+4.*C)) -#define Kernel(x) MN(0.334, 0.333, abs(x)) -#define taps 2.0 - -#define Luma(rgb) dot(rgb*rgb, vec3(0.2126, 0.7152, 0.0722)) - -vec4 hook() { - float low = ceil((LOWRES_pos - taps/input_size) * LOWRES_size - offset - 0.5)[axis]; - float high = floor((LOWRES_pos + taps/input_size) * LOWRES_size - offset - 0.5)[axis]; - - float W = 0.0; - vec4 avg = vec4(0); - vec2 pos = LOWRES_pos; - vec4 tex; - - for (float k = low; k <= high; k++) { - pos[axis] = LOWRES_pt[axis] * (k - offset[axis] + 0.5); - float rel = (pos[axis] - LOWRES_pos[axis])*input_size[axis]; - float w = Kernel(rel); - - tex.rgb = textureLod(LOWRES_raw, pos, 0.0).rgb * LOWRES_mul; - tex.a = Luma(tex.rgb); - avg += w * tex; - W += w; - } - avg /= W; - - return vec4(avg.rgb, max(abs(avg.a - Luma(avg.rgb)), 5e-7) + LOWRES_texOff(0).a); -} - -//!HOOK POSTKERNEL -//!BIND PREKERNEL -//!BIND LOWRES -//!SAVE var -//!WIDTH NATIVE_CROPPED.w -//!HEIGHT NATIVE_CROPPED.h -//!WHEN NATIVE_CROPPED.h OUTPUT.h < -//!COMPONENTS 2 -//!DESC SSSR var - -#define spread 1.0 / 4.0 - -#define GetL(x,y) PREKERNEL_tex(PREKERNEL_pt * (PREKERNEL_pos * input_size + tex_offset + vec2(x,y))).rgb -#define GetH(x,y) LOWRES_texOff(vec2(x,y)).rgb - -#define Luma(rgb) dot(rgb*rgb, vec3(0.2126, 0.7152, 0.0722)) -#define diff(x,y) vec2(Luma((GetL(x,y) - meanL)), Luma((GetH(x,y) - meanH))) - -vec4 hook() { - vec3 meanL = GetL(0,0); - vec3 meanH = GetH(0,0); - for (int X=-1; X<=1; X+=2) { - meanL += GetL(X,0) * spread; - meanH += GetH(X,0) * spread; - } - for (int Y=-1; Y<=1; Y+=2) { - meanL += GetL(0,Y) * spread; - meanH += GetH(0,Y) * spread; - } - meanL /= (1.0 + 4.0*spread); - meanH /= (1.0 + 4.0*spread); - - vec2 var = diff(0,0); - for (int X=-1; X<=1; X+=2) - var += diff(X,0) * spread; - - for (int Y=-1; Y<=1; Y+=2) - var += diff(0,Y) * spread; - - return vec4(max(var / (1.0 + 4.0*spread), vec2(1e-6)), 0, 0); -} - -//!HOOK POSTKERNEL -//!BIND HOOKED -//!BIND PREKERNEL -//!BIND LOWRES -//!BIND var -//!WHEN NATIVE_CROPPED.h OUTPUT.h < -//!DESC SSSR final pass - -#define oversharp 0.5 - -// -- Window Size -- -#define taps 3.0 -#define even (taps - 2.0 * floor(taps / 2.0) == 0.0) -#define minX int(1.0-ceil(taps/2.0)) -#define maxX int(floor(taps/2.0)) - -#define Kernel(x) cos(acos(-1.0)*(x)/taps) // Hann kernel - -// -- Input processing -- -#define var(x,y) var_tex(var_pt * (pos + vec2(x,y) + 0.5)).rg -#define GetL(x,y) PREKERNEL_tex(PREKERNEL_pt * (pos + tex_offset + vec2(x,y) + 0.5)).rgb -#define GetH(x,y) LOWRES_tex(LOWRES_pt * (pos + vec2(x,y) + 0.5)) - -#define Luma(rgb) dot(rgb*rgb, vec3(0.2126, 0.7152, 0.0722)) - -vec4 hook() { - vec4 c0 = HOOKED_texOff(0); - - vec2 pos = HOOKED_pos * LOWRES_size - vec2(0.5); - vec2 offset = pos - (even ? floor(pos) : round(pos)); - pos -= offset; - - vec2 mVar = vec2(0.0); - for (int X=-1; X<=1; X++) - for (int Y=-1; Y<=1; Y++) { - vec2 w = clamp(1.5 - abs(vec2(X,Y)), 0.0, 1.0); - mVar += w.r * w.g * vec2(GetH(X,Y).a, 1.0); - } - mVar.r /= mVar.g; - - // Calculate faithfulness force - float weightSum = 0.0; - vec3 diff = vec3(0); - - for (int X = minX; X <= maxX; X++) - for (int Y = minX; Y <= maxX; Y++) - { - float R = (-1.0 - oversharp) * sqrt(var(X,Y).r / (var(X,Y).g + mVar.r)); - - vec2 krnl = Kernel(vec2(X,Y) - offset); - float weight = krnl.r * krnl.g / (Luma((c0.rgb - GetH(X,Y).rgb)) + GetH(X,Y).a); - - diff += weight * (GetL(X,Y) + GetH(X,Y).rgb * R + (-1.0 - R) * (c0.rgb)); - weightSum += weight; - } - diff /= weightSum; - - c0.rgb = ((c0.rgb) + diff); - - return c0; -} diff --git a/utils/windows/installer/windows-installer.nsi b/utils/windows/installer/windows-installer.nsi index d208f30..0734e38 100644 --- a/utils/windows/installer/windows-installer.nsi +++ b/utils/windows/installer/windows-installer.nsi @@ -309,6 +309,7 @@ Section "uninstall" IfErrors 0 KeepUserData notsilent: RMDir /r "$LOCALAPPDATA\${COMPANY_NAME}" + RMDir /r "$APPDATA\${COMPANY_NAME}" RMDir /r "$APPDATA\${DATA_FOLDER}" KeepUserData: