From e011ae029596d9118018b298e208638336658885 Mon Sep 17 00:00:00 2001 From: RTLcoil Date: Tue, 14 May 2019 17:34:23 +0300 Subject: [PATCH] Added support for sources in video tag (Update video HTML 5 tag to include support for h265 and vp9) --- lib-es5/cloudinary.js | 33 +++++---- lib-es5/utils/index.js | 15 ++++- lib/cloudinary.js | 31 +++++---- lib/utils/index.js | 13 +++- test/spechelper.js | 21 ++++++ test/video_spec.js | 147 ++++++++++++++++++++++++++++++++++++++++- 6 files changed, 230 insertions(+), 30 deletions(-) diff --git a/lib-es5/cloudinary.js b/lib-es5/cloudinary.js index 4be68ae0..af6d94a4 100644 --- a/lib-es5/cloudinary.js +++ b/lib-es5/cloudinary.js @@ -123,6 +123,7 @@ exports.video = function video(public_id, options) { public_id = public_id.replace(/\.(mp4|ogv|webm)$/, ''); var source_types = optionConsume(options, 'source_types', []); var source_transformation = optionConsume(options, 'source_transformation', {}); + var sources = optionConsume(options, 'sources', []); var fallback = optionConsume(options, 'fallback_content', ''); if (source_types.length === 0) source_types = cloudinary.utils.DEFAULT_VIDEO_SOURCE_TYPES; @@ -145,29 +146,35 @@ exports.video = function video(public_id, options) { var html = ''; - return html; + return `${html}${fallback}`; }; /** diff --git a/lib-es5/utils/index.js b/lib-es5/utils/index.js index d9604b73..c71f3e33 100644 --- a/lib-es5/utils/index.js +++ b/lib-es5/utils/index.js @@ -458,8 +458,7 @@ exports.build_custom_headers = function build_custom_headers(headers) { } }; -var TRANSFORMATION_PARAMS = ['angle', 'aspect_ratio', 'audio_codec', 'audio_frequency', 'background', 'bit_rate', 'border', 'color', 'color_space', 'crop', 'default_image', 'delay', 'density', 'dpr', 'duration', 'effect', 'end_offset', 'fetch_format', 'flags', 'fps', 'gravity', 'height', 'if', 'keyframe_interval', 'offset', 'opacity', 'overlay', 'page', 'prefix', 'quality', 'radius', 'raw_transformation', 'responsive_width', 'size', 'start_offset', 'streaming_profile', 'transformation', 'underlay', 'variables', 'video_codec', 'video_sampling', 'width', 'x', 'y', 'zoom' // + any key that starts with '$' -]; +var TRANSFORMATION_PARAMS = ['angle', 'aspect_ratio', 'audio_codec', 'audio_frequency', 'background', 'bit_rate', 'border', 'color', 'color_space', 'crop', 'default_image', 'delay', 'density', 'dpr', 'duration', 'effect', 'end_offset', 'fetch_format', 'flags', 'fps', 'gravity', 'height', 'if', 'keyframe_interval', 'offset', 'opacity', 'overlay', 'page', 'prefix', 'quality', 'radius', 'raw_transformation', 'responsive_width', 'size', 'sources', 'start_offset', 'streaming_profile', 'transformation', 'underlay', 'variables', 'video_codec', 'video_sampling', 'width', 'x', 'y', 'zoom']; exports.generate_transformation_string = function generate_transformation_string(options) { if (utils.isString(options)) { @@ -1327,6 +1326,18 @@ exports.archive_params = function archive_params() { }; }; +exports.create_source_tag = function create_source_tag(src, source_type) { + var codecs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; + + var video_type = source_type === 'ogv' ? 'ogg' : source_type; + var mime_type = `video/${video_type}`; + if (!isEmpty(codecs)) { + var codecs_str = isArray(codecs) ? codecs.join(', ') : codecs; + mime_type += `; codecs=${codecs_str}`; + } + return ``; +}; + exports.build_explicit_api_params = function build_explicit_api_params(public_id) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; diff --git a/lib/cloudinary.js b/lib/cloudinary.js index b666e6b6..ff8c74aa 100644 --- a/lib/cloudinary.js +++ b/lib/cloudinary.js @@ -114,6 +114,7 @@ exports.video = function video(public_id, options) { public_id = public_id.replace(/\.(mp4|ogv|webm)$/, ''); let source_types = optionConsume(options, 'source_types', []); let source_transformation = optionConsume(options, 'source_transformation', {}); + let sources = optionConsume(options, 'sources', []); let fallback = optionConsume(options, 'fallback_content', ''); if (source_types.length === 0) source_types = cloudinary.utils.DEFAULT_VIDEO_SOURCE_TYPES; @@ -136,29 +137,33 @@ exports.video = function video(public_id, options) { let html = ''; - return html; + return `${html}${fallback}`; }; /** diff --git a/lib/utils/index.js b/lib/utils/index.js index bcf7c385..f71e0e42 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -474,6 +474,7 @@ const TRANSFORMATION_PARAMS = [ 'raw_transformation', 'responsive_width', 'size', + 'sources', 'start_offset', 'streaming_profile', 'transformation', @@ -484,7 +485,7 @@ const TRANSFORMATION_PARAMS = [ 'width', 'x', 'y', - 'zoom' // + any key that starts with '$' + 'zoom', // + any key that starts with '$' ]; exports.generate_transformation_string = function generate_transformation_string(options) { @@ -1273,6 +1274,16 @@ exports.archive_params = function archive_params(options = {}) { }; }; +exports.create_source_tag = function create_source_tag(src, source_type, codecs = null) { + let video_type = source_type === 'ogv' ? 'ogg' : source_type; + let mime_type = `video/${video_type}`; + if (!isEmpty(codecs)) { + let codecs_str = isArray(codecs) ? codecs.join(', ') : codecs; + mime_type += `; codecs=${codecs_str}` + } + return `` +} + exports.build_explicit_api_params = function build_explicit_api_params(public_id, options = {}) { return [exports.build_upload_params(extend({}, {public_id}, options))]; }; diff --git a/test/spechelper.js b/test/spechelper.js index a6d87132..f8d0d3a3 100644 --- a/test/spechelper.js +++ b/test/spechelper.js @@ -66,6 +66,27 @@ exports.ICON_FILE = "test/resources/favicon.ico"; exports.IMAGE_URL = "http://res.cloudinary.com/demo/image/upload/sample"; +exports.SAMPLE_VIDEO_SOURCES = [ + { + type: 'mp4', + codecs: 'hev1', + transformations: { video_codec: 'h265' }, + }, + { + type: 'webm', + codecs: 'vp9', + transformations: { video_codec: 'vp9' }, + }, + { + type: 'mp4', + transformations: { video_codec: 'auto' }, + }, + { + type: 'webm', + transformations: { video_codec: 'auto' }, + }, +]; + exports.test_cloudinary_url = function(public_id, options, expected_url, expected_options) { var url; url = utils.url(public_id, options); diff --git a/test/video_spec.js b/test/video_spec.js index f13a1e45..eb1613b2 100644 --- a/test/video_spec.js +++ b/test/video_spec.js @@ -1,9 +1,11 @@ -var cloudinary, expect; +var cloudinary, expect, helper; expect = require('expect.js'); cloudinary = require('../cloudinary'); +helper = require("./spechelper"); + describe("video tag helper", function () { var DEFAULT_UPLOAD_PATH, VIDEO_UPLOAD_PATH; VIDEO_UPLOAD_PATH = "http://res.cloudinary.com/test123/video/upload/"; @@ -154,4 +156,147 @@ describe("video tag helper", function () { expect(options.video_codec).to.eql('auto'); expect(options.autoplay).to.be.true; }); + + describe('sources', function() { + const expected_url = VIDEO_UPLOAD_PATH + 'movie'; + const expected_url_mp4 = VIDEO_UPLOAD_PATH + 'vc_auto/movie.mp4'; + const expected_url_webm = VIDEO_UPLOAD_PATH + 'vc_auto/movie.webm'; + it('should generate video tag with default sources if not given sources or source_types', function() { + expect(cloudinary.video('movie')).to.eql( + `' + ); + }); + it('should generate video tag with given custom sources', function() { + var custom_sources = [ + { + type: 'mp4', + }, + { + type: 'webm', + }, + ]; + expect( + cloudinary.video('movie', { + sources: custom_sources, + }) + ).to.eql( + `` + ); + }); + it('should generate video tag overriding source_types with sources if both are given', function() { + var custom_sources = [ + { + type: 'mp4', + }, + ]; + expect( + cloudinary.video('movie', { + sources: custom_sources, + source_types: ['ogv', 'mp4', 'webm'], + }) + ).to.eql( + `` + ); + }); + it('should correctly handle ogg/ogv', function() { + expect( + cloudinary.video('movie', { + sources: [{ type: 'ogv' }], + }) + ).to.eql( + `` + ); + }); + it('should generate video tag with sources with codecs string', function() { + var custom_sources = [ + { + type: 'mp4', + codecs: 'vp8, vorbis', + transformations: { video_codec: 'auto' }, + }, + { + type: 'webm', + codecs: 'avc1.4D401E, mp4a.40.2', + transformations: { video_codec: 'auto' }, + }, + ]; + expect( + cloudinary.video('movie', { + sources: custom_sources, + }) + ).to.eql( + `` + ); + }); + it('should generate video tag with sources with codecs arrays', function() { + var custom_sources = [ + { + type: 'mp4', + codecs: ['vp8', 'vorbis'], + transformations: { video_codec: 'auto' }, + }, + { + type: 'webm', + codecs: ['avc1.4D401E', 'mp4a.40.2'], + transformations: { video_codec: 'auto' }, + }, + ]; + expect( + cloudinary.video('movie', { + sources: custom_sources, + }) + ).to.eql( + `` + ); + }); + it('should generate video tag with sources and transformations', function() { + const options = { + source_types: 'mp4', + html_height: '100', + html_width: '200', + video_codec: { codec: 'h264' }, + audio_codec: 'acc', + start_offset: 3, + sources: helper.SAMPLE_VIDEO_SOURCES, + }; + const expected_poster_url = + VIDEO_UPLOAD_PATH + 'ac_acc,so_3,vc_h264/movie.jpg'; + const expected_url_mp4_codecs = + VIDEO_UPLOAD_PATH + 'ac_acc,so_3,vc_h265/movie.mp4'; + const expected_url_webm_codecs = + VIDEO_UPLOAD_PATH + 'ac_acc,so_3,vc_vp9/movie.webm'; + const expected_url_mp4_audio = + VIDEO_UPLOAD_PATH + 'ac_acc,so_3,vc_auto/movie.mp4'; + const expected_url_webm_audio = + VIDEO_UPLOAD_PATH + 'ac_acc,so_3,vc_auto/movie.webm'; + expect(cloudinary.video('movie', options)).to.eql( + `` + ); + }); + }); });