@jrmc/adonis-attachment 2.3.2 → 2.4.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 +11 -11
- package/build/commands/commands.json +1 -0
- package/build/commands/main.d.ts +4 -0
- package/build/commands/main.js +36 -0
- package/build/commands/make/converter.d.ts +17 -0
- package/build/commands/make/converter.js +31 -0
- package/build/configure.js +1 -0
- package/build/src/attachment_manager.d.ts +2 -0
- package/build/src/attachment_manager.js +4 -0
- package/build/src/converter_manager.d.ts +1 -1
- package/build/src/converter_manager.js +30 -21
- package/build/src/converters/autodetect_converter.d.ts +1 -1
- package/build/src/converters/document_thumbnail_converter.d.ts +1 -1
- package/build/src/converters/document_thumbnail_converter.js +10 -16
- package/build/src/converters/image_converter.d.ts +2 -1
- package/build/src/converters/image_converter.js +13 -19
- package/build/src/converters/pdf_thumbnail_converter.d.ts +1 -1
- package/build/src/converters/pdf_thumbnail_converter.js +10 -16
- package/build/src/converters/video_thumbnail_converter.d.ts +1 -1
- package/build/src/converters/video_thumbnail_converter.js +16 -18
- package/build/src/errors.d.ts +8 -0
- package/build/src/errors.js +8 -0
- package/build/src/types/config.d.ts +5 -0
- package/build/src/types/converter.d.ts +0 -2
- package/build/src/utils/actions.js +11 -12
- package/build/stubs/make/converter/main.stub +20 -0
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -8,10 +8,10 @@ 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
|
-
|
|
13
11
|
[ChangeLog](https://adonis-attachment.jrmc.dev/changelog.html)
|
|
14
12
|
|
|
13
|
+
[Discord](https://discord.gg/89eMn2vB)
|
|
14
|
+
|
|
15
15
|
⚠️ [Breaking change](https://adonis-attachment.jrmc.dev/changelog.html) version 2, include [@adonisjs/drive](https://docs.adonisjs.com/guides/digging-deeper/drive)
|
|
16
16
|
|
|
17
17
|
## Roadmap
|
|
@@ -23,9 +23,9 @@ Project sample : [adonis-starter-kit](https://github.com/batosai/adonis-starter-
|
|
|
23
23
|
- [x] documents thumbnail
|
|
24
24
|
- [x] videos thumbnail
|
|
25
25
|
- [ ] command regenerate
|
|
26
|
-
- [
|
|
26
|
+
- [x] command make:convert
|
|
27
27
|
- [x] adonis-drive/flydrive
|
|
28
|
-
- [
|
|
28
|
+
- [x] jobs queue
|
|
29
29
|
- [x] serialize
|
|
30
30
|
|
|
31
31
|
|
|
@@ -46,11 +46,11 @@ Simple upload file
|
|
|
46
46
|
import { BaseModel } from '@adonisjs/lucid/orm'
|
|
47
47
|
import { compose } from '@adonisjs/core/helpers'
|
|
48
48
|
import { attachment, Attachmentable } from '@jrmc/adonis-attachment'
|
|
49
|
-
import type { Attachment } from '@jrmc/adonis-attachment/types/attachment'
|
|
49
|
+
import type { Attachment } from '@jrmc/adonis-attachment/types/attachment'
|
|
50
50
|
|
|
51
|
-
class User extends compose(BaseModel, Attachmentable) {
|
|
52
|
-
@attachment()
|
|
53
|
-
declare avatar: Attachment
|
|
51
|
+
class User extends compose(BaseModel, Attachmentable) {
|
|
52
|
+
@attachment()
|
|
53
|
+
declare avatar: Attachment
|
|
54
54
|
}
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -58,14 +58,14 @@ class User extends compose(BaseModel, Attachmentable) { // [!code highlight]
|
|
|
58
58
|
|
|
59
59
|
```ts
|
|
60
60
|
// app/controllers/users_controller.ts
|
|
61
|
-
import { attachmentManager } from '@jrmc/adonis-attachment'
|
|
61
|
+
import { attachmentManager } from '@jrmc/adonis-attachment'
|
|
62
62
|
|
|
63
63
|
class UsersController {
|
|
64
64
|
public store({ request }: HttpContext) {
|
|
65
|
-
const avatar = request.file('avatar')!
|
|
65
|
+
const avatar = request.file('avatar')!
|
|
66
66
|
const user = new User()
|
|
67
67
|
|
|
68
|
-
user.avatar = await attachmentManager.createFromFile(avatar)
|
|
68
|
+
user.avatar = await attachmentManager.createFromFile(avatar)
|
|
69
69
|
await user.save()
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"commands":[{"commandName":"make:converter","description":"Create a new media converter class","help":"","namespace":"make","aliases":[],"flags":[],"args":[{"name":"name","argumentName":"name","required":true,"description":"The name of the converter","type":"string"}],"options":{},"filePath":"make/converter.js"}],"version":1}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* In-memory cache of commands after they have been loaded
|
|
5
|
+
*/
|
|
6
|
+
let commandsMetaData
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Reads the commands from the "./commands.json" file. Since, the commands.json
|
|
10
|
+
* file is generated automatically, we do not have to validate its contents
|
|
11
|
+
*/
|
|
12
|
+
export async function getMetaData() {
|
|
13
|
+
if (commandsMetaData) {
|
|
14
|
+
return commandsMetaData
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const commandsIndex = await readFile(new URL('./commands.json', import.meta.url), 'utf-8')
|
|
18
|
+
commandsMetaData = JSON.parse(commandsIndex).commands
|
|
19
|
+
|
|
20
|
+
return commandsMetaData
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Imports the command by lookingup its path from the commands
|
|
25
|
+
* metadata
|
|
26
|
+
*/
|
|
27
|
+
export async function getCommand(metaData) {
|
|
28
|
+
const commands = await getMetaData()
|
|
29
|
+
const command = commands.find(({ commandName }) => metaData.commandName === commandName)
|
|
30
|
+
if (!command) {
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { default: commandConstructor } = await import(new URL(command.filePath, import.meta.url).href)
|
|
35
|
+
return commandConstructor
|
|
36
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jrmc/adonis-attachment
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
+
*/
|
|
7
|
+
import { BaseCommand } from '@adonisjs/core/ace';
|
|
8
|
+
/**
|
|
9
|
+
* The make controller command to create an HTTP controller
|
|
10
|
+
*/
|
|
11
|
+
export default class MakeConverter extends BaseCommand {
|
|
12
|
+
static commandName: string;
|
|
13
|
+
static description: string;
|
|
14
|
+
name: string;
|
|
15
|
+
protected stubPath: string;
|
|
16
|
+
run(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jrmc/adonis-attachment
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
+
*/
|
|
7
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
8
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
10
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
11
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
12
|
+
};
|
|
13
|
+
import { stubsRoot } from '../../stubs/main.js';
|
|
14
|
+
import { args, BaseCommand } from '@adonisjs/core/ace';
|
|
15
|
+
/**
|
|
16
|
+
* The make controller command to create an HTTP controller
|
|
17
|
+
*/
|
|
18
|
+
export default class MakeConverter extends BaseCommand {
|
|
19
|
+
static commandName = 'make:converter';
|
|
20
|
+
static description = 'Create a new media converter class';
|
|
21
|
+
stubPath = 'make/converter/main.stub';
|
|
22
|
+
async run() {
|
|
23
|
+
const codemods = await this.createCodemods();
|
|
24
|
+
await codemods.makeUsingStub(stubsRoot, this.stubPath, {
|
|
25
|
+
name: this.name,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
__decorate([
|
|
30
|
+
args.string({ description: 'The name of the converter' })
|
|
31
|
+
], MakeConverter.prototype, "name", void 0);
|
package/build/configure.js
CHANGED
|
@@ -8,10 +8,12 @@ import type { DriveService, SignedURLOptions } from '@adonisjs/drive/types';
|
|
|
8
8
|
import type { MultipartFile } from '@adonisjs/core/bodyparser';
|
|
9
9
|
import type { AttachmentBase, Attachment as AttachmentType } from './types/attachment.js';
|
|
10
10
|
import type { ResolvedAttachmentConfig } from './types/config.js';
|
|
11
|
+
import { DeferQueue } from '@poppinss/defer';
|
|
11
12
|
import { Attachment } from './attachments/attachment.js';
|
|
12
13
|
import Converter from './converters/converter.js';
|
|
13
14
|
export declare class AttachmentManager {
|
|
14
15
|
#private;
|
|
16
|
+
queue: DeferQueue;
|
|
15
17
|
constructor(config: ResolvedAttachmentConfig, drive: DriveService);
|
|
16
18
|
getConfig(): ResolvedAttachmentConfig;
|
|
17
19
|
createFromDbResponse(response: any): Attachment | null;
|
|
@@ -4,17 +4,21 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
6
|
*/
|
|
7
|
+
import { DeferQueue } from '@poppinss/defer';
|
|
7
8
|
import * as errors from './errors.js';
|
|
8
9
|
import { Attachment } from './attachments/attachment.js';
|
|
9
10
|
import { createAttachmentAttributes, isBase64 } from './utils/helpers.js';
|
|
10
11
|
import { exif } from './adapters/exif.js';
|
|
11
12
|
const REQUIRED_ATTRIBUTES = ['name', 'size', 'extname', 'mimeType'];
|
|
12
13
|
export class AttachmentManager {
|
|
14
|
+
queue;
|
|
13
15
|
#config;
|
|
14
16
|
#drive;
|
|
15
17
|
constructor(config, drive) {
|
|
16
18
|
this.#drive = drive;
|
|
17
19
|
this.#config = config;
|
|
20
|
+
const concurrency = this.#config.queue?.concurrency || 1;
|
|
21
|
+
this.queue = new DeferQueue({ concurrency });
|
|
18
22
|
}
|
|
19
23
|
getConfig() {
|
|
20
24
|
return this.#config;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ConverterInitializeAttributes } from './types/converter.js';
|
|
2
2
|
export declare class ConverterManager {
|
|
3
3
|
#private;
|
|
4
|
-
constructor({ record, attributeName
|
|
4
|
+
constructor({ record, attributeName }: ConverterInitializeAttributes);
|
|
5
5
|
save(): Promise<void>;
|
|
6
6
|
}
|
|
@@ -1,35 +1,44 @@
|
|
|
1
1
|
import db from '@adonisjs/lucid/services/db';
|
|
2
2
|
import attachmentManager from '../services/main.js';
|
|
3
|
+
import * as errors from './errors.js';
|
|
4
|
+
import { getOptions } from './utils/helpers.js';
|
|
3
5
|
export class ConverterManager {
|
|
4
|
-
#key;
|
|
5
|
-
#input;
|
|
6
6
|
#record;
|
|
7
7
|
#attributeName;
|
|
8
|
-
|
|
9
|
-
constructor({ record, attributeName, key, converter }) {
|
|
10
|
-
this.#key = key;
|
|
8
|
+
constructor({ record, attributeName }) {
|
|
11
9
|
this.#record = record;
|
|
12
10
|
this.#attributeName = attributeName;
|
|
13
|
-
this.#input = record.$attributes[attributeName].input;
|
|
14
|
-
this.#converter = converter;
|
|
15
11
|
}
|
|
16
12
|
async save() {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
options: this.#converter.options,
|
|
23
|
-
});
|
|
24
|
-
const Model = record.constructor;
|
|
25
|
-
const id = record.$attributes['id'];
|
|
26
|
-
const attachment = record.$attributes[attribute];
|
|
13
|
+
const options = getOptions(this.#record, this.#attributeName);
|
|
14
|
+
const attachment = this.#record.$attributes[this.#attributeName];
|
|
15
|
+
const input = attachment.input;
|
|
16
|
+
const Model = this.#record.constructor;
|
|
17
|
+
const id = this.#record.$attributes['id'];
|
|
27
18
|
const data = {};
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
if (options.variants) {
|
|
20
|
+
for (const option of options.variants) {
|
|
21
|
+
const converter = (await attachmentManager.getConverter(option));
|
|
22
|
+
if (attachment && converter) {
|
|
23
|
+
const output = await converter.handle({
|
|
24
|
+
input,
|
|
25
|
+
options: converter.options,
|
|
26
|
+
});
|
|
27
|
+
if (output === undefined) {
|
|
28
|
+
throw new errors.E_CANNOT_PATH_BY_CONVERTER();
|
|
29
|
+
}
|
|
30
|
+
const variant = await attachment.createVariant(option, output);
|
|
31
|
+
await attachmentManager.save(variant);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
data[this.#attributeName] = JSON.stringify(attachment.toObject());
|
|
31
36
|
const trx = await db.transaction();
|
|
32
|
-
trx.after('rollback', () =>
|
|
37
|
+
trx.after('rollback', () => {
|
|
38
|
+
for (const variant of attachment.variants) {
|
|
39
|
+
attachmentManager.delete(variant);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
33
42
|
try {
|
|
34
43
|
await trx.query().from(Model.table).where('id', id).update(data);
|
|
35
44
|
return await trx.commit();
|
|
@@ -7,5 +7,5 @@
|
|
|
7
7
|
import type { ConverterAttributes } from '../types/converter.js';
|
|
8
8
|
import Converter from './converter.js';
|
|
9
9
|
export default class AutodetectConverter extends Converter {
|
|
10
|
-
handle({ input, options }: ConverterAttributes): Promise<
|
|
10
|
+
handle({ input, options }: ConverterAttributes): Promise<false | import("../types/input.js").Input | undefined>;
|
|
11
11
|
}
|
|
@@ -8,6 +8,6 @@ import type { ConverterAttributes } from '../types/converter.js';
|
|
|
8
8
|
import type { Input } from '../types/input.js';
|
|
9
9
|
import Converter from './converter.js';
|
|
10
10
|
export default class DocumentThumbnailConverter extends Converter {
|
|
11
|
-
handle({ input, options }: ConverterAttributes): Promise<
|
|
11
|
+
handle({ input, options }: ConverterAttributes): Promise<Input>;
|
|
12
12
|
documentToImage(LibreOfficeFileConverter: any, input: Input): Promise<any>;
|
|
13
13
|
}
|
|
@@ -4,28 +4,22 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
6
|
*/
|
|
7
|
-
import logger from '@adonisjs/core/services/logger';
|
|
8
7
|
import Converter from './converter.js';
|
|
9
8
|
import ImageConverter from './image_converter.js';
|
|
10
9
|
import { use } from '../utils/helpers.js';
|
|
11
10
|
export default class DocumentThumbnailConverter extends Converter {
|
|
12
11
|
async handle({ input, options }) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
return outputBuffer;
|
|
25
|
-
}
|
|
26
|
-
catch (err) {
|
|
27
|
-
logger.error({ err });
|
|
12
|
+
const lib = await use('libreoffice-file-converter');
|
|
13
|
+
const LibreOfficeFileConverter = lib.LibreOfficeFileConverter;
|
|
14
|
+
const outputBuffer = await this.documentToImage(LibreOfficeFileConverter, input);
|
|
15
|
+
if (options && outputBuffer) {
|
|
16
|
+
const converter = new ImageConverter();
|
|
17
|
+
return await converter.handle({
|
|
18
|
+
input: outputBuffer,
|
|
19
|
+
options,
|
|
20
|
+
});
|
|
28
21
|
}
|
|
22
|
+
return outputBuffer;
|
|
29
23
|
}
|
|
30
24
|
async documentToImage(LibreOfficeFileConverter, input) {
|
|
31
25
|
let binaryPaths = undefined;
|
|
@@ -5,7 +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
10
|
export default class ImageConverter extends Converter {
|
|
10
|
-
handle({ input, options }: ConverterAttributes): Promise<
|
|
11
|
+
handle({ input, options }: ConverterAttributes): Promise<Input>;
|
|
11
12
|
}
|
|
@@ -4,29 +4,23 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
6
|
*/
|
|
7
|
-
import logger from '@adonisjs/core/services/logger';
|
|
8
7
|
import Converter from './converter.js';
|
|
9
8
|
import { use } from '../utils/helpers.js';
|
|
10
9
|
export default class ImageConverter extends Converter {
|
|
11
10
|
async handle({ input, options }) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
format = format.format;
|
|
20
|
-
}
|
|
21
|
-
const buffer = await sharp(input)
|
|
22
|
-
.withMetadata()
|
|
23
|
-
.resize(resize)
|
|
24
|
-
.toFormat(format, formatoptions)
|
|
25
|
-
.toBuffer();
|
|
26
|
-
return buffer;
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
logger.error({ err });
|
|
11
|
+
const sharp = await use('sharp');
|
|
12
|
+
const resize = options?.resize || {};
|
|
13
|
+
let format = options?.format || 'webp';
|
|
14
|
+
let formatoptions = {};
|
|
15
|
+
if (typeof format !== 'string') {
|
|
16
|
+
formatoptions = format?.options;
|
|
17
|
+
format = format.format;
|
|
30
18
|
}
|
|
19
|
+
const buffer = await sharp(input)
|
|
20
|
+
.withMetadata()
|
|
21
|
+
.resize(resize)
|
|
22
|
+
.toFormat(format, formatoptions)
|
|
23
|
+
.toBuffer();
|
|
24
|
+
return buffer;
|
|
31
25
|
}
|
|
32
26
|
}
|
|
@@ -8,6 +8,6 @@ import type { ConverterAttributes } from '../types/converter.js';
|
|
|
8
8
|
import type { Input } from '../types/input.js';
|
|
9
9
|
import Converter from './converter.js';
|
|
10
10
|
export default class PdfThumbnailConverter extends Converter {
|
|
11
|
-
handle({ input, options }: ConverterAttributes): Promise<
|
|
11
|
+
handle({ input, options }: ConverterAttributes): Promise<Input>;
|
|
12
12
|
pdfToImage(Poppler: any, input: Input): Promise<string>;
|
|
13
13
|
}
|
|
@@ -7,28 +7,22 @@
|
|
|
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';
|
|
11
10
|
import Converter from './converter.js';
|
|
12
11
|
import ImageConverter from './image_converter.js';
|
|
13
12
|
import { use } from '../utils/helpers.js';
|
|
14
13
|
export default class PdfThumbnailConverter extends Converter {
|
|
15
14
|
async handle({ input, options }) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
return filePath;
|
|
28
|
-
}
|
|
29
|
-
catch (err) {
|
|
30
|
-
logger.error({ err });
|
|
15
|
+
const nodePoppler = await use('node-poppler');
|
|
16
|
+
const Poppler = nodePoppler.Poppler;
|
|
17
|
+
const filePath = await this.pdfToImage(Poppler, input);
|
|
18
|
+
if (options && filePath) {
|
|
19
|
+
const converter = new ImageConverter();
|
|
20
|
+
return await converter.handle({
|
|
21
|
+
input: filePath,
|
|
22
|
+
options,
|
|
23
|
+
});
|
|
31
24
|
}
|
|
25
|
+
return filePath;
|
|
32
26
|
}
|
|
33
27
|
async pdfToImage(Poppler, input) {
|
|
34
28
|
let binPath = null;
|
|
@@ -8,6 +8,6 @@ import type { ConverterAttributes } from '../types/converter.js';
|
|
|
8
8
|
import type { Input } from '../types/input.js';
|
|
9
9
|
import Converter from './converter.js';
|
|
10
10
|
export default class VideoThumbnailConvert extends Converter {
|
|
11
|
-
handle({ input, options }: ConverterAttributes): Promise<
|
|
11
|
+
handle({ input, options }: ConverterAttributes): Promise<false | Input>;
|
|
12
12
|
videoToImage(ffmpeg: Function, input: Input): Promise<string | false>;
|
|
13
13
|
}
|
|
@@ -7,28 +7,22 @@
|
|
|
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';
|
|
11
10
|
import Converter from './converter.js';
|
|
12
11
|
import ImageConverter from './image_converter.js';
|
|
13
12
|
import { bufferToTempFile, use } from '../utils/helpers.js';
|
|
14
13
|
export default class VideoThumbnailConvert extends Converter {
|
|
15
14
|
async handle({ input, options }) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
return filePath;
|
|
28
|
-
}
|
|
15
|
+
const ffmpeg = await use('fluent-ffmpeg');
|
|
16
|
+
const filePath = await this.videoToImage(ffmpeg, input);
|
|
17
|
+
if (options && filePath) {
|
|
18
|
+
const converter = new ImageConverter();
|
|
19
|
+
return await converter.handle({
|
|
20
|
+
input: filePath,
|
|
21
|
+
options,
|
|
22
|
+
});
|
|
29
23
|
}
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
else {
|
|
25
|
+
return filePath;
|
|
32
26
|
}
|
|
33
27
|
}
|
|
34
28
|
async videoToImage(ffmpeg, input) {
|
|
@@ -36,7 +30,7 @@ export default class VideoThumbnailConvert extends Converter {
|
|
|
36
30
|
if (Buffer.isBuffer(input)) {
|
|
37
31
|
file = await bufferToTempFile(input);
|
|
38
32
|
}
|
|
39
|
-
return new Promise((resolve) => {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
40
34
|
const folder = os.tmpdir();
|
|
41
35
|
const filename = `${cuid()}.png`;
|
|
42
36
|
const ff = ffmpeg(file);
|
|
@@ -49,8 +43,12 @@ export default class VideoThumbnailConvert extends Converter {
|
|
|
49
43
|
count: 1,
|
|
50
44
|
filename,
|
|
51
45
|
folder,
|
|
52
|
-
})
|
|
46
|
+
})
|
|
47
|
+
.on('end', () => {
|
|
53
48
|
resolve(path.join(folder, filename));
|
|
49
|
+
})
|
|
50
|
+
.on('error', (err) => {
|
|
51
|
+
reject(err);
|
|
54
52
|
});
|
|
55
53
|
});
|
|
56
54
|
}
|
package/build/src/errors.d.ts
CHANGED
|
@@ -44,6 +44,14 @@ export declare const E_MISSING_PACKAGE: new (args: [key: string], options?: Erro
|
|
|
44
44
|
* Unable to create Attachment Object
|
|
45
45
|
*/
|
|
46
46
|
export declare const E_CANNOT_CREATE_ATTACHMENT: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
47
|
+
/**
|
|
48
|
+
* Unable to create variant
|
|
49
|
+
*/
|
|
50
|
+
export declare const E_CANNOT_CREATE_VARIANT: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
51
|
+
/**
|
|
52
|
+
* Missing path
|
|
53
|
+
*/
|
|
54
|
+
export declare const E_CANNOT_PATH_BY_CONVERTER: new (args?: any, options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
47
55
|
/**
|
|
48
56
|
* Is not a Buffer
|
|
49
57
|
*/
|
package/build/src/errors.js
CHANGED
|
@@ -42,6 +42,14 @@ export const E_MISSING_PACKAGE = createError('Missing package, please install "%
|
|
|
42
42
|
* Unable to create Attachment Object
|
|
43
43
|
*/
|
|
44
44
|
export const E_CANNOT_CREATE_ATTACHMENT = createError('Cannot create attachment from database response. Missing attribute "%s"', 'E_CANNOT_CREATE_ATTACHMENT');
|
|
45
|
+
/**
|
|
46
|
+
* Unable to create variant
|
|
47
|
+
*/
|
|
48
|
+
export const E_CANNOT_CREATE_VARIANT = createError('Cannot create variant. "%s"', 'E_CANNOT_CREATE_VARIANT');
|
|
49
|
+
/**
|
|
50
|
+
* Missing path
|
|
51
|
+
*/
|
|
52
|
+
export const E_CANNOT_PATH_BY_CONVERTER = createError('Path not found', 'E_CANNOT_PATH_BY_CONVERTER');
|
|
45
53
|
/**
|
|
46
54
|
* Is not a Buffer
|
|
47
55
|
*/
|
|
@@ -13,6 +13,9 @@ type ConverterConfig = {
|
|
|
13
13
|
converter: () => Promise<ImportConverter>;
|
|
14
14
|
options?: ConverterOptions;
|
|
15
15
|
};
|
|
16
|
+
type Queue = {
|
|
17
|
+
concurrency: number;
|
|
18
|
+
};
|
|
16
19
|
export type BinPaths = {
|
|
17
20
|
ffmpegPath?: string;
|
|
18
21
|
ffprobePath?: string;
|
|
@@ -25,6 +28,7 @@ export type AttachmentConfig = {
|
|
|
25
28
|
rename?: boolean;
|
|
26
29
|
preComputeUrl?: boolean;
|
|
27
30
|
converters?: ConverterConfig[];
|
|
31
|
+
queue?: Queue;
|
|
28
32
|
};
|
|
29
33
|
export type ResolvedConverter = {
|
|
30
34
|
key: string;
|
|
@@ -36,5 +40,6 @@ export type ResolvedAttachmentConfig = {
|
|
|
36
40
|
rename?: boolean;
|
|
37
41
|
preComputeUrl?: boolean;
|
|
38
42
|
converters?: ResolvedConverter[];
|
|
43
|
+
queue?: Queue;
|
|
39
44
|
};
|
|
40
45
|
export {};
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import attachmentManager from '../../services/main.js';
|
|
8
8
|
import { getOptions } from './helpers.js';
|
|
9
9
|
import { ConverterManager } from '../converter_manager.js';
|
|
10
|
+
import { E_CANNOT_CREATE_VARIANT } from '../errors.js';
|
|
10
11
|
/**
|
|
11
12
|
* During commit, we should cleanup the old detached files
|
|
12
13
|
*/
|
|
@@ -45,7 +46,6 @@ export async function persistAttachment(modelInstance, attributeName) {
|
|
|
45
46
|
* If there is a new file and its local then we must save this
|
|
46
47
|
* file.
|
|
47
48
|
*/
|
|
48
|
-
// if (newFile && newFile.isLocal) {
|
|
49
49
|
if (newFile) {
|
|
50
50
|
newFile.setOptions(options);
|
|
51
51
|
modelInstance.$attachments.attached.push(newFile);
|
|
@@ -72,20 +72,19 @@ export async function preComputeUrl(modelInstance, attributeName) {
|
|
|
72
72
|
* Launch converter by variant option
|
|
73
73
|
*/
|
|
74
74
|
export async function generateVariants(modelInstance, attributeName) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const converter = await attachmentManager.getConverter(option);
|
|
80
|
-
if (attachment && converter) {
|
|
75
|
+
attachmentManager.queue.push({
|
|
76
|
+
name: `${modelInstance.constructor.name}-${attributeName}`,
|
|
77
|
+
async run() {
|
|
78
|
+
try {
|
|
81
79
|
const converterManager = new ConverterManager({
|
|
82
80
|
record: modelInstance,
|
|
83
81
|
attributeName,
|
|
84
|
-
key: option,
|
|
85
|
-
converter,
|
|
86
82
|
});
|
|
87
|
-
converterManager.save();
|
|
83
|
+
await converterManager.save();
|
|
88
84
|
}
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
catch (err) {
|
|
86
|
+
throw new E_CANNOT_CREATE_VARIANT([err.message]);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
});
|
|
91
90
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{{#var entity = generators.createEntity(name)}}
|
|
2
|
+
{{#var modelConverterName = string(entity.name).removeSuffix('converter').pascalCase().suffix(string.pascalCase('converter')).toString()}}
|
|
3
|
+
{{#var modelConverterFileName = string(entity.name).snakeCase().suffix('_converter').ext('.ts').toString()}}
|
|
4
|
+
{{{
|
|
5
|
+
exports({ to: app.makePath('app/converters', entity.path, modelConverterFileName) })
|
|
6
|
+
}}}
|
|
7
|
+
|
|
8
|
+
import type { ConverterAttributes } from '@jrmc/adonis-attachment/types/converter'
|
|
9
|
+
import type { Input } from '@jrmc/adonis-attachment/types/input'
|
|
10
|
+
|
|
11
|
+
import logger from '@adonisjs/core/services/logger'
|
|
12
|
+
import Converter from '@jrmc/adonis-attachment/converters/converter'
|
|
13
|
+
|
|
14
|
+
export default class {{ modelConverterName }} extends Converter {
|
|
15
|
+
async handle({ input }: ConverterAttributes): Promise<Input> {
|
|
16
|
+
logger.info('Input path %s', input)
|
|
17
|
+
|
|
18
|
+
return input
|
|
19
|
+
}
|
|
20
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jrmc/adonis-attachment",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Turn any field on your Lucid model to an attachment data type",
|
|
6
6
|
"engines": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "https://github.com/batosai/adonis-attachment.git"
|
|
13
13
|
},
|
|
14
|
+
"homepage": "https://adonis-attachment.jrmc.dev",
|
|
14
15
|
"author": "Jeremy Chaufourier jeremy@chaufourier.fr",
|
|
15
16
|
"license": "MIT",
|
|
16
17
|
"keywords": [
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
"image-attachment",
|
|
22
23
|
"image-manipulation",
|
|
23
24
|
"responsive-images",
|
|
24
|
-
"
|
|
25
|
+
"upload",
|
|
25
26
|
"resize-images",
|
|
26
27
|
"optimize-images",
|
|
27
28
|
"image",
|
|
@@ -36,10 +37,12 @@
|
|
|
36
37
|
],
|
|
37
38
|
"scripts": {
|
|
38
39
|
"typecheck": "tsc --noEmit",
|
|
39
|
-
"build": "npm run clean && tsc
|
|
40
|
+
"build": "npm run clean && tsc",
|
|
41
|
+
"postbuild": "npm run copyfiles && npm run index:commands",
|
|
40
42
|
"prepublishOnly": "npm run build",
|
|
41
43
|
"clean": "del-cli build",
|
|
42
44
|
"copyfiles": "copyfiles \"stubs/**/**/*.stub\" build",
|
|
45
|
+
"index:commands": "adonis-kit index build/commands",
|
|
43
46
|
"format": "prettier --write .",
|
|
44
47
|
"docs:dev": "vitepress dev docs",
|
|
45
48
|
"docs:build": "vitepress build docs",
|
|
@@ -52,10 +55,14 @@
|
|
|
52
55
|
"./decorators": "./build/src/decorators/attachment.js",
|
|
53
56
|
"./services/*": "./build/services/*.js",
|
|
54
57
|
"./converters/*": "./build/src/converters/*.js",
|
|
58
|
+
"./providers/*": "./build/providers/*.js",
|
|
59
|
+
"./commands": "./build/commands/main.js",
|
|
60
|
+
"./commands/*": "./build/commands/*.js",
|
|
55
61
|
"./attachment_service": "./build/services/attachment_service.js",
|
|
56
62
|
"./attachment_provider": "./build/providers/attachment_provider.js"
|
|
57
63
|
},
|
|
58
64
|
"dependencies": {
|
|
65
|
+
"@poppinss/defer": "^1.1.0",
|
|
59
66
|
"exifreader": "^4.23.3",
|
|
60
67
|
"file-type": "^19.4.0"
|
|
61
68
|
},
|