Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to MakeMKV Auto Rip will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed

- **Windows MakeMKV detection** - Detection, validation and the executable the app runs now all use `makemkvcon64.exe`, falling back to `makemkvcon.exe` when the 64-bit executable is not installed

## [1.0.0] - 2025-08-12

### Added
Expand Down
8 changes: 3 additions & 5 deletions src/config/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { readFileSync } from "fs";
import { dirname, join, resolve, normalize, sep } from "path";
import { dirname, resolve, normalize, sep } from "path";
import { fileURLToPath } from "url";
import { parse } from "yaml";
import { FileSystemUtils } from "../utils/filesystem.js";
Expand Down Expand Up @@ -168,10 +168,8 @@ export class AppConfig {
const mkvDir = await this.getMkvDir();
if (!mkvDir) return null;

// Handle cross-platform executable names
const executableName =
process.platform === "win32" ? "makemkvcon64.exe" : "makemkvcon";
const executablePath = join(mkvDir, executableName);
const executablePath = await FileSystemUtils.findMakeMKVExecutable(mkvDir);
if (!executablePath) return null;

// Quote the path if it contains spaces (important for Windows paths)
return executablePath.includes(" ")
Expand Down
41 changes: 23 additions & 18 deletions src/utils/filesystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,9 @@ export class FileSystemUtils {
await access(basePath);

// Check if makemkvcon executable exists in this path
const executableName =
platform === "win32" ? "makemkvcon.exe" : "makemkvcon";
const executablePath = join(basePath, executableName);

try {
await access(executablePath);
if (await this.findMakeMKVExecutable(basePath)) {
Logger.info(`Found MakeMKV installation at: ${basePath}`);
return basePath;
} catch {
// Executable not found in this directory, try next
continue;
}
} catch {
// Directory doesn't exist, try next
Expand All @@ -151,17 +143,30 @@ export class FileSystemUtils {
*/
static async validateMakeMKVInstallation(mkvDir) {
if (!mkvDir) return false;
return Boolean(await this.findMakeMKVExecutable(mkvDir));
}

try {
const platform = os.platform();
const executableName =
platform === "win32" ? "makemkvcon.exe" : "makemkvcon";
const executablePath = join(mkvDir, executableName);
/**
* Find the makemkvcon executable in a MakeMKV directory.
* On Windows, prefers makemkvcon64.exe and falls back to makemkvcon.exe.
* @param {string} mkvDir - Path to MakeMKV directory
* @returns {Promise<string|null>} - Full executable path or null if not found
*/
static async findMakeMKVExecutable(mkvDir) {
const executableNames =
os.platform() === "win32"
? ["makemkvcon64.exe", "makemkvcon.exe"]
: ["makemkvcon"];

await access(executablePath);
return true;
} catch {
return false;
for (const executableName of executableNames) {
const executablePath = join(mkvDir, executableName);
try {
await access(executablePath);
return executablePath;
} catch {
// Not found, try next name
}
}
return null;
}
}
10 changes: 10 additions & 0 deletions tests/unit/config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@
*/

import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
import { join } from "path";

// Partially mock FileSystemUtils for detection/validation only
const mockFileSystemUtils = {
detectMakeMKVInstallation: vi.fn().mockResolvedValue("/usr/bin"),
validateMakeMKVInstallation: vi.fn().mockResolvedValue(true),
findMakeMKVExecutable: vi.fn(),
};
const platformExecutable = async (mkvDir) =>
join(
mkvDir,
process.platform === "win32" ? "makemkvcon64.exe" : "makemkvcon"
);
vi.mock("../../src/utils/filesystem.js", async () => {
const actual = await vi.importActual("../../src/utils/filesystem.js");
return {
Expand All @@ -33,6 +40,9 @@ describe("AppConfig", () => {
// Reset mock implementation
mockFileSystemUtils.detectMakeMKVInstallation.mockResolvedValue("/usr/bin");
mockFileSystemUtils.validateMakeMKVInstallation.mockResolvedValue(true);
mockFileSystemUtils.findMakeMKVExecutable.mockImplementation(
platformExecutable
);
});

afterEach(() => {
Expand Down
19 changes: 18 additions & 1 deletion tests/unit/filesystem.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ describe("FileSystemUtils", () => {
const result = await FileSystemUtils.detectMakeMKVInstallation();
expect(result).toBe("C:/Program Files/MakeMKV");
expect(mockedFsPromises.access).toHaveBeenCalled();
expect(mockedFsPromises.access).toHaveBeenLastCalledWith(
expect.stringMatching(/makemkvcon64\.exe$/)
);
});

it("should detect MakeMKV on Linux", async () => {
Expand Down Expand Up @@ -125,7 +128,21 @@ describe("FileSystemUtils", () => {
"C:/Program Files/MakeMKV"
);
expect(result).toBe(true);
expect(mockedFsPromises.access).toHaveBeenCalled();
expect(mockedFsPromises.access).toHaveBeenCalledWith(
expect.stringMatching(/makemkvcon64\.exe$/)
);
});

it("should fall back to makemkvcon.exe on Windows", async () => {
mockedOs.platform.mockReturnValue("win32");
mockedFsPromises.access.mockImplementation(async (path) => {
if (path.endsWith("makemkvcon64.exe")) throw new Error("ENOENT");
});

const result = await FileSystemUtils.findMakeMKVExecutable(
"C:/Program Files/MakeMKV"
);
expect(result).toMatch(/[\\/]makemkvcon\.exe$/);
});

it("should validate Unix installation", async () => {
Expand Down