@jrmc/adonis-attachment 1.0.1 → 1.1.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 +18 -3
- package/build/src/adapters/exif.d.ts +2 -3
- package/build/src/adapters/exif.js +46 -2
- package/build/src/converters/autodetect_converter.d.ts +11 -0
- package/build/src/converters/autodetect_converter.js +34 -0
- package/build/src/converters/video_thumbnail_converter.d.ts +13 -0
- package/build/src/converters/video_thumbnail_converter.js +53 -0
- package/build/src/mixins/attachmentable.d.ts +3 -3
- package/build/src/utils/helpers.d.ts +1 -1
- package/build/src/utils/helpers.js +2 -6
- package/build/stubs/config.stub +8 -0
- package/package.json +1 -1
- package/build/src/converters/video_converter.d.ts +0 -7
- package/build/src/converters/video_converter.js +0 -7
package/README.md
CHANGED
|
@@ -10,8 +10,8 @@ Project sample : [adonis-starter-kit](https://github.com/batosai/adonis-starter-
|
|
|
10
10
|
- [x] save meta data
|
|
11
11
|
- [x] variantes
|
|
12
12
|
- [x] images
|
|
13
|
-
- [ ] documents
|
|
14
|
-
- [
|
|
13
|
+
- [ ] documents thumbnail
|
|
14
|
+
- [x] videos thumbnail
|
|
15
15
|
- [ ] command regenerate
|
|
16
16
|
- [ ] adonis-drive/flydrive
|
|
17
17
|
- [ ] jobs queue
|
|
@@ -227,11 +227,19 @@ export default defineConfig({
|
|
|
227
227
|
|
|
228
228
|
}
|
|
229
229
|
},
|
|
230
|
+
{
|
|
231
|
+
key: 'preview',
|
|
232
|
+
converter: () => import('@jrmc/adonis-attachment/converters/video_thumbnail_converter'),
|
|
233
|
+
options: {
|
|
234
|
+
format: 'jpeg',
|
|
235
|
+
resize: 720
|
|
236
|
+
}
|
|
237
|
+
},
|
|
230
238
|
]
|
|
231
239
|
})
|
|
232
240
|
```
|
|
233
241
|
|
|
234
|
-
|
|
242
|
+
Variants images are generates by [sharp module](https://sharp.pixelplumbing.com)
|
|
235
243
|
|
|
236
244
|
Options resize is `number` or `object`(options) details in documentation : [sharp api resize](https://sharp.pixelplumbing.com/api-resize)
|
|
237
245
|
|
|
@@ -240,3 +248,10 @@ Options format is `string` or `object` [ format, options ] details in documenta
|
|
|
240
248
|
```sh
|
|
241
249
|
npm install sharp
|
|
242
250
|
```
|
|
251
|
+
|
|
252
|
+
Variants thumbnail videos are generate by [fluent-ffmpeg](https://www.npmjs.com/package/fluent-ffmpeg)
|
|
253
|
+
By default, image format is PNG and size is video size. `options` attribute use image_converter (and sharp)
|
|
254
|
+
|
|
255
|
+
```sh
|
|
256
|
+
npm install fluent-ffmpeg
|
|
257
|
+
```
|
|
@@ -4,6 +4,5 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export declare const exif: (buffer: Buffer) => Promise<Exif>;
|
|
7
|
+
import type { Exif, Input } from '../types/input.js';
|
|
8
|
+
export declare const exif: (input: Input) => Promise<Exif | undefined>;
|
|
@@ -4,9 +4,35 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
6
|
*/
|
|
7
|
+
import fs from 'node:fs/promises';
|
|
7
8
|
import ExifReader from 'exifreader';
|
|
9
|
+
import logger from '@adonisjs/core/services/logger';
|
|
10
|
+
import { fileTypeFromBuffer, fileTypeFromFile } from 'file-type';
|
|
8
11
|
import { cleanObject } from '../utils/helpers.js';
|
|
9
|
-
export const exif = async (
|
|
12
|
+
export const exif = async (input) => {
|
|
13
|
+
let fileType;
|
|
14
|
+
let buffer;
|
|
15
|
+
if (Buffer.isBuffer(input)) {
|
|
16
|
+
fileType = await fileTypeFromBuffer(input);
|
|
17
|
+
if (fileType?.mime.includes('image')) {
|
|
18
|
+
buffer = input;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
fileType = await fileTypeFromFile(input);
|
|
23
|
+
if (fileType?.mime.includes('image')) {
|
|
24
|
+
buffer = await fs.readFile(input);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (fileType?.mime.includes('video')) {
|
|
28
|
+
return videoExif(input);
|
|
29
|
+
}
|
|
30
|
+
if (buffer && fileType?.mime.includes('image')) {
|
|
31
|
+
return imageExif(buffer);
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
};
|
|
35
|
+
async function imageExif(buffer) {
|
|
10
36
|
const tags = await ExifReader.load(buffer, { expanded: true });
|
|
11
37
|
const data = {};
|
|
12
38
|
if (tags.exif) {
|
|
@@ -75,4 +101,22 @@ export const exif = async (buffer) => {
|
|
|
75
101
|
}
|
|
76
102
|
}
|
|
77
103
|
return cleanObject(data);
|
|
78
|
-
}
|
|
104
|
+
}
|
|
105
|
+
async function videoExif(input) {
|
|
106
|
+
return new Promise(async (resolve) => {
|
|
107
|
+
const module = 'fluent-ffmpeg';
|
|
108
|
+
const result = await import(module);
|
|
109
|
+
const ffmpeg = result.default;
|
|
110
|
+
ffmpeg(input).ffprobe(0, (err, data) => {
|
|
111
|
+
if (err) {
|
|
112
|
+
logger.error({ err });
|
|
113
|
+
}
|
|
114
|
+
resolve({
|
|
115
|
+
dimension: {
|
|
116
|
+
width: data.streams[0].width,
|
|
117
|
+
height: data.streams[0].height,
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
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 Converter from './converter.js';
|
|
9
|
+
export default class AutodetectConverter extends Converter {
|
|
10
|
+
handle({ input, options }: ConverterAttributes): Promise<any>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jrmc/adonis-attachment
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
+
*/
|
|
7
|
+
import { fileTypeFromBuffer, fileTypeFromFile } from 'file-type';
|
|
8
|
+
import Converter from './converter.js';
|
|
9
|
+
import ImageConverter from './image_converter.js';
|
|
10
|
+
import VideoThumnailConverter from './video_thumbnail_converter.js';
|
|
11
|
+
export default class AutodetectConverter extends Converter {
|
|
12
|
+
async handle({ input, options }) {
|
|
13
|
+
let converter;
|
|
14
|
+
let fileType;
|
|
15
|
+
if (Buffer.isBuffer(input)) {
|
|
16
|
+
fileType = await fileTypeFromBuffer(input);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
fileType = await fileTypeFromFile(input);
|
|
20
|
+
}
|
|
21
|
+
if (fileType?.mime.includes('image')) {
|
|
22
|
+
converter = new ImageConverter();
|
|
23
|
+
}
|
|
24
|
+
else if (fileType?.mime.includes('video')) {
|
|
25
|
+
converter = new VideoThumnailConverter();
|
|
26
|
+
}
|
|
27
|
+
if (converter) {
|
|
28
|
+
return await converter.handle({
|
|
29
|
+
input,
|
|
30
|
+
options
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -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 Converter from './converter.js';
|
|
9
|
+
import { Input } from '../types/input.js';
|
|
10
|
+
export default class VideoThumbnailConvert extends Converter {
|
|
11
|
+
handle({ input, options }: ConverterAttributes): Promise<any>;
|
|
12
|
+
videoToImage(ffmpeg: Function, input: Input): Promise<string | false>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
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 logger from '@adonisjs/core/services/logger';
|
|
10
|
+
import { cuid } from '@adonisjs/core/helpers';
|
|
11
|
+
import Converter from './converter.js';
|
|
12
|
+
import ImageConverter from './image_converter.js';
|
|
13
|
+
export default class VideoThumbnailConvert extends Converter {
|
|
14
|
+
async handle({ input, options }) {
|
|
15
|
+
let ffmpeg;
|
|
16
|
+
try {
|
|
17
|
+
const module = 'fluent-ffmpeg';
|
|
18
|
+
const result = await import(module);
|
|
19
|
+
ffmpeg = result.default;
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
logger.error({ err: error }, 'Dependence missing, please install fluent-ffmpeg');
|
|
23
|
+
}
|
|
24
|
+
if (ffmpeg) {
|
|
25
|
+
const filePath = await this.videoToImage(ffmpeg, input);
|
|
26
|
+
if (options && filePath) {
|
|
27
|
+
const converter = new ImageConverter();
|
|
28
|
+
return await converter.handle({
|
|
29
|
+
input: filePath,
|
|
30
|
+
options
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
return filePath;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async videoToImage(ffmpeg, input) {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
const folder = os.tmpdir();
|
|
41
|
+
const filename = `${cuid()}.png`;
|
|
42
|
+
ffmpeg(input)
|
|
43
|
+
.screenshots({
|
|
44
|
+
count: 1,
|
|
45
|
+
filename,
|
|
46
|
+
folder,
|
|
47
|
+
})
|
|
48
|
+
.on('end', () => {
|
|
49
|
+
resolve(path.join(folder, filename));
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -264,6 +264,7 @@ export declare const Attachmentable: <Model extends NormalizeConstructor<import(
|
|
|
264
264
|
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;
|
|
265
265
|
}): Promise<void>;
|
|
266
266
|
find: <T_1 extends import("@adonisjs/lucid/types/model").LucidModel>(this: T_1, value: any, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions | undefined) => Promise<InstanceType<T_1> | null>;
|
|
267
|
+
create: <T_2 extends import("@adonisjs/lucid/types/model").LucidModel>(this: T_2, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T_2>>>, options?: import("@adonisjs/lucid/types/model").ModelAssignOptions | undefined) => Promise<InstanceType<T_2>>;
|
|
267
268
|
readonly booted: boolean;
|
|
268
269
|
$columnsDefinitions: Map<string, import("@adonisjs/lucid/types/model").ModelColumnOptions>;
|
|
269
270
|
$relationsDefinitions: Map<string, import("@adonisjs/lucid/types/relations").RelationshipsContract>;
|
|
@@ -284,8 +285,8 @@ export declare const Attachmentable: <Model extends NormalizeConstructor<import(
|
|
|
284
285
|
serializedToColumns: import("@adonisjs/lucid/types/model").ModelKeysContract;
|
|
285
286
|
serializedToAttributes: import("@adonisjs/lucid/types/model").ModelKeysContract;
|
|
286
287
|
};
|
|
287
|
-
$createFromAdapterResult: <
|
|
288
|
-
$createMultipleFromAdapterResult: <
|
|
288
|
+
$createFromAdapterResult: <T_3 extends import("@adonisjs/lucid/types/model").LucidModel>(this: T_3, result?: import("@adonisjs/lucid/types/model").ModelObject | undefined, sideloadAttributes?: import("@adonisjs/lucid/types/model").ModelObject | undefined, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions | undefined) => InstanceType<T_3> | null;
|
|
289
|
+
$createMultipleFromAdapterResult: <T_4 extends import("@adonisjs/lucid/types/model").LucidModel>(this: T_4, results: import("@adonisjs/lucid/types/model").ModelObject[], sideloadAttributes?: import("@adonisjs/lucid/types/model").ModelObject | undefined, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions | undefined) => InstanceType<T_4>[];
|
|
289
290
|
$addColumn: (name: string, options: Partial<import("@adonisjs/lucid/types/model").ColumnOptions>) => import("@adonisjs/lucid/types/model").ColumnOptions;
|
|
290
291
|
$hasColumn: (name: string) => boolean;
|
|
291
292
|
$getColumn: (name: string) => import("@adonisjs/lucid/types/model").ModelColumnOptions | undefined;
|
|
@@ -310,7 +311,6 @@ export declare const Attachmentable: <Model extends NormalizeConstructor<import(
|
|
|
310
311
|
<Model_8 extends import("@adonisjs/lucid/types/model").LucidModel>(this: Model_8, event: "paginate", handler: import("@adonisjs/lucid/types/model").HooksHandler<import("@adonisjs/lucid/types/model").ModelPaginatorContract<InstanceType<Model_8>>, "paginate">): void;
|
|
311
312
|
<Model_9 extends import("@adonisjs/lucid/types/model").LucidModel, Event_2 extends import("@adonisjs/lucid/types/model").EventsList>(this: Model_9, event: Event_2, handler: import("@adonisjs/lucid/types/model").HooksHandler<InstanceType<Model_9>, Event_2>): void;
|
|
312
313
|
};
|
|
313
|
-
create: <T_4 extends import("@adonisjs/lucid/types/model").LucidModel>(this: T_4, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T_4>>>, options?: import("@adonisjs/lucid/types/model").ModelAssignOptions | undefined) => Promise<InstanceType<T_4>>;
|
|
314
314
|
createMany: <T_5 extends import("@adonisjs/lucid/types/model").LucidModel>(this: T_5, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T_5>>>[], options?: import("@adonisjs/lucid/types/model").ModelAssignOptions | undefined) => Promise<InstanceType<T_5>[]>;
|
|
315
315
|
findOrFail: <T_6 extends import("@adonisjs/lucid/types/model").LucidModel>(this: T_6, value: any, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions | undefined) => Promise<InstanceType<T_6>>;
|
|
316
316
|
findBy: {
|
|
@@ -14,7 +14,7 @@ export declare function createAttachmentAttributes(input: Input, name?: string):
|
|
|
14
14
|
extname: import("file-type").FileExtension;
|
|
15
15
|
mimeType: import("file-type").MimeType;
|
|
16
16
|
size: number;
|
|
17
|
-
meta: import("../types/input.js").Exif;
|
|
17
|
+
meta: import("../types/input.js").Exif | undefined;
|
|
18
18
|
}>;
|
|
19
19
|
export declare function cleanObject(obj: any): any;
|
|
20
20
|
export declare function clone(object: Object): any;
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
6
|
*/
|
|
7
|
-
import fs from 'node:fs/promises';
|
|
8
7
|
import { cuid } from '@adonisjs/core/helpers';
|
|
9
8
|
import string from '@adonisjs/core/helpers/string';
|
|
10
9
|
import { fileTypeFromBuffer, fileTypeFromFile } from 'file-type';
|
|
@@ -19,16 +18,13 @@ export function getOptions(modelInstance, attributeName) {
|
|
|
19
18
|
}
|
|
20
19
|
export async function createAttachmentAttributes(input, name) {
|
|
21
20
|
let fileType;
|
|
22
|
-
let meta;
|
|
23
21
|
if (Buffer.isBuffer(input)) {
|
|
24
22
|
fileType = await fileTypeFromBuffer(input);
|
|
25
|
-
meta = await exif(input);
|
|
26
23
|
}
|
|
27
24
|
else {
|
|
28
25
|
fileType = await fileTypeFromFile(input);
|
|
29
|
-
const buffer = await fs.readFile(input);
|
|
30
|
-
meta = await exif(buffer);
|
|
31
26
|
}
|
|
27
|
+
const meta = await exif(input);
|
|
32
28
|
if (name) {
|
|
33
29
|
name = string.slug(name);
|
|
34
30
|
}
|
|
@@ -40,7 +36,7 @@ export async function createAttachmentAttributes(input, name) {
|
|
|
40
36
|
extname: fileType.ext,
|
|
41
37
|
mimeType: fileType.mime,
|
|
42
38
|
size: input.length,
|
|
43
|
-
meta,
|
|
39
|
+
meta: meta,
|
|
44
40
|
};
|
|
45
41
|
}
|
|
46
42
|
export function cleanObject(obj) {
|
package/build/stubs/config.stub
CHANGED
|
@@ -37,5 +37,13 @@ export default defineConfig({
|
|
|
37
37
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
|
+
// {
|
|
41
|
+
// key: 'preview',
|
|
42
|
+
// converter: () => import('@jrmc/adonis-attachment/converters/video_thumbnail_converter'),
|
|
43
|
+
// options: {
|
|
44
|
+
// format: 'jpeg',
|
|
45
|
+
// resize: 720
|
|
46
|
+
// }
|
|
47
|
+
// },
|
|
40
48
|
]
|
|
41
49
|
})
|
package/package.json
CHANGED