Skip to content

Commit 7db2cca

Browse files
committed
feat(VIDEO-20042): image to video (resourceType: 'image')
1 parent 2c5b4ed commit 7db2cca

File tree

9 files changed

+239
-9
lines changed

9 files changed

+239
-9
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Cloudinary Video Player</title>
6+
<link
7+
href="https://res.cloudinary.com/cloudinary-marketing/image/upload/f_auto,q_auto/c_scale,w_32,e_hue:290/creative_staging/cloudinary_internal/Website/Brand%20Updates/Favicon/cloudinary_web_favicon_192x192.png"
8+
rel="icon"
9+
type="image/png"
10+
/>
11+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
12+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
13+
</head>
14+
<body>
15+
<div class="container p-4 col-12 col-md-9 col-xl-6">
16+
<nav class="nav mb-2">
17+
<a href="/index.html">&#60;&#60; Back to examples index</a>
18+
</nav>
19+
<h1>Cloudinary Video Player</h1>
20+
21+
<h3 class="mb-4">Image to Video</h3>
22+
23+
<video id="player" playsinline controls muted autoplay class="cld-video-player" width="500"></video>
24+
25+
<p class="mt-4">
26+
<a href="https://cloudinary.com/documentation/cloudinary_video_player"
27+
>Full documentation</a
28+
>
29+
</p>
30+
</div>
31+
32+
<script type="module">
33+
import { videoPlayer } from 'cloudinary-video-player';
34+
import 'cloudinary-video-player/cld-video-player.min.css';
35+
36+
const player = videoPlayer('player', {
37+
cloudName: 'dz6ajwh6k',
38+
publicId: 'sample',
39+
resourceType: 'image',
40+
transformation: [
41+
{effect: 'ai:model_veo-animate;details_(prompt_!flying bee!)'},
42+
{format: 'auto:video'}
43+
]
44+
});
45+
</script>
46+
47+
<!-- Bootstrap -->
48+
<link
49+
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
50+
rel="stylesheet"
51+
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
52+
crossorigin="anonymous"
53+
/>
54+
</body>
55+
</html>
56+

docs/es-modules/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ <h3 class="mt-4">Code examples:</h3>
6060
<li><a href="./floating-player.html">Floating Player</a></li>
6161
<li><a href="./fluid.html">Fluid Layouts</a></li>
6262
<li><a href="./force-hls-subtitles.html">Force HLS Subtitles</a></li>
63+
<li><a href="./image-to-video.html">Image to Video</a></li>
6364
<li><a href="./interaction-area.html">Interaction Area</a></li>
6465
<li><a href="./multiple-players.html">Multiple Players</a></li>
6566
<li><a href="./playlist.html">Playlist</a></li>

docs/image-to-video.html

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Cloudinary Video Player</title>
6+
<link href="https://res.cloudinary.com/cloudinary-marketing/image/upload/f_auto,q_auto/c_scale,w_32/v1597183771/creative_staging/cloudinary_internal/Website/Brand%20Updates/Favicon/cloudinary_web_favicon_192x192.png" rel="icon" type="image/png">
7+
8+
<!-- Bootstrap -->
9+
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
10+
11+
<!-- highlight.js -->
12+
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/solarized-light.min.css">
13+
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
14+
<script>hljs.initHighlightingOnLoad();</script>
15+
16+
<!--
17+
We're loading scripts & style dynamically for development/testing.
18+
Real-world usage would look like this:
19+
20+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/cloudinary-video-player/dist/cld-video-player.min.css">
21+
<script src="https://cdn.jsdelivr.net/npm/cloudinary-video-player/dist/cld-video-player.min.js"></script>
22+
-->
23+
24+
<script type="text/javascript" src="./scripts.js"></script>
25+
26+
<script type="text/javascript">
27+
window.addEventListener('load', function(){
28+
29+
var player = cloudinary.videoPlayer('player', {
30+
cloudName: 'dz6ajwh6k',
31+
publicId: 'sample',
32+
resourceType: 'image',
33+
transformation: [
34+
{effect: 'ai:model_veo-animate;details_(prompt_!flying bee!)'},
35+
{format: 'auto:video'}
36+
]
37+
});
38+
39+
}, false);
40+
</script>
41+
42+
</head>
43+
<body>
44+
<div class="container p-4 col-12 col-md-9 col-xl-6">
45+
<nav class="nav mb-2">
46+
<a href="./index.html">&#60;&#60; Back to examples index</a>
47+
</nav>
48+
<h1>Cloudinary Video Player</h1>
49+
<h3 class="mb-4">Image to Video</h3>
50+
51+
<video
52+
id="player"
53+
playsinline
54+
controls
55+
muted
56+
autoplay
57+
class="cld-video-player"
58+
width="500"
59+
></video>
60+
61+
<p class="mt-4">
62+
<a href="https://cloudinary.com/documentation/cloudinary_video_player">Full documentation</a>
63+
</p>
64+
65+
<h3 class="mt-4">Example Code:</h3>
66+
<pre>
67+
<code class="language-html">
68+
&lt;video
69+
id="player"
70+
playsinline
71+
controls
72+
muted
73+
autoplay
74+
class="cld-video-player"
75+
width="500"
76+
&gt;&lt;/video&gt;
77+
</code>
78+
<code class="language-javascript">
79+
80+
// Initialize player with image resource type
81+
var player = cloudinary.videoPlayer('player', {
82+
cloudName: 'dz6ajwh6k',
83+
publicId: 'sample',
84+
resourceType: 'image',
85+
transformation: [
86+
{effect: 'ai:model_veo-animate;details_(prompt_!flying bee!)'},
87+
{format: 'auto:video'}
88+
]
89+
});
90+
</code>
91+
</pre>
92+
</div>
93+
94+
</body>
95+
</html>
96+

docs/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ <h3 class="mt-4">Some code examples:</h3>
6464
<li><a href="./floating-player.html">Floating Player</a></li>
6565
<li><a href="./fluid.html">Fluid Layouts</a></li>
6666
<li><a href="./force-hls-subtitles-ios.html">Force HLS Subtitles</a></li>
67+
<li><a href="./image-to-video.html">Image to Video</a></li>
6768
<li><a href="./interaction-area.html">Interaction Area</a></li>
6869
<li><a href="./live-streaming.html">Live Streaming</a></li>
6970
<li><a href="./multiple-players.html">Multiple Players</a></li>

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
export const DEFAULT_POSTER_PARAMS = { format: 'jpg', resource_type: 'video' };
22

33
export const DEFAULT_VIDEO_PARAMS = {
4-
resource_type: 'video',
54
type: 'upload',
65
transformation: [],
76
sourceTransformation: {},

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ class VideoSource extends BaseSource {
3434
options = Object.assign({}, DEFAULT_VIDEO_PARAMS, options);
3535

3636
if (!options.poster) {
37-
options.poster = Object.assign({ publicId }, DEFAULT_POSTER_PARAMS);
37+
options.poster = Object.assign({ publicId }, DEFAULT_POSTER_PARAMS, {
38+
resource_type: options.resourceType || 'video'
39+
});
3840
}
3941

4042
super(publicId, options);
@@ -67,18 +69,18 @@ class VideoSource extends BaseSource {
6769
this[prop](options[prop]);
6870
}
6971
});
70-
72+
7173
// Initialize poster
7274
this.poster(options.poster);
73-
75+
7476
this.objectId = generateId();
7577
}
7678

7779
// Helper method to create simple getter/setter methods
7880
_createGetterSetters(properties) {
7981
properties.forEach(prop => {
8082
const privateKey = `_${prop}`;
81-
this[prop] = function(value) {
83+
this[prop] = function (value) {
8284
if (value === undefined) {
8385
// Provide sensible defaults for specific properties
8486
if (prop === 'sourceTypes' && this[privateKey] === undefined) {
@@ -119,7 +121,9 @@ class VideoSource extends BaseSource {
119121

120122
if (!publicId) {
121123
publicId = this.publicId();
122-
options = Object.assign({}, options, DEFAULT_POSTER_PARAMS);
124+
options = Object.assign({}, options, DEFAULT_POSTER_PARAMS, {
125+
resource_type: this.resourceType() || 'video'
126+
});
123127
}
124128

125129
options.cloudinaryConfig = options.cloudinaryConfig || this.cloudinaryConfig();
@@ -149,7 +153,10 @@ class VideoSource extends BaseSource {
149153
opts.transformation = castArray(srcTransformation);
150154
}
151155

152-
Object.assign(opts, { resource_type: 'video', format });
156+
Object.assign(opts, {
157+
resource_type: this.resourceType() || 'video',
158+
format
159+
});
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/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',

src/video-player.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,10 @@ class VideoPlayer extends Utils.mixin(Eventable) {
268268
transformation.flags.push('sprite');
269269

270270
const vttSrc = source.config()
271-
.url(`${publicId}.vtt`, { transformation })
271+
.url(`${publicId}.vtt`, {
272+
transformation,
273+
resource_type: source.resourceType ? source.resourceType() : undefined
274+
})
272275
.replace(/\.json$/, ''); // Handle playlist by tag
273276

274277
// vttThumbnails must be called differently on init and on source update.

test/unit/videoSource.test.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,4 +474,70 @@ 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 image 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+
});
477543
});

0 commit comments

Comments
 (0)