Skip to content

Commit 68abf29

Browse files
authored
feat(VIDEO-20042): image to video (resourceType: 'image') (#916)
* feat(VIDEO-20042): image to video (resourceType: 'image') * chore: remove I2V examples * chore: resourceType config * chore: resourceType config * chore: resourceType config
1 parent 2c5b4ed commit 68abf29

File tree

6 files changed

+102
-5
lines changed

6 files changed

+102
-5
lines changed

src/config/configSchema.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,10 @@
274274
},
275275
"default": ["auto"]
276276
},
277+
"resourceType": {
278+
"type": "string",
279+
"default": "video"
280+
},
277281
"transformation": {
278282
"oneOf": [
279283
{

src/plugins/cloudinary/models/video-source/video-source.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ class VideoSource extends BaseSource {
3333

3434
options = Object.assign({}, DEFAULT_VIDEO_PARAMS, options);
3535

36+
if (options.resourceType) {
37+
options.resource_type = options.resourceType;
38+
}
39+
3640
if (!options.poster) {
3741
options.poster = Object.assign({ publicId }, DEFAULT_POSTER_PARAMS);
3842
}
@@ -67,18 +71,18 @@ class VideoSource extends BaseSource {
6771
this[prop](options[prop]);
6872
}
6973
});
70-
74+
7175
// Initialize poster
7276
this.poster(options.poster);
73-
77+
7478
this.objectId = generateId();
7579
}
7680

7781
// Helper method to create simple getter/setter methods
7882
_createGetterSetters(properties) {
7983
properties.forEach(prop => {
8084
const privateKey = `_${prop}`;
81-
this[prop] = function(value) {
85+
this[prop] = function (value) {
8286
if (value === undefined) {
8387
// Provide sensible defaults for specific properties
8488
if (prop === 'sourceTypes' && this[privateKey] === undefined) {
@@ -123,6 +127,9 @@ class VideoSource extends BaseSource {
123127
}
124128

125129
options.cloudinaryConfig = options.cloudinaryConfig || this.cloudinaryConfig();
130+
131+
options.resource_type = this.resourceType() || options.resource_type;
132+
126133
this._poster = new ImageSource(publicId, options);
127134

128135
return this;
@@ -149,7 +156,7 @@ class VideoSource extends BaseSource {
149156
opts.transformation = castArray(srcTransformation);
150157
}
151158

152-
Object.assign(opts, { resource_type: 'video', format });
159+
Object.assign(opts, { format });
153160

154161
const [type, codecTrans] = formatToMimeTypeAndTransformation(sourceType);
155162

@@ -214,7 +221,7 @@ class VideoSource extends BaseSource {
214221
}
215222

216223
const info = this._info || this.getInitOptions().info;
217-
224+
218225
return {
219226
title: this.title() || info?.title || '',
220227
subtitle: this.description() || info?.subtitle || '',

src/utils/get-analytics-player-options.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const getSourceOptions = (sourceOptions = {}) => ({
1717
autoShowRecommendations: sourceOptions.autoShowRecommendations,
1818
fontFace: sourceOptions.fontFace,
1919
sourceTypes: sourceOptions.sourceTypes,
20+
resourceType: sourceOptions.resourceType,
2021
chapters: (() => {
2122
if (sourceOptions.chapters === true) return 'auto';
2223
if (sourceOptions.chapters && sourceOptions.chapters.url) return 'url';

src/validators/validators.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export const playerValidators = {
8585

8686
export const sourceValidators = {
8787
raw_transformation: validator.isString,
88+
resourceType: validator.isString,
8889
shoppable: validator.isPlainObject,
8990
chapters: validator.or(validator.isBoolean, validator.isPlainObject),
9091
visualSearch: validator.or(validator.isBoolean),

src/video-player.const.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const SOURCE_PARAMS = [
1313
'publicId',
1414
'rawTransformation',
1515
'recommendations',
16+
'resourceType',
1617
'shoppable',
1718
'source',
1819
'sourceTransformation',

test/unit/videoSource.test.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,4 +474,87 @@ describe('test hasCodec method', () => {
474474
});
475475

476476
});
477+
478+
describe('resourceType parameter tests', () => {
479+
it('should use image resourceType when specified', () => {
480+
const source = new VideoSource('sample', {
481+
cloudinaryConfig: cld,
482+
resourceType: 'image',
483+
transformation: [
484+
{ effect: 'ai:model_veo-animate;details_(prompt_!flying bee!)' },
485+
{ format: 'auto:video' }
486+
]
487+
});
488+
489+
const srcs = source.generateSources();
490+
expect(srcs[0].src).toContain('/image/upload/');
491+
expect(srcs[0].src).toContain('e_ai:model_veo-animate');
492+
});
493+
494+
it('should default to video resourceType when not specified', () => {
495+
const source = new VideoSource('sea_turtle', {
496+
cloudinaryConfig: cld
497+
});
498+
499+
const srcs = source.generateSources();
500+
expect(srcs[0].src).toContain('/video/upload/');
501+
});
502+
503+
it('should use image resourceType for poster', () => {
504+
const source = new VideoSource('sample', {
505+
cloudinaryConfig: cld,
506+
resourceType: 'image',
507+
transformation: [
508+
{ effect: 'ai:model_veo-animate' }
509+
]
510+
});
511+
512+
const posterUrl = source.poster().url();
513+
expect(posterUrl).toContain('/image/upload/');
514+
expect(posterUrl).toContain('sample.jpg');
515+
});
516+
517+
it('should use video resourceType for poster by default', () => {
518+
const source = new VideoSource('sea_turtle', {
519+
cloudinaryConfig: cld
520+
});
521+
522+
const posterUrl = source.poster().url();
523+
expect(posterUrl).toContain('/video/upload/');
524+
});
525+
526+
it('should use configured resourceType for seek thumbnails', () => {
527+
const source = new VideoSource('sample', {
528+
cloudinaryConfig: cld,
529+
resourceType: 'image',
530+
transformation: [
531+
{ effect: 'ai:model_veo-animate' }
532+
]
533+
});
534+
535+
const vttUrl = source.config().url('sample.vtt', {
536+
transformation: { flags: ['sprite'] },
537+
resource_type: source.resourceType()
538+
});
539+
expect(vttUrl).toContain('/image/upload/');
540+
expect(vttUrl).toContain('fl_sprite');
541+
});
542+
543+
it('should default to video resourceType for seek thumbnails when not specified', () => {
544+
const source = new VideoSource('sea_turtle', {
545+
cloudinaryConfig: cld
546+
});
547+
548+
const resourceType = source.resourceType() || 'video';
549+
const vttUrl = source.config().url('sea_turtle.vtt', {
550+
transformation: { flags: ['sprite'] },
551+
resource_type: resourceType
552+
});
553+
554+
// When resourceType is not set, resourceType() returns undefined, so we default to 'video'
555+
expect(resourceType).toBe('video');
556+
expect(vttUrl).toContain('/video/upload/');
557+
expect(vttUrl).toContain('fl_sprite');
558+
});
559+
});
477560
});

0 commit comments

Comments
 (0)