@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 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
- - [ ] videos
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
- Variant image is generate by [sharp module](https://sharp.pixelplumbing.com)
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
- /// <reference types="node" resolution-mode="require"/>
8
- import type { Exif } from '../types/input.js';
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 (buffer) => {
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: <T_2 extends import("@adonisjs/lucid/types/model").LucidModel>(this: T_2, 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_2> | null;
288
- $createMultipleFromAdapterResult: <T_3 extends import("@adonisjs/lucid/types/model").LucidModel>(this: T_3, 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_3>[];
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) {
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jrmc/adonis-attachment",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "Turn any field on your Lucid model to an attachment data type",
6
6
  "engines": {
@@ -1,7 +0,0 @@
1
- export {};
2
- /**
3
- * @jrmc/adonis-attachment
4
- *
5
- * @license MIT
6
- * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
7
- */
@@ -1,7 +0,0 @@
1
- export {};
2
- /**
3
- * @jrmc/adonis-attachment
4
- *
5
- * @license MIT
6
- * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
7
- */