Just wanted to share this little include file that provides a couple of functions that can be used to detect the main image types from a file based on the header (magic number) of the file.
inc_commonImage.opusscriptinstall (2.4 KB)
Standard use:
var header = readBytes(imagePath, 64);
var fmt = detectFormat(header);
The object returned has two properties:
fmt.format: string with the format name (e.g. JPEG, PNG, WEBP, ...)fmy.ext: string with the common extension for that file type (e.g. .jpg, .png, .webp, ...)
Source code:
// commonImage
// (c) 2026 Stephane
// This is an include file script for Directory Opus.
// See https://www.gpsoft.com.au/endpoints/redirect.php?page=scripts for development information.
// Called by Directory Opus to initialize the include file script
function OnInitIncludeFile(initData)
{
initData.name = "commonImage";
initData.version = "1.0";
initData.copyright = "(c) 2026 Stephane";
// initData.url = "https://resource.dopus.com/c/buttons-scripts/16";
initData.desc = "";
initData.min_version = "13.0";
initData.shared = true;
}
// --- Helpers ---
function readBytes(filePath, maxBytes) {
var file = DOpus.FSUtil.OpenFile(filePath);
var data = DOpus.Create.Blob();
var sizeToRead = Math.min(maxBytes, file.size.val);
var sizeRead = file.Read(data, sizeToRead);
// logger.debug("Read " + sizeRead + " bytes");
var out = [];
for (var i = 0; i < data.size; i++) {
out.push(data(i));
// logger.debug("#" + i + " => " + data(i));
}
file.Close();
return out;
}
function bytesToString(bytes, start, length) {
// Convert bytes to 8-bit ASCII strings (1 char = 1 byte).
// Used to compare text magic numbers (e.g.: "RIFF", "WEBP", "8BPS").
var s = "";
var end = (typeof length === "number") ? (start + length) : bytes.length;
for (var i = start; i < end && i < bytes.length; i++) {
s += String.fromCharCode(bytes[i] & 0xFF);
}
return s;
}
function matchPrefix(bytes, arr) {
// Compares an exact binary prefix : arr = [0xFF, 0xD8, ...]
if (!bytes || bytes.length < arr.length) return false;
for (var i = 0; i < arr.length; i++) {
if ((bytes[i] & 0xFF) !== arr[i]) return false;
}
return true;
}
function equalsAt(bytes, offset, str) {
// Compares an ASCII string at a given offset
if (!bytes || bytes.length < offset + str.length) return false;
for (var i = 0; i < str.length; i++) {
if ((bytes[offset + i] & 0xFF) !== str.charCodeAt(i)) return false;
}
return true;
}
var magicNumbers = DOpus.Create.Map();
magicNumbers("JPEG") = {header: [0xFF, 0xD8, 0xFF], ext: ".jpg" };
magicNumbers("JPEG") = {header: [0xFF, 0xD8, 0xFF], ext:".jpg" };
magicNumbers("PNG") = {header: [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A], ext:".png" };
magicNumbers("TIFF (Big-endian)") = {header: [0x4D, 0x4D, 0x00, 0x2A], ext:".tiff" };
magicNumbers("JPEG 2000 (.jp2)") = {header: [0x00,0x00,0x00,0x0C,0x6A,0x50,0x20,0x20,0x0D,0x0A,0x87,0x0A], ext:".jp2" };
magicNumbers("JPEG XL (bitstream)") = {header: [0xFF, 0x0A], ext:".jxl" };
magicNumbers("JPEG XL (container)") = {header: [0x00,0x00,0x00,0x0C,0x4A,0x58,0x4C,0x20,0x0D,0x0A,0x87,0x0A], ext:".jxl" };
magicNumbers("ICO (icône Windows)") = {header: [0x00,0x00,0x01,0x00], ext:".ico" };
magicNumbers("CUR (curseur Windows)") = {header: [0x00,0x00,0x02,0x00], ext:".cur" };
var stringHeaders = DOpus.Create.Map();
stringHeaders("GIF") = {start:0, length:6, match: "GIF87a", ext: ".gif"};
stringHeaders("GIF") = {start:0, length:6, match: "GIF89a", ext: ".gif"};
stringHeaders("BMP") = {start:0, length:2, match: "BM", ext: ".bmp"};
stringHeaders("PSD (Adobe Photoshop)") = {start:0, length:4, match: "8BPS", ext: ".psd"};
stringHeaders("XCF (GIMP)") = {start:0, length:8, match: "gimp xcf", ext: ".xcf"};
function detectFormat(bytes) {
if (!bytes || bytes.length === 0) return { format: "Unknown (empty file)", ext: "" };
// Helper for the return values
function res(format, ext) { return { format: format, ext: ext }; }
// All fixed header from the magicNumbers map
for (var e = new Enumerator(magicNumbers); !e.atEnd(); e.moveNext()) {
var format = e.item();
var specs = magicNumbers(format);
if (matchPrefix(bytes, specs.header))
return res(format, specs.ext);
}
// TIFF (LE / BE)
if (matchPrefix(bytes, [0x49, 0x49, 0x2A, 0x00])) {
// Canon CR2 : "II*\x00\x10\x00\x00\x00CR"
var isCR2 = (bytes.length >= 12 &&
bytes[4] === 0x10 && bytes[5] === 0x00 &&
bytes[6] === 0x00 && bytes[7] === 0x00 &&
bytes[8] === 0x43 && bytes[9] === 0x52);
return isCR2 ? res("Canon CR2 (TIFF LE basé)", ".cr2") : res("TIFF (Little-endian)", ".tiff");
}
if (matchPrefix(bytes, [0x4D, 0x4D, 0x00, 0x2A])) {
// NEF (Nikon) is often TIFF BE ; without further check, we return .tiff
return res("TIFF (Big-endian)", ".tiff");
}
for (var e = new Enumerator(stringHeaders); !e.atEnd(); e.moveNext()) {
var format = e.item();
var specs = stringHeaders(format);
if (bytesToString(bytes, specs.start, specs.length) === specs.match)
return res(format, specs.ext);
}
// WEBP (RIFF + 'WEBP' at offset 8)
if (bytesToString(bytes, 0, 4) === "RIFF" && bytesToString(bytes, 8, 4) === "WEBP") {
return res("WEBP", ".webp");
}
// ISO BMFF: 'ftyp' at offset 4 -> HEIC/HEIF/AVIF, etc.
if (bytes.length >= 12 && bytesToString(bytes, 4, 4) === "ftyp") {
var major = bytesToString(bytes, 8, 4);
var heifBrands = { "heic":1, "heix":1, "heim":1, "heis":1, "hevc":1, "mif1":1, "msf1":1 };
var avifBrands = { "avif":1, "av01":1, "avis":1 };
if (heifBrands[major]) {
// HEIC (HEVC) vs HEIF generic
if (major === "heic" || major === "heix" || major === "heim" || major === "heis" || major === "hevc") {
return res("HEIC (HEIF/ISO-BMFF)", ".heic");
}
return res("HEIF (ISO-BMFF)", ".heif");
}
if (avifBrands[major]) {
return res("AVIF (ISO-BMFF)", ".avif");
}
// Other ISO-BMFF (probably not a standard image)
return res("ISO-BMFF (ftyp: " + major + ")", "");
}
return res("Unknwon signature", "");
}