feat: add support for setting group icons in BlueBubbles, enhancing group management capabilities
This commit is contained in:
committed by
Peter Steinberger
parent
574b848863
commit
14a072f5fa
@@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url";
|
||||
import { logVerbose, shouldLogVerbose } from "../globals.js";
|
||||
import { type MediaKind, maxBytesForKind, mediaKindFromMime } from "../media/constants.js";
|
||||
import { fetchRemoteMedia } from "../media/fetch.js";
|
||||
import { resizeToJpeg } from "../media/image-ops.js";
|
||||
import { convertHeicToJpeg, resizeToJpeg } from "../media/image-ops.js";
|
||||
import { detectMime, extensionForMime } from "../media/mime.js";
|
||||
|
||||
type WebMediaResult = {
|
||||
@@ -20,6 +20,15 @@ type WebMediaOptions = {
|
||||
optimizeImages?: boolean;
|
||||
};
|
||||
|
||||
const HEIC_MIME_RE = /^image\/hei[cf]$/i;
|
||||
const HEIC_EXT_RE = /\.(heic|heif)$/i;
|
||||
|
||||
function isHeicSource(opts: { contentType?: string; fileName?: string }): boolean {
|
||||
if (opts.contentType && HEIC_MIME_RE.test(opts.contentType.trim())) return true;
|
||||
if (opts.fileName && HEIC_EXT_RE.test(opts.fileName.trim())) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
async function loadWebMediaInternal(
|
||||
mediaUrl: string,
|
||||
options: WebMediaOptions = {},
|
||||
@@ -34,9 +43,13 @@ async function loadWebMediaInternal(
|
||||
}
|
||||
}
|
||||
|
||||
const optimizeAndClampImage = async (buffer: Buffer, cap: number) => {
|
||||
const optimizeAndClampImage = async (
|
||||
buffer: Buffer,
|
||||
cap: number,
|
||||
meta?: { contentType?: string; fileName?: string },
|
||||
) => {
|
||||
const originalSize = buffer.length;
|
||||
const optimized = await optimizeImageToJpeg(buffer, cap);
|
||||
const optimized = await optimizeImageToJpeg(buffer, cap, meta);
|
||||
if (optimized.optimizedSize < originalSize && shouldLogVerbose()) {
|
||||
logVerbose(
|
||||
`Optimized media from ${(originalSize / (1024 * 1024)).toFixed(2)}MB to ${(optimized.optimizedSize / (1024 * 1024)).toFixed(2)}MB (side≤${optimized.resizeSide}px, q=${optimized.quality})`,
|
||||
@@ -86,7 +99,10 @@ async function loadWebMediaInternal(
|
||||
};
|
||||
}
|
||||
return {
|
||||
...(await optimizeAndClampImage(params.buffer, cap)),
|
||||
...(await optimizeAndClampImage(params.buffer, cap, {
|
||||
contentType: params.contentType,
|
||||
fileName: params.fileName,
|
||||
})),
|
||||
fileName: params.fileName,
|
||||
};
|
||||
}
|
||||
@@ -150,6 +166,7 @@ export async function loadWebMediaRaw(
|
||||
export async function optimizeImageToJpeg(
|
||||
buffer: Buffer,
|
||||
maxBytes: number,
|
||||
opts: { contentType?: string; fileName?: string } = {},
|
||||
): Promise<{
|
||||
buffer: Buffer;
|
||||
optimizedSize: number;
|
||||
@@ -157,6 +174,14 @@ export async function optimizeImageToJpeg(
|
||||
quality: number;
|
||||
}> {
|
||||
// Try a grid of sizes/qualities until under the limit.
|
||||
let source = buffer;
|
||||
if (isHeicSource(opts)) {
|
||||
try {
|
||||
source = await convertHeicToJpeg(buffer);
|
||||
} catch (err) {
|
||||
throw new Error(`HEIC image conversion failed: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
const sides = [2048, 1536, 1280, 1024, 800];
|
||||
const qualities = [80, 70, 60, 50, 40];
|
||||
let smallest: {
|
||||
@@ -170,7 +195,7 @@ export async function optimizeImageToJpeg(
|
||||
for (const quality of qualities) {
|
||||
try {
|
||||
const out = await resizeToJpeg({
|
||||
buffer,
|
||||
buffer: source,
|
||||
maxSide: side,
|
||||
quality,
|
||||
withoutEnlargement: true,
|
||||
|
||||
Reference in New Issue
Block a user