Skip to content

Commit 1b5be87

Browse files
Merge pull request #629 from cloudinary/fetch-overlay-video-bug
fix: fetch overlay video creates correct transformation
2 parents f4d7ebf + 0163f36 commit 1b5be87

File tree

6 files changed

+293
-127
lines changed

6 files changed

+293
-127
lines changed

lib-es5/utils/encoding/smart_escape.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
"use strict";
22

33
// Based on CGI::unescape. In addition does not escape / :
4-
// smart_escape = (string)->
5-
// encodeURIComponent(string).replace(/%3A/g, ":").replace(/%2F/g, "/")
4+
// smart_escape = (string) => encodeURIComponent(string).replace(/%3A/g, ":").replace(/%2F/g, "/")
65
function smart_escape(string) {
76
var unsafe = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : /([^a-zA-Z0-9_.\-\/:]+)/g;
87

lib-es5/utils/index.js

Lines changed: 99 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ function textStyle(layer) {
131131
});
132132

133133
if (layer.hasOwnProperty("font_size" || "font_family") || !isEmpty(keywords)) {
134-
if (!layer.font_size) throw `Must supply font_size for text in overlay/underlay`;
135-
if (!layer.font_family) throw `Must supply font_family for text in overlay/underlay`;
134+
if (!layer.font_size) throw new Error('Must supply font_size for text in overlay/underlay');
135+
if (!layer.font_family) throw new Error('Must supply font_family for text in overlay/underlay');
136136
keywords.unshift(layer.font_size);
137137
keywords.unshift(layer.font_family);
138138
style = compact(keywords).join("_");
@@ -222,71 +222,107 @@ function process_if(ifValue) {
222222
* @return {string} layer transformation string
223223
*/
224224
function process_layer(layer) {
225-
var result = '';
226-
if (isPlainObject(layer)) {
227-
if (layer.resource_type === "fetch" || layer.url != null) {
228-
result = `fetch:${base64EncodeURL(layer.url)}`;
225+
if (isString(layer)) {
226+
var resourceType = null;
227+
var layerUrl = '';
228+
229+
var fetchLayerBegin = 'fetch:';
230+
if (layer.startsWith(fetchLayerBegin)) {
231+
layerUrl = layer.substring(fetchLayerBegin.length);
232+
} else if (layer.indexOf(':fetch:', 0) !== -1) {
233+
var parts = layer.split(':', 3);
234+
resourceType = parts[0];
235+
layerUrl = parts[2];
229236
} else {
230-
var public_id = layer.public_id;
231-
var format = layer.format;
232-
var resource_type = layer.resource_type || "image";
233-
var type = layer.type || "upload";
234-
var text = layer.text;
235-
var style = null;
236-
var components = [];
237-
var noPublicId = isEmpty(public_id);
238-
if (!noPublicId) {
239-
public_id = public_id.replace(new RegExp("/", 'g'), ":");
240-
if (format != null) {
241-
public_id = `${public_id}.${format}`;
242-
}
243-
}
244-
if (isEmpty(text) && resource_type !== "text") {
245-
if (noPublicId) {
246-
throw "Must supply public_id for resource_type layer_parameter";
247-
}
248-
if (resource_type === "subtitles") {
249-
style = textStyle(layer);
250-
}
251-
} else {
252-
resource_type = "text";
253-
type = null;
254-
// type is ignored for text layers
255-
style = textStyle(layer);
256-
if (!isEmpty(text)) {
257-
var noStyle = isEmpty(style);
258-
if (!(noPublicId || noStyle) || noPublicId && noStyle) {
259-
throw "Must supply either style parameters or a public_id when providing text parameter in a text overlay/underlay";
260-
}
261-
var re = /\$\([a-zA-Z]\w*\)/g;
262-
var start = 0;
263-
var textSource = smart_escape(decodeURIComponent(text), /[,\/]/g);
264-
text = "";
265-
for (var res = re.exec(textSource); res; res = re.exec(textSource)) {
266-
text += smart_escape(textSource.slice(start, res.index));
267-
text += res[0];
268-
start = res.index + res[0].length;
269-
}
270-
text += encodeURIComponent(textSource.slice(start));
271-
}
272-
}
273-
if (resource_type !== "image") {
274-
components.push(resource_type);
275-
}
276-
if (type !== "upload") {
277-
components.push(type);
278-
}
279-
components.push(style);
237+
return layer;
238+
}
239+
240+
layer = {
241+
url: layerUrl,
242+
type: 'fetch'
243+
};
244+
245+
if (resourceType) {
246+
layer.resource_type = resourceType;
247+
}
248+
}
249+
250+
if (typeof layer !== 'object') {
251+
return layer;
252+
}
253+
254+
var _layer = layer,
255+
resource_type = _layer.resource_type,
256+
text = _layer.text,
257+
type = _layer.type,
258+
public_id = _layer.public_id,
259+
format = _layer.format,
260+
fetchUrl = _layer.url;
261+
262+
var components = [];
263+
264+
if (!isEmpty(text) && isEmpty(resource_type)) {
265+
resource_type = 'text';
266+
}
267+
268+
if (!isEmpty(fetchUrl) && isEmpty(type)) {
269+
type = 'fetch';
270+
}
271+
272+
if (!isEmpty(public_id) && !isEmpty(format)) {
273+
public_id = `${public_id}.${format}`;
274+
}
275+
276+
if (isEmpty(public_id) && resource_type !== 'text' && type !== 'fetch') {
277+
throw new Error('Must supply public_id for non-text overlay');
278+
}
279+
280+
if (!isEmpty(resource_type) && resource_type !== 'image') {
281+
components.push(resource_type);
282+
}
283+
284+
if (!isEmpty(type) && type !== 'upload') {
285+
components.push(type);
286+
}
287+
288+
if (resource_type === 'text' || resource_type === 'subtitles') {
289+
if (isEmpty(public_id) && isEmpty(text)) {
290+
throw new Error('Must supply either text or public_in in overlay');
291+
}
292+
293+
var textOptions = textStyle(layer);
294+
295+
if (!isEmpty(textOptions)) {
296+
components.push(textOptions);
297+
}
298+
299+
if (!isEmpty(public_id)) {
300+
public_id = public_id.replace('/', ':');
280301
components.push(public_id);
302+
}
303+
304+
if (!isEmpty(text)) {
305+
var re = /\$\([a-zA-Z]\w*\)/g;
306+
var start = 0;
307+
var textSource = smart_escape(decodeURIComponent(text), /[,\/]/g);
308+
text = "";
309+
for (var res = re.exec(textSource); res; res = re.exec(textSource)) {
310+
text += smart_escape(textSource.slice(start, res.index));
311+
text += res[0];
312+
start = res.index + res[0].length;
313+
}
314+
text += encodeURIComponent(textSource.slice(start));
281315
components.push(text);
282-
result = compact(components).join(":");
283316
}
284-
} else if (/^fetch:.+/.test(layer)) {
285-
result = `fetch:${base64EncodeURL(layer.substr(6))}`;
317+
} else if (type === 'fetch') {
318+
var encodedUrl = base64EncodeURL(fetchUrl);
319+
components.push(encodedUrl);
286320
} else {
287-
result = layer;
321+
public_id = public_id.replace('/', ':');
322+
components.push(public_id);
288323
}
289-
return result;
324+
325+
return components.join(':');
290326
}
291327

292328
/**
@@ -1547,6 +1583,8 @@ function archive_params() {
15471583
};
15481584
}
15491585

1586+
exports.process_layer = process_layer;
1587+
15501588
exports.create_source_tag = function create_source_tag(src, source_type) {
15511589
var codecs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
15521590

lib/utils/encoding/smart_escape.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Based on CGI::unescape. In addition does not escape / :
2-
// smart_escape = (string)->
3-
// encodeURIComponent(string).replace(/%3A/g, ":").replace(/%2F/g, "/")
2+
// smart_escape = (string) => encodeURIComponent(string).replace(/%3A/g, ":").replace(/%2F/g, "/")
43
function smart_escape(string, unsafe = /([^a-zA-Z0-9_.\-\/:]+)/g) {
54
return string.replace(unsafe, function (match) {
65
return match.split("").map(function (c) {

lib/utils/index.js

Lines changed: 99 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ function textStyle(layer) {
121121
});
122122

123123
if (layer.hasOwnProperty("font_size" || "font_family") || !isEmpty(keywords)) {
124-
if (!layer.font_size) throw `Must supply font_size for text in overlay/underlay`;
125-
if (!layer.font_family) throw `Must supply font_family for text in overlay/underlay`;
124+
if (!layer.font_size) throw new Error('Must supply font_size for text in overlay/underlay');
125+
if (!layer.font_family) throw new Error('Must supply font_family for text in overlay/underlay');
126126
keywords.unshift(layer.font_size);
127127
keywords.unshift(layer.font_family);
128128
style = compact(keywords).join("_");
@@ -206,71 +206,107 @@ function process_if(ifValue) {
206206
* @return {string} layer transformation string
207207
*/
208208
function process_layer(layer) {
209-
let result = '';
210-
if (isPlainObject(layer)) {
211-
if (layer.resource_type === "fetch" || (layer.url != null)) {
212-
result = `fetch:${base64EncodeURL(layer.url)}`;
209+
if (isString(layer)) {
210+
let resourceType = null;
211+
let layerUrl = '';
212+
213+
let fetchLayerBegin = 'fetch:';
214+
if (layer.startsWith(fetchLayerBegin)) {
215+
layerUrl = layer.substring(fetchLayerBegin.length);
216+
} else if (layer.indexOf(':fetch:', 0) !== -1) {
217+
const parts = layer.split(':', 3);
218+
resourceType = parts[0];
219+
layerUrl = parts[2];
213220
} else {
214-
let public_id = layer.public_id;
215-
let format = layer.format;
216-
let resource_type = layer.resource_type || "image";
217-
let type = layer.type || "upload";
218-
let text = layer.text;
219-
let style = null;
220-
let components = [];
221-
const noPublicId = isEmpty(public_id);
222-
if (!noPublicId) {
223-
public_id = public_id.replace(new RegExp("/", 'g'), ":");
224-
if (format != null) {
225-
public_id = `${public_id}.${format}`;
226-
}
227-
}
228-
if (isEmpty(text) && resource_type !== "text") {
229-
if (noPublicId) {
230-
throw "Must supply public_id for resource_type layer_parameter";
231-
}
232-
if (resource_type === "subtitles") {
233-
style = textStyle(layer);
234-
}
235-
} else {
236-
resource_type = "text";
237-
type = null;
238-
// type is ignored for text layers
239-
style = textStyle(layer);
240-
if (!isEmpty(text)) {
241-
const noStyle = isEmpty(style);
242-
if (!(noPublicId || noStyle) || (noPublicId && noStyle)) {
243-
throw "Must supply either style parameters or a public_id when providing text parameter in a text overlay/underlay";
244-
}
245-
let re = /\$\([a-zA-Z]\w*\)/g;
246-
let start = 0;
247-
let textSource = smart_escape(decodeURIComponent(text), /[,\/]/g);
248-
text = "";
249-
for (let res = re.exec(textSource); res; res = re.exec(textSource)) {
250-
text += smart_escape(textSource.slice(start, res.index));
251-
text += res[0];
252-
start = res.index + res[0].length;
253-
}
254-
text += encodeURIComponent(textSource.slice(start));
255-
}
256-
}
257-
if (resource_type !== "image") {
258-
components.push(resource_type);
259-
}
260-
if (type !== "upload") {
261-
components.push(type);
262-
}
263-
components.push(style);
221+
return layer;
222+
}
223+
224+
layer = {
225+
url: layerUrl,
226+
type: 'fetch'
227+
};
228+
229+
if (resourceType) {
230+
layer.resource_type = resourceType;
231+
}
232+
}
233+
234+
if (typeof layer !== 'object') {
235+
return layer;
236+
}
237+
238+
let {
239+
resource_type,
240+
text,
241+
type,
242+
public_id,
243+
format,
244+
url: fetchUrl
245+
} = layer;
246+
const components = [];
247+
248+
if (!isEmpty(text) && isEmpty(resource_type)) {
249+
resource_type = 'text';
250+
}
251+
252+
if (!isEmpty(fetchUrl) && isEmpty(type)) {
253+
type = 'fetch';
254+
}
255+
256+
if (!isEmpty(public_id) && !isEmpty(format)) {
257+
public_id = `${public_id}.${format}`;
258+
}
259+
260+
if (isEmpty(public_id) && resource_type !== 'text' && type !== 'fetch') {
261+
throw new Error('Must supply public_id for non-text overlay');
262+
}
263+
264+
if (!isEmpty(resource_type) && resource_type !== 'image') {
265+
components.push(resource_type);
266+
}
267+
268+
if (!isEmpty(type) && type !== 'upload') {
269+
components.push(type);
270+
}
271+
272+
if (resource_type === 'text' || resource_type === 'subtitles') {
273+
if (isEmpty(public_id) && isEmpty(text)) {
274+
throw new Error('Must supply either text or public_in in overlay');
275+
}
276+
277+
const textOptions = textStyle(layer);
278+
279+
if (!isEmpty(textOptions)) {
280+
components.push(textOptions);
281+
}
282+
283+
if (!isEmpty(public_id)) {
284+
public_id = public_id.replace('/', ':');
264285
components.push(public_id);
286+
}
287+
288+
if (!isEmpty(text)) {
289+
let re = /\$\([a-zA-Z]\w*\)/g;
290+
let start = 0;
291+
let textSource = smart_escape(decodeURIComponent(text), /[,\/]/g);
292+
text = "";
293+
for (let res = re.exec(textSource); res; res = re.exec(textSource)) {
294+
text += smart_escape(textSource.slice(start, res.index));
295+
text += res[0];
296+
start = res.index + res[0].length;
297+
}
298+
text += encodeURIComponent(textSource.slice(start));
265299
components.push(text);
266-
result = compact(components).join(":");
267300
}
268-
} else if (/^fetch:.+/.test(layer)) {
269-
result = `fetch:${base64EncodeURL(layer.substr(6))}`;
301+
} else if (type === 'fetch') {
302+
const encodedUrl = base64EncodeURL(fetchUrl);
303+
components.push(encodedUrl);
270304
} else {
271-
result = layer;
305+
public_id = public_id.replace('/', ':');
306+
components.push(public_id);
272307
}
273-
return result;
308+
309+
return components.join(':');
274310
}
275311

276312
/**
@@ -1437,6 +1473,8 @@ function archive_params(options = {}) {
14371473
};
14381474
}
14391475

1476+
exports.process_layer = process_layer;
1477+
14401478
exports.create_source_tag = function create_source_tag(src, source_type, codecs = null) {
14411479
let video_type = source_type === 'ogv' ? 'ogg' : source_type;
14421480
let mime_type = `video/${video_type}`;

0 commit comments

Comments
 (0)