@jrmc/adonis-attachment 2.2.0 → 2.3.1
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 +40 -21
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/providers/attachment_provider.js +1 -2
- package/build/src/adapters/exif.js +6 -2
- package/build/src/attachment_manager.d.ts +2 -2
- package/build/src/attachment_manager.js +26 -42
- package/build/src/converters/document_thumbnail_converter.d.ts +13 -0
- package/build/src/converters/document_thumbnail_converter.js +60 -0
- package/build/src/converters/image_converter.js +6 -2
- package/build/src/converters/pdf_thumbnail_converter.d.ts +13 -0
- package/build/src/converters/pdf_thumbnail_converter.js +48 -0
- package/build/src/converters/video_thumbnail_converter.d.ts +1 -1
- package/build/src/converters/video_thumbnail_converter.js +12 -4
- package/build/src/errors.d.ts +62 -0
- package/build/src/errors.js +56 -0
- package/build/src/mixins/attachmentable.d.ts +1 -1
- package/build/src/types/config.d.ts +2 -0
- package/build/src/utils/helpers.d.ts +2 -0
- package/build/src/utils/helpers.js +29 -4
- package/package.json +1 -2
- package/build/src/converters/document_converter.d.ts +0 -7
- package/build/src/converters/document_converter.js +0 -7
- package/build/src/converters/pdf_converter.d.ts +0 -7
- package/build/src/converters/pdf_converter.js +0 -7
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ Project sample : [adonis-starter-kit](https://github.com/batosai/adonis-starter-
|
|
|
8
8
|
|
|
9
9
|
[View documentation](https://adonis-attachment.jrmc.dev/)
|
|
10
10
|
|
|
11
|
+
[Discord](https://discord.gg/89eMn2vB)
|
|
12
|
+
|
|
11
13
|
[ChangeLog](https://adonis-attachment.jrmc.dev/changelog.html)
|
|
12
14
|
|
|
13
15
|
⚠️ [Breaking change](https://adonis-attachment.jrmc.dev/changelog.html) version 2, include [@adonisjs/drive](https://docs.adonisjs.com/guides/digging-deeper/drive)
|
|
@@ -18,44 +20,61 @@ Project sample : [adonis-starter-kit](https://github.com/batosai/adonis-starter-
|
|
|
18
20
|
- [x] save meta data
|
|
19
21
|
- [x] variantes
|
|
20
22
|
- [x] images
|
|
21
|
-
- [
|
|
23
|
+
- [x] documents thumbnail
|
|
22
24
|
- [x] videos thumbnail
|
|
23
25
|
- [ ] command regenerate
|
|
26
|
+
- [ ] command make:convert
|
|
24
27
|
- [x] adonis-drive/flydrive
|
|
25
28
|
- [ ] jobs queue
|
|
26
29
|
- [x] serialize
|
|
27
30
|
|
|
28
|
-
### Meta data list (if available)
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
- size
|
|
32
|
-
- dimension (width, height)
|
|
33
|
-
- created date
|
|
34
|
-
- orientation
|
|
35
|
-
- mime type
|
|
36
|
-
- gps
|
|
32
|
+
## Setup
|
|
37
33
|
|
|
38
|
-
|
|
34
|
+
Install and configure the package:
|
|
39
35
|
|
|
40
|
-
|
|
36
|
+
```sh
|
|
37
|
+
node ace add @jrmc/adonis-attachment
|
|
38
|
+
```
|
|
41
39
|
|
|
42
|
-
|
|
40
|
+
## Sample
|
|
43
41
|
|
|
44
|
-
|
|
42
|
+
Simple upload file
|
|
45
43
|
|
|
46
|
-
|
|
44
|
+
```ts
|
|
45
|
+
// app/models/user.ts
|
|
46
|
+
import { BaseModel } from '@adonisjs/lucid/orm'
|
|
47
|
+
import { compose } from '@adonisjs/core/helpers'
|
|
48
|
+
import { attachment, Attachmentable } from '@jrmc/adonis-attachment'
|
|
49
|
+
import type { Attachment } from '@jrmc/adonis-attachment/types/attachment' // [!code highlight]
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
class User extends compose(BaseModel, Attachmentable) { // [!code highlight]
|
|
52
|
+
@attachment() // [!code highlight]
|
|
53
|
+
declare avatar: Attachment // [!code highlight]
|
|
54
|
+
}
|
|
55
|
+
```
|
|
49
56
|
|
|
50
|
-
|
|
57
|
+
---
|
|
51
58
|
|
|
52
|
-
|
|
59
|
+
```ts
|
|
60
|
+
// app/controllers/users_controller.ts
|
|
61
|
+
import { attachmentManager } from '@jrmc/adonis-attachment' // [!code focus]
|
|
53
62
|
|
|
54
|
-
|
|
63
|
+
class UsersController {
|
|
64
|
+
public store({ request }: HttpContext) {
|
|
65
|
+
const avatar = request.file('avatar')! // [!code focus]
|
|
66
|
+
const user = new User()
|
|
55
67
|
|
|
56
|
-
|
|
68
|
+
user.avatar = await attachmentManager.createFromFile(avatar) // [!code focus]
|
|
69
|
+
await user.save()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
57
73
|
|
|
58
|
-
|
|
59
|
-
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
```edge
|
|
77
|
+
<img src="{{ await user.avatar.getUrl() }}" loading="lazy" alt="" />
|
|
60
78
|
```
|
|
61
79
|
|
|
80
|
+
Read [documentation](https://adonis-attachment.jrmc.dev/) for advanced usage(thumbnail video/pdf/doc, create from buffer/base64...)
|
package/build/index.d.ts
CHANGED
|
@@ -4,4 +4,5 @@ export { Attachment } from './src/attachments/attachment.js';
|
|
|
4
4
|
export { attachment } from './src/decorators/attachment.js';
|
|
5
5
|
export { defineConfig } from './src/define_config.js';
|
|
6
6
|
export { Attachmentable } from './src/mixins/attachmentable.js';
|
|
7
|
+
export * as errors from './src/errors.js';
|
|
7
8
|
export { attachmentManager };
|
package/build/index.js
CHANGED
|
@@ -4,4 +4,5 @@ export { Attachment } from './src/attachments/attachment.js';
|
|
|
4
4
|
export { attachment } from './src/decorators/attachment.js';
|
|
5
5
|
export { defineConfig } from './src/define_config.js';
|
|
6
6
|
export { Attachmentable } from './src/mixins/attachmentable.js';
|
|
7
|
+
export * as errors from './src/errors.js';
|
|
7
8
|
export { attachmentManager };
|
|
@@ -17,12 +17,11 @@ export default class AttachmentProvider {
|
|
|
17
17
|
const { AttachmentManager } = await import('../src/attachment_manager.js');
|
|
18
18
|
const attachmentConfig = this.app.config.get('attachment');
|
|
19
19
|
const config = await configProvider.resolve(this.app, attachmentConfig);
|
|
20
|
-
const logger = await this.app.container.make('logger');
|
|
21
20
|
const drive = await this.app.container.make('drive.manager');
|
|
22
21
|
if (!config) {
|
|
23
22
|
throw new RuntimeException('Invalid config exported from "config/attachment.ts" file. Make sure to use the defineConfig method');
|
|
24
23
|
}
|
|
25
|
-
this.#manager = new AttachmentManager(config,
|
|
24
|
+
this.#manager = new AttachmentManager(config, drive);
|
|
26
25
|
return this.#manager;
|
|
27
26
|
});
|
|
28
27
|
}
|
|
@@ -8,7 +8,7 @@ import fs from 'node:fs/promises';
|
|
|
8
8
|
import ExifReader from 'exifreader';
|
|
9
9
|
import logger from '@adonisjs/core/services/logger';
|
|
10
10
|
import { fileTypeFromBuffer, fileTypeFromFile } from 'file-type';
|
|
11
|
-
import { cleanObject, use } from '../utils/helpers.js';
|
|
11
|
+
import { bufferToTempFile, cleanObject, use } from '../utils/helpers.js';
|
|
12
12
|
import { attachmentManager } from '../../index.js';
|
|
13
13
|
export const exif = async (input) => {
|
|
14
14
|
let fileType;
|
|
@@ -106,7 +106,11 @@ async function imageExif(buffer) {
|
|
|
106
106
|
async function videoExif(input) {
|
|
107
107
|
return new Promise(async (resolve) => {
|
|
108
108
|
const ffmpeg = await use('fluent-ffmpeg');
|
|
109
|
-
|
|
109
|
+
let file = input;
|
|
110
|
+
if (Buffer.isBuffer(input)) {
|
|
111
|
+
file = await bufferToTempFile(input);
|
|
112
|
+
}
|
|
113
|
+
const ff = ffmpeg(file);
|
|
110
114
|
const config = attachmentManager.getConfig();
|
|
111
115
|
if (config.bin) {
|
|
112
116
|
if (config.bin.ffprobePath) {
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
6
|
*/
|
|
7
|
-
import type { LoggerService } from '@adonisjs/core/types';
|
|
8
7
|
import type { DriveService, SignedURLOptions } from '@adonisjs/drive/types';
|
|
9
8
|
import type { MultipartFile } from '@adonisjs/core/bodyparser';
|
|
10
9
|
import type { AttachmentBase, Attachment as AttachmentType } from './types/attachment.js';
|
|
@@ -13,11 +12,12 @@ import { Attachment } from './attachments/attachment.js';
|
|
|
13
12
|
import Converter from './converters/converter.js';
|
|
14
13
|
export declare class AttachmentManager {
|
|
15
14
|
#private;
|
|
16
|
-
constructor(config: ResolvedAttachmentConfig,
|
|
15
|
+
constructor(config: ResolvedAttachmentConfig, drive: DriveService);
|
|
17
16
|
getConfig(): ResolvedAttachmentConfig;
|
|
18
17
|
createFromDbResponse(response: any): Attachment | null;
|
|
19
18
|
createFromFile(file: MultipartFile): Promise<Attachment>;
|
|
20
19
|
createFromBuffer(buffer: Buffer, name?: string): Promise<Attachment>;
|
|
20
|
+
createFromBase64(data: string, name?: string): Promise<Attachment>;
|
|
21
21
|
getConverter(key: string): Promise<void | Converter>;
|
|
22
22
|
computeUrl(attachment: AttachmentType | AttachmentBase, signedUrlOptions?: SignedURLOptions): Promise<void>;
|
|
23
23
|
preComputeUrl(attachment: AttachmentType): Promise<void>;
|
|
@@ -4,17 +4,15 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
6
|
*/
|
|
7
|
-
import
|
|
7
|
+
import * as errors from './errors.js';
|
|
8
8
|
import { Attachment } from './attachments/attachment.js';
|
|
9
|
-
import { createAttachmentAttributes } from './utils/helpers.js';
|
|
9
|
+
import { createAttachmentAttributes, isBase64 } from './utils/helpers.js';
|
|
10
10
|
import { exif } from './adapters/exif.js';
|
|
11
11
|
const REQUIRED_ATTRIBUTES = ['name', 'size', 'extname', 'mimeType'];
|
|
12
12
|
export class AttachmentManager {
|
|
13
|
-
#logger;
|
|
14
13
|
#config;
|
|
15
14
|
#drive;
|
|
16
|
-
constructor(config,
|
|
17
|
-
this.#logger = logger;
|
|
15
|
+
constructor(config, drive) {
|
|
18
16
|
this.#drive = drive;
|
|
19
17
|
this.#config = config;
|
|
20
18
|
}
|
|
@@ -28,7 +26,7 @@ export class AttachmentManager {
|
|
|
28
26
|
const attributes = typeof response === 'string' ? JSON.parse(response) : response;
|
|
29
27
|
REQUIRED_ATTRIBUTES.forEach((attribute) => {
|
|
30
28
|
if (attributes[attribute] === undefined) {
|
|
31
|
-
throw new
|
|
29
|
+
throw new errors.E_CANNOT_CREATE_ATTACHMENT([attribute]);
|
|
32
30
|
}
|
|
33
31
|
});
|
|
34
32
|
return new Attachment(this.#drive, attributes);
|
|
@@ -41,14 +39,25 @@ export class AttachmentManager {
|
|
|
41
39
|
size: file.size,
|
|
42
40
|
};
|
|
43
41
|
if (!file.tmpPath) {
|
|
44
|
-
throw new
|
|
42
|
+
throw new errors.ENOENT();
|
|
45
43
|
}
|
|
46
44
|
return new Attachment(this.#drive, attributes, file.tmpPath);
|
|
47
45
|
}
|
|
48
46
|
async createFromBuffer(buffer, name) {
|
|
47
|
+
if (!Buffer.isBuffer(buffer)) {
|
|
48
|
+
throw new errors.E_ISNOT_BUFFER();
|
|
49
|
+
}
|
|
49
50
|
const attributes = await createAttachmentAttributes(buffer, name);
|
|
50
51
|
return new Attachment(this.#drive, attributes, buffer);
|
|
51
52
|
}
|
|
53
|
+
async createFromBase64(data, name) {
|
|
54
|
+
const base64Data = data.replace(/^data:([A-Za-z-+\/]+);base64,/, '');
|
|
55
|
+
if (!isBase64(base64Data)) {
|
|
56
|
+
throw new errors.E_ISNOT_BASE64();
|
|
57
|
+
}
|
|
58
|
+
const buffer = Buffer.from(base64Data, 'base64');
|
|
59
|
+
return await this.createFromBuffer(buffer, name);
|
|
60
|
+
}
|
|
52
61
|
async getConverter(key) {
|
|
53
62
|
if (this.#config.converters) {
|
|
54
63
|
for (const c of this.#config.converters) {
|
|
@@ -89,47 +98,22 @@ export class AttachmentManager {
|
|
|
89
98
|
else {
|
|
90
99
|
attachment.meta = undefined;
|
|
91
100
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
await attachment.getDisk().put(destinationPath, attachment.input);
|
|
95
|
-
}
|
|
96
|
-
else if (attachment.input) {
|
|
97
|
-
await attachment.getDisk().copyFromFs(attachment.input, destinationPath);
|
|
98
|
-
}
|
|
101
|
+
if (Buffer.isBuffer(attachment.input)) {
|
|
102
|
+
await attachment.getDisk().put(destinationPath, attachment.input);
|
|
99
103
|
}
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
else if (attachment.input) {
|
|
105
|
+
await attachment.getDisk().copyFromFs(attachment.input, destinationPath);
|
|
102
106
|
}
|
|
103
107
|
}
|
|
104
108
|
async delete(attachment) {
|
|
105
109
|
if (attachment.path) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (accessError.code === 'ENOENT') {
|
|
113
|
-
this.#logger.warn(`File not found: ${filePath}`);
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
throw accessError;
|
|
117
|
-
}
|
|
110
|
+
const filePath = attachment.path;
|
|
111
|
+
await attachment.getDisk().delete(filePath);
|
|
112
|
+
if (attachment instanceof Attachment) {
|
|
113
|
+
if (attachment.variants) {
|
|
114
|
+
const variantPath = attachment.variants[0].folder;
|
|
115
|
+
await attachment.getDisk().deleteAll(variantPath);
|
|
118
116
|
}
|
|
119
|
-
if (attachment instanceof Attachment) {
|
|
120
|
-
if (attachment.variants) {
|
|
121
|
-
const variantPath = attachment.variants[0].folder;
|
|
122
|
-
try {
|
|
123
|
-
await attachment.getDisk().deleteAll(variantPath);
|
|
124
|
-
}
|
|
125
|
-
catch (rmError) {
|
|
126
|
-
this.#logger.error(`Failed to remove variants folder: ${rmError.message}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
catch (error) {
|
|
132
|
-
this.#logger.error(error);
|
|
133
117
|
}
|
|
134
118
|
}
|
|
135
119
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jrmc/adonis-attachment
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
+
*/
|
|
7
|
+
import type { ConverterAttributes } from '../types/converter.js';
|
|
8
|
+
import type { Input } from '../types/input.js';
|
|
9
|
+
import Converter from './converter.js';
|
|
10
|
+
export default class DocumentThumbnailConverter extends Converter {
|
|
11
|
+
handle({ input, options }: ConverterAttributes): Promise<any>;
|
|
12
|
+
documentToImage(LibreOfficeFileConverter: any, input: Input): Promise<any>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jrmc/adonis-attachment
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
+
*/
|
|
7
|
+
import logger from '@adonisjs/core/services/logger';
|
|
8
|
+
import Converter from './converter.js';
|
|
9
|
+
import ImageConverter from './image_converter.js';
|
|
10
|
+
import { use } from '../utils/helpers.js';
|
|
11
|
+
export default class DocumentThumbnailConverter extends Converter {
|
|
12
|
+
async handle({ input, options }) {
|
|
13
|
+
try {
|
|
14
|
+
const lib = await use('libreoffice-file-converter');
|
|
15
|
+
const LibreOfficeFileConverter = lib.LibreOfficeFileConverter;
|
|
16
|
+
const outputBuffer = await this.documentToImage(LibreOfficeFileConverter, input);
|
|
17
|
+
if (options && outputBuffer) {
|
|
18
|
+
const converter = new ImageConverter();
|
|
19
|
+
return await converter.handle({
|
|
20
|
+
input: outputBuffer,
|
|
21
|
+
options
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return outputBuffer;
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
logger.error({ err });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async documentToImage(LibreOfficeFileConverter, input) {
|
|
31
|
+
let binaryPaths = undefined;
|
|
32
|
+
if (this.binPaths && this.binPaths.libreofficePaths) {
|
|
33
|
+
binaryPaths = this.binPaths.libreofficePaths;
|
|
34
|
+
}
|
|
35
|
+
const libreOfficeFileConverter = new LibreOfficeFileConverter({
|
|
36
|
+
childProcessOptions: {
|
|
37
|
+
timeout: 60 * 1000,
|
|
38
|
+
},
|
|
39
|
+
binaryPaths
|
|
40
|
+
});
|
|
41
|
+
if (Buffer.isBuffer(input)) {
|
|
42
|
+
const output = await libreOfficeFileConverter.convert({
|
|
43
|
+
buffer: input,
|
|
44
|
+
input: 'buffer',
|
|
45
|
+
output: 'buffer',
|
|
46
|
+
format: 'jpeg',
|
|
47
|
+
});
|
|
48
|
+
return output;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const output = await libreOfficeFileConverter.convert({
|
|
52
|
+
inputPath: input,
|
|
53
|
+
input: 'file',
|
|
54
|
+
output: 'buffer',
|
|
55
|
+
format: 'jpeg',
|
|
56
|
+
});
|
|
57
|
+
return output;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
6
|
*/
|
|
7
|
+
import logger from '@adonisjs/core/services/logger';
|
|
7
8
|
import Converter from './converter.js';
|
|
8
9
|
import { use } from '../utils/helpers.js';
|
|
9
10
|
export default class ImageConverter extends Converter {
|
|
10
11
|
async handle({ input, options }) {
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
try {
|
|
13
|
+
const sharp = await use('sharp');
|
|
13
14
|
const resize = options?.resize || {};
|
|
14
15
|
let format = options?.format || 'webp';
|
|
15
16
|
let formatoptions = {};
|
|
@@ -24,5 +25,8 @@ export default class ImageConverter extends Converter {
|
|
|
24
25
|
.toBuffer();
|
|
25
26
|
return buffer;
|
|
26
27
|
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
logger.error({ err });
|
|
30
|
+
}
|
|
27
31
|
}
|
|
28
32
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jrmc/adonis-attachment
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
+
*/
|
|
7
|
+
import type { ConverterAttributes } from '../types/converter.js';
|
|
8
|
+
import type { Input } from '../types/input.js';
|
|
9
|
+
import Converter from './converter.js';
|
|
10
|
+
export default class PdfThumbnailConverter extends Converter {
|
|
11
|
+
handle({ input, options }: ConverterAttributes): Promise<any>;
|
|
12
|
+
pdfToImage(Poppler: any, input: Input): Promise<string>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jrmc/adonis-attachment
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
+
*/
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { cuid } from '@adonisjs/core/helpers';
|
|
10
|
+
import logger from '@adonisjs/core/services/logger';
|
|
11
|
+
import Converter from './converter.js';
|
|
12
|
+
import ImageConverter from './image_converter.js';
|
|
13
|
+
import { use } from '../utils/helpers.js';
|
|
14
|
+
export default class PdfThumbnailConverter extends Converter {
|
|
15
|
+
async handle({ input, options }) {
|
|
16
|
+
try {
|
|
17
|
+
const nodePoppler = await use('node-poppler');
|
|
18
|
+
const Poppler = nodePoppler.Poppler;
|
|
19
|
+
const filePath = await this.pdfToImage(Poppler, input);
|
|
20
|
+
if (options && filePath) {
|
|
21
|
+
const converter = new ImageConverter();
|
|
22
|
+
return await converter.handle({
|
|
23
|
+
input: filePath,
|
|
24
|
+
options
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return filePath;
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
logger.error({ err });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async pdfToImage(Poppler, input) {
|
|
34
|
+
let binPath = null;
|
|
35
|
+
if (this.binPaths && this.binPaths.pdftocairoBasePath) {
|
|
36
|
+
binPath = this.binPaths.pdftocairoBasePath;
|
|
37
|
+
}
|
|
38
|
+
const poppler = new Poppler(binPath);
|
|
39
|
+
const options = {
|
|
40
|
+
// firstPageToConvert: 1,
|
|
41
|
+
lastPageToConvert: 1,
|
|
42
|
+
jpegFile: true,
|
|
43
|
+
};
|
|
44
|
+
const filePath = path.join(os.tmpdir(), cuid());
|
|
45
|
+
await poppler.pdfToCairo(input, filePath, options);
|
|
46
|
+
return filePath + '-1.jpg';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
6
|
*/
|
|
7
7
|
import type { ConverterAttributes } from '../types/converter.js';
|
|
8
|
+
import type { Input } from '../types/input.js';
|
|
8
9
|
import Converter from './converter.js';
|
|
9
|
-
import { Input } from '../types/input.js';
|
|
10
10
|
export default class VideoThumbnailConvert extends Converter {
|
|
11
11
|
handle({ input, options }: ConverterAttributes): Promise<any>;
|
|
12
12
|
videoToImage(ffmpeg: Function, input: Input): Promise<string | false>;
|
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { cuid } from '@adonisjs/core/helpers';
|
|
10
|
+
import logger from '@adonisjs/core/services/logger';
|
|
10
11
|
import Converter from './converter.js';
|
|
11
12
|
import ImageConverter from './image_converter.js';
|
|
12
|
-
import { use } from '../utils/helpers.js';
|
|
13
|
+
import { bufferToTempFile, use } from '../utils/helpers.js';
|
|
13
14
|
export default class VideoThumbnailConvert extends Converter {
|
|
14
15
|
async handle({ input, options }) {
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
try {
|
|
17
|
+
const ffmpeg = await use('fluent-ffmpeg');
|
|
17
18
|
const filePath = await this.videoToImage(ffmpeg, input);
|
|
18
19
|
if (options && filePath) {
|
|
19
20
|
const converter = new ImageConverter();
|
|
@@ -26,12 +27,19 @@ export default class VideoThumbnailConvert extends Converter {
|
|
|
26
27
|
return filePath;
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
logger.error({ err });
|
|
32
|
+
}
|
|
29
33
|
}
|
|
30
34
|
async videoToImage(ffmpeg, input) {
|
|
35
|
+
let file = input;
|
|
36
|
+
if (Buffer.isBuffer(input)) {
|
|
37
|
+
file = await bufferToTempFile(input);
|
|
38
|
+
}
|
|
31
39
|
return new Promise((resolve) => {
|
|
32
40
|
const folder = os.tmpdir();
|
|
33
41
|
const filename = `${cuid()}.png`;
|
|
34
|
-
const ff = ffmpeg(
|
|
42
|
+
const ff = ffmpeg(file);
|
|
35
43
|
if (this.binPaths) {
|
|
36
44
|
if (this.binPaths.ffmpegPath) {
|
|
37
45
|
ff.setFfmpegPath(this.binPaths.ffmpegPath);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jrmc/adonis-attachment
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Unable to write file to the destination
|
|
9
|
+
*/
|
|
10
|
+
export declare const E_CANNOT_WRITE_FILE: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
11
|
+
/**
|
|
12
|
+
* Unable to read file
|
|
13
|
+
*/
|
|
14
|
+
export declare const E_CANNOT_READ_FILE: new (args: [key: string], options?: ErrorOptions) => import(
|
|
15
|
+
/**
|
|
16
|
+
* Unable to read file
|
|
17
|
+
*/
|
|
18
|
+
"@adonisjs/core/exceptions").Exception;
|
|
19
|
+
/**
|
|
20
|
+
* Unable to delete file
|
|
21
|
+
*/
|
|
22
|
+
export declare const E_CANNOT_DELETE_FILE: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
23
|
+
/**
|
|
24
|
+
* Unable to set file visibility
|
|
25
|
+
*/
|
|
26
|
+
export declare const E_CANNOT_SET_VISIBILITY: new (args: [key: string], options?: ErrorOptions) => import(
|
|
27
|
+
/**
|
|
28
|
+
* Unable to read file
|
|
29
|
+
*/
|
|
30
|
+
"@adonisjs/core/exceptions").Exception;
|
|
31
|
+
/**
|
|
32
|
+
* Unable to generate URL for a file
|
|
33
|
+
*/
|
|
34
|
+
export declare const E_CANNOT_GENERATE_URL: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
35
|
+
/**
|
|
36
|
+
* The file key has unallowed set of characters
|
|
37
|
+
*/
|
|
38
|
+
export declare const E_UNALLOWED_CHARACTERS: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
39
|
+
/**
|
|
40
|
+
* Key post normalization leads to an empty string
|
|
41
|
+
*/
|
|
42
|
+
export declare const E_INVALID_KEY: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
43
|
+
/**
|
|
44
|
+
* Missing package
|
|
45
|
+
*/
|
|
46
|
+
export declare const E_MISSING_PACKAGE: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
47
|
+
/**
|
|
48
|
+
* Unable to create Attachment Object
|
|
49
|
+
*/
|
|
50
|
+
export declare const E_CANNOT_CREATE_ATTACHMENT: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
51
|
+
/**
|
|
52
|
+
* Is not a Buffer
|
|
53
|
+
*/
|
|
54
|
+
export declare const E_ISNOT_BUFFER: new (args?: any, options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
55
|
+
/**
|
|
56
|
+
* Is not a Base64
|
|
57
|
+
*/
|
|
58
|
+
export declare const E_ISNOT_BASE64: new (args?: any, options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
59
|
+
/**
|
|
60
|
+
* Unable to read file
|
|
61
|
+
*/
|
|
62
|
+
export declare const ENOENT: new (args?: any, options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jrmc/adonis-attachment
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
+
*/
|
|
7
|
+
import { createError } from '@adonisjs/core/exceptions';
|
|
8
|
+
import { errors } from 'flydrive';
|
|
9
|
+
/**
|
|
10
|
+
* Unable to write file to the destination
|
|
11
|
+
*/
|
|
12
|
+
export const E_CANNOT_WRITE_FILE = errors.E_CANNOT_WRITE_FILE;
|
|
13
|
+
/**
|
|
14
|
+
* Unable to read file
|
|
15
|
+
*/
|
|
16
|
+
export const E_CANNOT_READ_FILE = errors.E_CANNOT_READ_FILE;
|
|
17
|
+
/**
|
|
18
|
+
* Unable to delete file
|
|
19
|
+
*/
|
|
20
|
+
export const E_CANNOT_DELETE_FILE = errors.E_CANNOT_DELETE_FILE;
|
|
21
|
+
/**
|
|
22
|
+
* Unable to set file visibility
|
|
23
|
+
*/
|
|
24
|
+
export const E_CANNOT_SET_VISIBILITY = errors.E_CANNOT_SET_VISIBILITY;
|
|
25
|
+
/**
|
|
26
|
+
* Unable to generate URL for a file
|
|
27
|
+
*/
|
|
28
|
+
export const E_CANNOT_GENERATE_URL = errors.E_CANNOT_GENERATE_URL;
|
|
29
|
+
/**
|
|
30
|
+
* The file key has unallowed set of characters
|
|
31
|
+
*/
|
|
32
|
+
export const E_UNALLOWED_CHARACTERS = errors.E_UNALLOWED_CHARACTERS;
|
|
33
|
+
/**
|
|
34
|
+
* Key post normalization leads to an empty string
|
|
35
|
+
*/
|
|
36
|
+
export const E_INVALID_KEY = errors.E_INVALID_KEY;
|
|
37
|
+
/**
|
|
38
|
+
* Missing package
|
|
39
|
+
*/
|
|
40
|
+
export const E_MISSING_PACKAGE = createError('Missing package, please install "%s"', 'E_MISSING_PACKAGE');
|
|
41
|
+
/**
|
|
42
|
+
* Unable to create Attachment Object
|
|
43
|
+
*/
|
|
44
|
+
export const E_CANNOT_CREATE_ATTACHMENT = createError('Cannot create attachment from database response. Missing attribute "%s"', 'E_CANNOT_CREATE_ATTACHMENT');
|
|
45
|
+
/**
|
|
46
|
+
* Is not a Buffer
|
|
47
|
+
*/
|
|
48
|
+
export const E_ISNOT_BUFFER = createError('Is not a Buffer', 'E_ISNOT_BUFFER');
|
|
49
|
+
/**
|
|
50
|
+
* Is not a Base64
|
|
51
|
+
*/
|
|
52
|
+
export const E_ISNOT_BASE64 = createError('Is not a Base64', 'E_ISNOT_BASE64');
|
|
53
|
+
/**
|
|
54
|
+
* Unable to read file
|
|
55
|
+
*/
|
|
56
|
+
export const ENOENT = createError('File not found', 'ENOENT');
|
|
@@ -369,7 +369,6 @@ export declare const Attachmentable: <Model extends NormalizeConstructor<typeof
|
|
|
369
369
|
related<Name_2 extends undefined>(relation: Name_2): any[Name_2] extends import("@adonisjs/lucid/types/relations").ModelRelations<import("@adonisjs/lucid/types/model").LucidModel, import("@adonisjs/lucid/types/model").LucidModel> ? any[Name_2]["client"] : never;
|
|
370
370
|
}): Promise<void>;
|
|
371
371
|
find: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, value: any, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions) => Promise<null | InstanceType<T>>;
|
|
372
|
-
create: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>, options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>>;
|
|
373
372
|
readonly booted: boolean;
|
|
374
373
|
$columnsDefinitions: Map<string, import("@adonisjs/lucid/types/model").ModelColumnOptions>;
|
|
375
374
|
$relationsDefinitions: Map<string, import("@adonisjs/lucid/types/relations").RelationshipsContract>;
|
|
@@ -416,6 +415,7 @@ export declare const Attachmentable: <Model extends NormalizeConstructor<typeof
|
|
|
416
415
|
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel>(this: Model_1, event: "paginate", handler: import("@adonisjs/lucid/types/model").HooksHandler<import("@adonisjs/lucid/types/model").ModelPaginatorContract<InstanceType<Model_1>>, "paginate">): void;
|
|
417
416
|
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event_2 extends import("@adonisjs/lucid/types/model").EventsList>(this: Model_1, event: Event_2, handler: import("@adonisjs/lucid/types/model").HooksHandler<InstanceType<Model_1>, Event_2>): void;
|
|
418
417
|
};
|
|
418
|
+
create: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>, options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>>;
|
|
419
419
|
createMany: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>[], options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>[]>;
|
|
420
420
|
findOrFail: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, value: any, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions) => Promise<InstanceType<T>>;
|
|
421
421
|
findBy: {
|
|
@@ -18,3 +18,5 @@ export declare function createAttachmentAttributes(input: Input, name?: string):
|
|
|
18
18
|
export declare function cleanObject(obj: any): any;
|
|
19
19
|
export declare function clone(object: Object): any;
|
|
20
20
|
export declare function use(module: string): Promise<any>;
|
|
21
|
+
export declare function bufferToTempFile(input: Buffer): Promise<string>;
|
|
22
|
+
export declare function isBase64(str: string): boolean;
|
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
6
|
*/
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import fs from 'fs/promises';
|
|
7
10
|
import { cuid } from '@adonisjs/core/helpers';
|
|
8
11
|
import string from '@adonisjs/core/helpers/string';
|
|
9
|
-
import logger from '@adonisjs/core/services/logger';
|
|
10
12
|
import { fileTypeFromBuffer, fileTypeFromFile } from 'file-type';
|
|
11
13
|
import { Attachment } from '../attachments/attachment.js';
|
|
14
|
+
import * as errors from '../errors.js';
|
|
12
15
|
import { optionsSym } from './symbols.js';
|
|
13
16
|
export function getAttachmentAttributeNames(modelInstance) {
|
|
14
17
|
return Object.keys(modelInstance.$attributes).filter((attr) => modelInstance.$attributes[attr] instanceof Attachment);
|
|
@@ -61,9 +64,31 @@ export function clone(object) {
|
|
|
61
64
|
export async function use(module) {
|
|
62
65
|
try {
|
|
63
66
|
const result = await import(module);
|
|
64
|
-
|
|
67
|
+
if (result.default) {
|
|
68
|
+
return result.default;
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
throw new errors.E_MISSING_PACKAGE([module], { cause: err });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export async function bufferToTempFile(input) {
|
|
77
|
+
const folder = os.tmpdir();
|
|
78
|
+
const tempFilePath = path.join(folder, `tempfile-${Date.now()}.tmp`);
|
|
79
|
+
await fs.writeFile(tempFilePath, input);
|
|
80
|
+
return tempFilePath;
|
|
81
|
+
}
|
|
82
|
+
export function isBase64(str) {
|
|
83
|
+
const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
84
|
+
if (!base64Regex.test(str)) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
Buffer.from(str, 'base64').toString();
|
|
89
|
+
return true;
|
|
65
90
|
}
|
|
66
|
-
catch (
|
|
67
|
-
|
|
91
|
+
catch (err) {
|
|
92
|
+
return false;
|
|
68
93
|
}
|
|
69
94
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jrmc/adonis-attachment",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Turn any field on your Lucid model to an attachment data type",
|
|
6
6
|
"engines": {
|
|
@@ -56,7 +56,6 @@
|
|
|
56
56
|
"./attachment_provider": "./build/providers/attachment_provider.js"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@poppinss/utils": "^6.7.3",
|
|
60
59
|
"exifreader": "^4.23.3",
|
|
61
60
|
"file-type": "^19.4.0"
|
|
62
61
|
},
|