@muhgholy/next-drive 4.5.0 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -39
- package/dist/{chunk-YUU5BFE7.js → chunk-OWKTTRQC.js} +190 -21
- package/dist/chunk-OWKTTRQC.js.map +1 -0
- package/dist/{chunk-KQGZXSKY.cjs → chunk-YR4DEKWI.cjs} +190 -21
- package/dist/chunk-YR4DEKWI.cjs.map +1 -0
- package/dist/server/controllers/drive.d.ts +26 -3
- package/dist/server/controllers/drive.d.ts.map +1 -1
- package/dist/server/express.cjs +11 -11
- package/dist/server/express.js +2 -2
- package/dist/server/index.cjs +13 -13
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/utils.d.ts +21 -1
- package/dist/server/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-KQGZXSKY.cjs.map +0 -1
- package/dist/chunk-YUU5BFE7.js.map +0 -1
package/README.md
CHANGED
|
@@ -55,17 +55,6 @@ This package uses [subpath exports](https://nodejs.org/api/packages.html#subpath
|
|
|
55
55
|
}
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
**For projects using bundlers (Vite, Webpack, etc.):**
|
|
59
|
-
|
|
60
|
-
```json
|
|
61
|
-
{
|
|
62
|
-
"compilerOptions": {
|
|
63
|
-
"module": "esnext",
|
|
64
|
-
"moduleResolution": "bundler"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
58
|
> ⚠️ The legacy `"moduleResolution": "node"` is **not supported** and will cause build errors with subpath imports like `@muhgholy/next-drive/server`.
|
|
70
59
|
|
|
71
60
|
**FFmpeg** (for video thumbnails):
|
|
@@ -280,18 +269,28 @@ Upload files programmatically from server-side code:
|
|
|
280
269
|
```typescript
|
|
281
270
|
import { driveUpload } from "@muhgholy/next-drive/server";
|
|
282
271
|
|
|
283
|
-
// Upload
|
|
272
|
+
// Upload to specific folder by ID
|
|
284
273
|
const file = await driveUpload(
|
|
285
274
|
"/tmp/photo.jpg",
|
|
286
275
|
{ userId: "123" },
|
|
287
276
|
{
|
|
288
277
|
name: "photo.jpg",
|
|
289
|
-
|
|
278
|
+
folder: { id: "folderId" }, // Optional: folder ID
|
|
290
279
|
accountId: "LOCAL", // Optional: storage account ID
|
|
291
280
|
enforce: false, // Optional: bypass quota check
|
|
292
281
|
}
|
|
293
282
|
);
|
|
294
283
|
|
|
284
|
+
// Upload to folder by path (creates folders if not exist)
|
|
285
|
+
const file = await driveUpload(
|
|
286
|
+
"/tmp/photo.jpg",
|
|
287
|
+
{ userId: "123" },
|
|
288
|
+
{
|
|
289
|
+
name: "photo.jpg",
|
|
290
|
+
folder: { path: "images/2024/january" }, // Creates folders recursively
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
|
|
295
294
|
// Upload from stream
|
|
296
295
|
import fs from "fs";
|
|
297
296
|
const stream = fs.createReadStream("/tmp/video.mp4");
|
|
@@ -318,13 +317,14 @@ const file = await driveUpload(
|
|
|
318
317
|
|
|
319
318
|
**Options:**
|
|
320
319
|
|
|
321
|
-
| Option
|
|
322
|
-
|
|
|
323
|
-
| `name`
|
|
324
|
-
| `
|
|
325
|
-
| `
|
|
326
|
-
| `
|
|
327
|
-
| `
|
|
320
|
+
| Option | Type | Required | Description |
|
|
321
|
+
| ------------ | --------------------------------- | -------- | -------------------------------------------------------- |
|
|
322
|
+
| `name` | `string` | Yes | File name with extension |
|
|
323
|
+
| `folder.id` | `string` | No | Parent folder ID |
|
|
324
|
+
| `folder.path`| `string` | No | Folder path (e.g., `images/2024`) - creates if not exist |
|
|
325
|
+
| `accountId` | `string` | No | Storage account ID ('LOCAL' for local storage) |
|
|
326
|
+
| `mime` | `string` | No | MIME type (auto-detected from extension if not provided) |
|
|
327
|
+
| `enforce` | `boolean` | No | Bypass quota check (default: false) |
|
|
328
328
|
|
|
329
329
|
### Get Signed URL
|
|
330
330
|
|
|
@@ -583,25 +583,122 @@ GOOGLE_REDIRECT_URI=http://localhost:3000/api/drive?action=callback
|
|
|
583
583
|
|
|
584
584
|
---
|
|
585
585
|
|
|
586
|
-
##
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
|
599
|
-
|
|
600
|
-
| `
|
|
601
|
-
| `
|
|
602
|
-
| `
|
|
603
|
-
| `
|
|
604
|
-
|
|
586
|
+
## Image Optimization
|
|
587
|
+
|
|
588
|
+
Serve optimized images with dynamic compression, resizing, and format conversion using query parameters.
|
|
589
|
+
|
|
590
|
+
### URL Format
|
|
591
|
+
|
|
592
|
+
```
|
|
593
|
+
/api/drive?action=serve&id={fileId}&quality={preset}&display={context}&size={preset}&format={format}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### Parameters
|
|
597
|
+
|
|
598
|
+
| Parameter | Type | Description |
|
|
599
|
+
|-----------|------|-------------|
|
|
600
|
+
| `quality` | `low` / `medium` / `high` / `1-100` | Compression level |
|
|
601
|
+
| `display` | string | Context-based quality adjustment |
|
|
602
|
+
| `size` | string | Predefined dimensions preset |
|
|
603
|
+
| `format` | `jpeg` / `webp` / `avif` / `png` | Output format |
|
|
604
|
+
|
|
605
|
+
### Quality Presets
|
|
606
|
+
|
|
607
|
+
| Preset | Base Quality | Use Case |
|
|
608
|
+
|--------|--------------|----------|
|
|
609
|
+
| `low` | 30 | Thumbnails, previews |
|
|
610
|
+
| `medium` | 50 | General content |
|
|
611
|
+
| `high` | 75 | High-quality display |
|
|
612
|
+
| `1-100` | Custom | Fine-tuned control |
|
|
613
|
+
|
|
614
|
+
> Quality is dynamically adjusted based on file size. Larger files get more aggressive compression.
|
|
615
|
+
|
|
616
|
+
### Display Presets (Quality Context)
|
|
617
|
+
|
|
618
|
+
| Display | Quality Factor | Use Case |
|
|
619
|
+
|---------|----------------|----------|
|
|
620
|
+
| `article-header` | 0.9 | Hero/banner images |
|
|
621
|
+
| `article-image` | 0.85 | In-content images |
|
|
622
|
+
| `thumbnail` | 0.7 | Small previews |
|
|
623
|
+
| `avatar` | 0.8 | Profile pictures |
|
|
624
|
+
| `logo` | 0.95 | Branding/logos |
|
|
625
|
+
| `card` | 0.8 | Card components |
|
|
626
|
+
| `gallery` | 0.85 | Gallery/grid |
|
|
627
|
+
| `og` | 0.9 | Open Graph/social |
|
|
628
|
+
| `icon` | 0.75 | Small icons |
|
|
629
|
+
| `cover` | 0.9 | Full-width covers |
|
|
630
|
+
| `story` | 0.85 | Story/vertical |
|
|
631
|
+
|
|
632
|
+
### Size Presets (Dimensions)
|
|
633
|
+
|
|
634
|
+
**Square Sizes:**
|
|
635
|
+
| Size | Dimensions |
|
|
636
|
+
|------|------------|
|
|
637
|
+
| `xs` | 64×64 |
|
|
638
|
+
| `sm` | 128×128 |
|
|
639
|
+
| `md` | 256×256 |
|
|
640
|
+
| `lg` | 512×512 |
|
|
641
|
+
| `xl` | 1024×1024 |
|
|
642
|
+
| `2xl` | 1600×1600 |
|
|
643
|
+
| `icon` | 48×48 |
|
|
644
|
+
| `thumb` | 150×150 |
|
|
645
|
+
| `square` | 600×600 |
|
|
646
|
+
| `avatar-sm` | 64×64 |
|
|
647
|
+
| `avatar-md` | 128×128 |
|
|
648
|
+
| `avatar-lg` | 256×256 |
|
|
649
|
+
|
|
650
|
+
**Landscape (16:9):**
|
|
651
|
+
| Size | Dimensions |
|
|
652
|
+
|------|------------|
|
|
653
|
+
| `landscape-sm` | 480×270 |
|
|
654
|
+
| `landscape` | 800×450 |
|
|
655
|
+
| `landscape-lg` | 1280×720 |
|
|
656
|
+
| `landscape-xl` | 1920×1080 |
|
|
657
|
+
|
|
658
|
+
**Portrait (9:16):**
|
|
659
|
+
| Size | Dimensions |
|
|
660
|
+
|------|------------|
|
|
661
|
+
| `portrait-sm` | 270×480 |
|
|
662
|
+
| `portrait` | 450×800 |
|
|
663
|
+
| `portrait-lg` | 720×1280 |
|
|
664
|
+
|
|
665
|
+
**Wide/Banner:**
|
|
666
|
+
| Size | Dimensions | Ratio |
|
|
667
|
+
|------|------------|-------|
|
|
668
|
+
| `wide` | 1200×630 | OG standard |
|
|
669
|
+
| `banner` | 1200×400 | 3:1 |
|
|
670
|
+
| `banner-sm` | 800×200 | 4:1 |
|
|
671
|
+
|
|
672
|
+
**Other:**
|
|
673
|
+
| Size | Dimensions | Ratio |
|
|
674
|
+
|------|------------|-------|
|
|
675
|
+
| `photo-4x3` | 800×600 | 4:3 |
|
|
676
|
+
| `photo-3x2` | 900×600 | 3:2 |
|
|
677
|
+
| `story` | 1080×1920 | 9:16 |
|
|
678
|
+
| `video` | 1280×720 | 16:9 |
|
|
679
|
+
| `video-sm` | 640×360 | 16:9 |
|
|
680
|
+
| `card-sm` | 300×200 | 3:2 |
|
|
681
|
+
| `card` | 400×300 | 4:3 |
|
|
682
|
+
| `card-lg` | 600×400 | 3:2 |
|
|
683
|
+
|
|
684
|
+
### Examples
|
|
685
|
+
|
|
686
|
+
```html
|
|
687
|
+
<!-- Article header with OG dimensions -->
|
|
688
|
+
<img src="/api/drive?action=serve&id=123&display=article-header&size=wide&format=webp">
|
|
689
|
+
|
|
690
|
+
<!-- Thumbnail with aggressive compression -->
|
|
691
|
+
<img src="/api/drive?action=serve&id=123&display=thumbnail&size=thumb&format=webp">
|
|
692
|
+
|
|
693
|
+
<!-- Avatar -->
|
|
694
|
+
<img src="/api/drive?action=serve&id=123&display=avatar&size=avatar-md&format=webp">
|
|
695
|
+
|
|
696
|
+
<!-- Gallery image -->
|
|
697
|
+
<img src="/api/drive?action=serve&id=123&display=gallery&size=landscape&format=webp">
|
|
698
|
+
|
|
699
|
+
<!-- Just quality, no resize -->
|
|
700
|
+
<img src="/api/drive?action=serve&id=123&quality=medium&format=webp">
|
|
701
|
+
```
|
|
605
702
|
|
|
606
703
|
---
|
|
607
704
|
|
|
@@ -356,13 +356,120 @@ var extractImageMetadata = async (filePath) => {
|
|
|
356
356
|
return null;
|
|
357
357
|
}
|
|
358
358
|
};
|
|
359
|
-
var
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
359
|
+
var DISPLAY_PRESETS = {
|
|
360
|
+
"article-header": 0.9,
|
|
361
|
+
// Hero/banner images - high quality
|
|
362
|
+
"article-image": 0.85,
|
|
363
|
+
// In-content images
|
|
364
|
+
"thumbnail": 0.7,
|
|
365
|
+
// Small previews - lower quality ok
|
|
366
|
+
"avatar": 0.8,
|
|
367
|
+
// Profile pictures
|
|
368
|
+
"logo": 0.95,
|
|
369
|
+
// Branding - needs clarity
|
|
370
|
+
"card": 0.8,
|
|
371
|
+
// Card components
|
|
372
|
+
"gallery": 0.85,
|
|
373
|
+
// Gallery/grid images
|
|
374
|
+
"og": 0.9,
|
|
375
|
+
// Open Graph/social sharing
|
|
376
|
+
"icon": 0.75,
|
|
377
|
+
// Small icons
|
|
378
|
+
"cover": 0.9,
|
|
379
|
+
// Full-width covers
|
|
380
|
+
"story": 0.85
|
|
381
|
+
// Story/vertical format
|
|
382
|
+
};
|
|
383
|
+
var SIZE_PRESETS = {
|
|
384
|
+
// Square sizes
|
|
385
|
+
"xs": { width: 64, height: 64 },
|
|
386
|
+
"sm": { width: 128, height: 128 },
|
|
387
|
+
"md": { width: 256, height: 256 },
|
|
388
|
+
"lg": { width: 512, height: 512 },
|
|
389
|
+
"xl": { width: 1024, height: 1024 },
|
|
390
|
+
"2xl": { width: 1600, height: 1600 },
|
|
391
|
+
// Named squares
|
|
392
|
+
"icon": { width: 48, height: 48 },
|
|
393
|
+
"thumb": { width: 150, height: 150 },
|
|
394
|
+
"square": { width: 600, height: 600 },
|
|
395
|
+
"avatar-sm": { width: 64, height: 64 },
|
|
396
|
+
"avatar-md": { width: 128, height: 128 },
|
|
397
|
+
"avatar-lg": { width: 256, height: 256 },
|
|
398
|
+
// Landscape (16:9)
|
|
399
|
+
"landscape-sm": { width: 480, height: 270 },
|
|
400
|
+
"landscape": { width: 800, height: 450 },
|
|
401
|
+
"landscape-lg": { width: 1280, height: 720 },
|
|
402
|
+
"landscape-xl": { width: 1920, height: 1080 },
|
|
403
|
+
// Portrait (9:16)
|
|
404
|
+
"portrait-sm": { width: 270, height: 480 },
|
|
405
|
+
"portrait": { width: 450, height: 800 },
|
|
406
|
+
"portrait-lg": { width: 720, height: 1280 },
|
|
407
|
+
// Wide/Banner (OG, social)
|
|
408
|
+
"wide": { width: 1200, height: 630 },
|
|
409
|
+
// Open Graph standard
|
|
410
|
+
"banner": { width: 1200, height: 400 },
|
|
411
|
+
// Banner/header
|
|
412
|
+
"banner-sm": { width: 800, height: 200 },
|
|
413
|
+
// Classic photo ratios
|
|
414
|
+
"photo-4x3": { width: 800, height: 600 },
|
|
415
|
+
// 4:3
|
|
416
|
+
"photo-3x2": { width: 900, height: 600 },
|
|
417
|
+
// 3:2
|
|
418
|
+
// Story/vertical (9:16)
|
|
419
|
+
"story": { width: 1080, height: 1920 },
|
|
420
|
+
// Video thumbnails
|
|
421
|
+
"video": { width: 1280, height: 720 },
|
|
422
|
+
"video-sm": { width: 640, height: 360 },
|
|
423
|
+
// Card sizes
|
|
424
|
+
"card-sm": { width: 300, height: 200 },
|
|
425
|
+
"card": { width: 400, height: 300 },
|
|
426
|
+
"card-lg": { width: 600, height: 400 }
|
|
427
|
+
};
|
|
428
|
+
var getImageSettings = (fileSizeInBytes, qualityPreset, display, size) => {
|
|
429
|
+
let baseQuality = 80;
|
|
430
|
+
if (qualityPreset === "low") baseQuality = 30;
|
|
431
|
+
else if (qualityPreset === "medium") baseQuality = 50;
|
|
432
|
+
else if (qualityPreset === "high") baseQuality = 75;
|
|
433
|
+
else if (qualityPreset) {
|
|
434
|
+
const n = parseInt(qualityPreset, 10);
|
|
435
|
+
if (!isNaN(n)) baseQuality = Math.min(100, Math.max(1, n));
|
|
436
|
+
}
|
|
437
|
+
const displayFactor = display && DISPLAY_PRESETS[display] ? DISPLAY_PRESETS[display] : 1;
|
|
438
|
+
baseQuality = Math.round(baseQuality * displayFactor);
|
|
439
|
+
let quality = baseQuality;
|
|
440
|
+
let effort = 4;
|
|
441
|
+
let pngCompression = 6;
|
|
442
|
+
if (fileSizeInBytes) {
|
|
443
|
+
const sizeInKB = fileSizeInBytes / 1024;
|
|
444
|
+
if (sizeInKB > 500) {
|
|
445
|
+
quality = Math.min(baseQuality, 25);
|
|
446
|
+
effort = 9;
|
|
447
|
+
pngCompression = 9;
|
|
448
|
+
} else if (sizeInKB > 300) {
|
|
449
|
+
quality = Math.min(baseQuality, 30);
|
|
450
|
+
effort = 8;
|
|
451
|
+
pngCompression = 9;
|
|
452
|
+
} else if (sizeInKB > 150) {
|
|
453
|
+
quality = Math.min(baseQuality, 35);
|
|
454
|
+
effort = 7;
|
|
455
|
+
pngCompression = 8;
|
|
456
|
+
} else if (sizeInKB > 90) {
|
|
457
|
+
quality = Math.min(baseQuality, 40);
|
|
458
|
+
effort = 6;
|
|
459
|
+
pngCompression = 8;
|
|
460
|
+
} else if (sizeInKB > 50) {
|
|
461
|
+
quality = Math.min(baseQuality, 50);
|
|
462
|
+
effort = 5;
|
|
463
|
+
pngCompression = 7;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const dimensions = size && SIZE_PRESETS[size] ? SIZE_PRESETS[size] : void 0;
|
|
467
|
+
return {
|
|
468
|
+
quality: Math.max(1, Math.min(100, quality)),
|
|
469
|
+
effort,
|
|
470
|
+
pngCompression,
|
|
471
|
+
...dimensions && { width: dimensions.width, height: dimensions.height }
|
|
472
|
+
};
|
|
366
473
|
};
|
|
367
474
|
var objectIdSchema = z.string().refine((val) => isValidObjectId(val), {
|
|
368
475
|
message: "Invalid ObjectId format"
|
|
@@ -1310,6 +1417,46 @@ var driveDelete = async (source, options) => {
|
|
|
1310
1417
|
const owner = drive.owner;
|
|
1311
1418
|
await provider.delete([driveId], owner, accountId);
|
|
1312
1419
|
};
|
|
1420
|
+
var resolveFolderByPath = async (folderPath, owner, accountId) => {
|
|
1421
|
+
const normalizedPath = folderPath.replace(/^\/+|\/+$/g, "");
|
|
1422
|
+
if (!normalizedPath) {
|
|
1423
|
+
throw new Error("Folder path cannot be empty");
|
|
1424
|
+
}
|
|
1425
|
+
const segments = normalizedPath.split("/").filter((s) => s.length > 0);
|
|
1426
|
+
if (segments.length === 0) {
|
|
1427
|
+
throw new Error("Invalid folder path");
|
|
1428
|
+
}
|
|
1429
|
+
let providerName = "LOCAL";
|
|
1430
|
+
if (accountId && accountId !== "LOCAL") {
|
|
1431
|
+
const account = await drive_default.db.model("StorageAccount").findOne({ _id: accountId, owner });
|
|
1432
|
+
if (!account) {
|
|
1433
|
+
throw new Error("Invalid Storage Account");
|
|
1434
|
+
}
|
|
1435
|
+
if (account.metadata.provider === "GOOGLE") {
|
|
1436
|
+
providerName = "GOOGLE";
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
let currentParentId = null;
|
|
1440
|
+
for (const segmentName of segments) {
|
|
1441
|
+
const existingFolder = await drive_default.findOne({
|
|
1442
|
+
owner,
|
|
1443
|
+
"provider.type": providerName,
|
|
1444
|
+
storageAccountId: accountId || null,
|
|
1445
|
+
parentId: currentParentId,
|
|
1446
|
+
name: segmentName,
|
|
1447
|
+
"information.type": "FOLDER",
|
|
1448
|
+
trashedAt: null
|
|
1449
|
+
});
|
|
1450
|
+
if (existingFolder) {
|
|
1451
|
+
currentParentId = String(existingFolder._id);
|
|
1452
|
+
} else {
|
|
1453
|
+
const provider = providerName === "GOOGLE" ? GoogleDriveProvider : LocalStorageProvider;
|
|
1454
|
+
const newFolder = await provider.createFolder(segmentName, currentParentId, owner, accountId);
|
|
1455
|
+
currentParentId = newFolder.id;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
return currentParentId;
|
|
1459
|
+
};
|
|
1313
1460
|
var driveUpload = async (source, key, options) => {
|
|
1314
1461
|
const config = getDriveConfig();
|
|
1315
1462
|
let provider = LocalStorageProvider;
|
|
@@ -1399,12 +1546,20 @@ var driveUpload = async (source, key, options) => {
|
|
|
1399
1546
|
throw new Error("Storage quota exceeded");
|
|
1400
1547
|
}
|
|
1401
1548
|
}
|
|
1549
|
+
let resolvedParentId = null;
|
|
1550
|
+
if (options.folder?.path) {
|
|
1551
|
+
resolvedParentId = await resolveFolderByPath(options.folder.path, key, accountId);
|
|
1552
|
+
} else if (options.folder?.id && options.folder.id !== "root") {
|
|
1553
|
+
resolvedParentId = options.folder.id;
|
|
1554
|
+
} else if (options.parentId && options.parentId !== "root") {
|
|
1555
|
+
resolvedParentId = options.parentId;
|
|
1556
|
+
}
|
|
1402
1557
|
const drive = new drive_default({
|
|
1403
1558
|
owner: key,
|
|
1404
1559
|
storageAccountId: accountId || null,
|
|
1405
1560
|
provider: { type: provider.name },
|
|
1406
1561
|
name: options.name,
|
|
1407
|
-
parentId:
|
|
1562
|
+
parentId: resolvedParentId,
|
|
1408
1563
|
order: await getNextOrderValue(key),
|
|
1409
1564
|
information: { type: "FILE", sizeInBytes: fileSize, mime: mimeType, path: "" },
|
|
1410
1565
|
status: "UPLOADING"
|
|
@@ -1548,24 +1703,32 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1548
1703
|
return;
|
|
1549
1704
|
}
|
|
1550
1705
|
if (action === "serve") {
|
|
1551
|
-
const { stream, mime, size } = await itemProvider.openStream(drive, itemAccountId);
|
|
1706
|
+
const { stream, mime, size: fileSize } = await itemProvider.openStream(drive, itemAccountId);
|
|
1552
1707
|
const safeFilename = sanitizeContentDispositionFilename(drive.name);
|
|
1553
1708
|
const format = req.query.format;
|
|
1554
1709
|
const quality = req.query.quality;
|
|
1710
|
+
const display = req.query.display;
|
|
1711
|
+
const sizePreset = req.query.size;
|
|
1555
1712
|
const isImage = mime.startsWith("image/");
|
|
1556
|
-
const shouldTransform = isImage && (format || quality);
|
|
1713
|
+
const shouldTransform = isImage && (format || quality || display || sizePreset);
|
|
1557
1714
|
res.setHeader("Content-Disposition", `inline; filename="${safeFilename}"`);
|
|
1558
1715
|
if (config.cors?.enabled) {
|
|
1559
1716
|
res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
|
|
1560
1717
|
}
|
|
1561
1718
|
if (shouldTransform) {
|
|
1562
1719
|
try {
|
|
1563
|
-
const
|
|
1720
|
+
const settings = getImageSettings(fileSize, quality, display, sizePreset);
|
|
1564
1721
|
let targetFormat = format || mime.split("/")[1];
|
|
1565
1722
|
if (targetFormat === "jpg") targetFormat = "jpeg";
|
|
1566
1723
|
const cacheDir = path.join(config.storage.path, "file", drive._id.toString(), "cache");
|
|
1567
|
-
const
|
|
1568
|
-
|
|
1724
|
+
const cacheKey = [
|
|
1725
|
+
"opt",
|
|
1726
|
+
`q${settings.quality}`,
|
|
1727
|
+
`e${settings.effort}`,
|
|
1728
|
+
settings.width ? `${settings.width}x${settings.height}` : "orig",
|
|
1729
|
+
targetFormat
|
|
1730
|
+
].join("_");
|
|
1731
|
+
const cachePath = path.join(cacheDir, `${cacheKey}.bin`);
|
|
1569
1732
|
if (fs.existsSync(cachePath)) {
|
|
1570
1733
|
const cacheStat = fs.statSync(cachePath);
|
|
1571
1734
|
res.setHeader("Content-Type", `image/${targetFormat}`);
|
|
@@ -1579,18 +1742,24 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1579
1742
|
return;
|
|
1580
1743
|
}
|
|
1581
1744
|
if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
|
|
1582
|
-
|
|
1745
|
+
let pipeline = sharp();
|
|
1746
|
+
if (settings.width && settings.height) {
|
|
1747
|
+
pipeline = pipeline.resize(settings.width, settings.height, {
|
|
1748
|
+
fit: "inside",
|
|
1749
|
+
withoutEnlargement: true
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1583
1752
|
if (targetFormat === "jpeg") {
|
|
1584
|
-
pipeline.jpeg({ quality:
|
|
1753
|
+
pipeline = pipeline.jpeg({ quality: settings.quality, mozjpeg: true });
|
|
1585
1754
|
res.setHeader("Content-Type", "image/jpeg");
|
|
1586
1755
|
} else if (targetFormat === "png") {
|
|
1587
|
-
pipeline.png({
|
|
1756
|
+
pipeline = pipeline.png({ compressionLevel: settings.pngCompression, adaptiveFiltering: true });
|
|
1588
1757
|
res.setHeader("Content-Type", "image/png");
|
|
1589
1758
|
} else if (targetFormat === "webp") {
|
|
1590
|
-
pipeline.webp({ quality:
|
|
1759
|
+
pipeline = pipeline.webp({ quality: settings.quality, effort: settings.effort });
|
|
1591
1760
|
res.setHeader("Content-Type", "image/webp");
|
|
1592
1761
|
} else if (targetFormat === "avif") {
|
|
1593
|
-
pipeline.avif({ quality:
|
|
1762
|
+
pipeline = pipeline.avif({ quality: settings.quality, effort: settings.effort });
|
|
1594
1763
|
res.setHeader("Content-Type", "image/avif");
|
|
1595
1764
|
}
|
|
1596
1765
|
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
@@ -1603,7 +1772,7 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1603
1772
|
}
|
|
1604
1773
|
}
|
|
1605
1774
|
res.setHeader("Content-Type", mime);
|
|
1606
|
-
if (
|
|
1775
|
+
if (fileSize) res.setHeader("Content-Length", fileSize);
|
|
1607
1776
|
stream.pipe(res);
|
|
1608
1777
|
return;
|
|
1609
1778
|
}
|
|
@@ -2137,5 +2306,5 @@ var driveAPIHandler = async (req, res) => {
|
|
|
2137
2306
|
};
|
|
2138
2307
|
|
|
2139
2308
|
export { driveAPIHandler, driveConfiguration, driveDelete, driveFilePath, driveFileSchemaZod, driveGetUrl, driveInfo, driveList, driveReadFile, driveUpload, getDriveConfig, getDriveInformation };
|
|
2140
|
-
//# sourceMappingURL=chunk-
|
|
2141
|
-
//# sourceMappingURL=chunk-
|
|
2309
|
+
//# sourceMappingURL=chunk-OWKTTRQC.js.map
|
|
2310
|
+
//# sourceMappingURL=chunk-OWKTTRQC.js.map
|