44import java .net .URLDecoder ;
55import java .security .MessageDigest ;
66import java .security .NoSuchAlgorithmException ;
7+ import java .util .ArrayList ;
8+ import java .util .HashMap ;
9+ import java .util .List ;
710import java .util .Locale ;
811import java .util .Map ;
912import java .util .TreeMap ;
1619import com .cloudinary .utils .StringUtils ;
1720
1821public class Url {
22+ private final Cloudinary cloudinary ;
1923 private final Configuration config ;
2024 String publicId = null ;
2125 String type = null ;
22- String resourceType = "image" ;
26+ String resourceType = null ;
2327 String format = null ;
2428 String version = null ;
2529 Transformation transformation = null ;
2630 boolean signUrl ;
2731 String source = null ;
2832 private String urlSuffix ;
2933 private Boolean useRootPath ;
34+ Map <String , Transformation > sourceTransformation = null ;
35+ String [] sourceTypes = null ;
36+ String fallbackContent = null ;
37+ Transformation posterTransformation = null ;
38+ String posterSource = null ;
39+ Url posterUrl = null ;
40+
3041 private static final String CL_BLANK = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" ;
42+ public static final String [] DEFAULT_VIDEO_SOURCE_TYPES = {"webm" , "mp4" , "ogv" };
43+ private static final Pattern VIDEO_EXTENSION_RE = Pattern .compile ("\\ .(" + StringUtils .join (DEFAULT_VIDEO_SOURCE_TYPES , "|" ) + ")$" );
3144
3245 public Url (Cloudinary cloudinary ) {
46+ this .cloudinary = cloudinary ;
3347 this .config = new Configuration (cloudinary .config );
3448 }
49+
50+ public Url clone () {
51+ Url cloned = cloudinary .url ();
52+
53+ cloned .fallbackContent = this .fallbackContent ;
54+ cloned .format = this .format ;
55+ cloned .posterSource = this .posterSource ;
56+ if (this .posterTransformation != null ) cloned .posterTransformation = new Transformation (this .posterTransformation );
57+ if (this .posterUrl != null ) cloned .posterUrl = this .posterUrl .clone ();
58+ cloned .publicId = this .publicId ;
59+ cloned .resourceType = this .resourceType ;
60+ cloned .signUrl = this .signUrl ;
61+ cloned .source = this .source ;
62+ if (this .transformation != null ) cloned .transformation = new Transformation (this .transformation );
63+ if (this .sourceTransformation != null ) {
64+ cloned .sourceTransformation = new HashMap <String , Transformation >();
65+ for (Map .Entry <String , Transformation > keyValuePair : this .sourceTransformation .entrySet ()) {
66+ cloned .sourceTransformation .put (keyValuePair .getKey (), keyValuePair .getValue ());
67+ };
68+ }
69+ cloned .sourceTypes = this .sourceTypes ;
70+ cloned .urlSuffix = this .urlSuffix ;
71+ cloned .useRootPath = this .useRootPath ;
72+
73+ return cloned ;
74+ }
3575
3676 private static Pattern identifierPattern = Pattern .compile ("^(?:([^/]+)/)??(?:([^/]+)/)??(?:v(\\ d+)/)?" + "(?:([^#/]+?)(?:\\ .([^.#/]+))?)(?:#([^/]+))?$" );
3777
@@ -170,6 +210,78 @@ public Url signed(boolean signUrl) {
170210 this .signUrl = signUrl ;
171211 return this ;
172212 }
213+
214+ public Url sourceTransformation (Map <String , Transformation > sourceTransformation ) {
215+ this .sourceTransformation = sourceTransformation ;
216+ return this ;
217+ }
218+
219+ public Url sourceTransformationFor (String source , Transformation transformation ) {
220+ if (this .sourceTransformation == null ) {
221+ this .sourceTransformation = new HashMap <String , Transformation >();
222+ }
223+ this .sourceTransformation .put (source , transformation );
224+ return this ;
225+ }
226+
227+ public Url sourceTypes (String [] sourceTypes ) {
228+ this .sourceTypes = sourceTypes ;
229+ return this ;
230+ }
231+
232+ public Url fallbackContent (String fallbackContent ) {
233+ this .fallbackContent = fallbackContent ;
234+ return this ;
235+ }
236+
237+ public Url posterTransformation (Transformation posterTransformation ) {
238+ this .posterTransformation = posterTransformation ;
239+ return this ;
240+ }
241+
242+ @ SuppressWarnings ("rawtypes" )
243+ public Url posterTransformation (List <Map > posterTransformations ) {
244+ this .posterTransformation = new Transformation (posterTransformations );
245+ return this ;
246+ }
247+
248+ @ SuppressWarnings ({ "rawtypes" , "unchecked" })
249+ public Url posterTransformation (Map posterTransformations ) {
250+ List <Map > transformations = new ArrayList <Map >();
251+ Map copy = new HashMap ();
252+ copy .putAll (posterTransformations );
253+ transformations .add (copy );
254+ this .posterTransformation = new Transformation (transformations );
255+ return this ;
256+ }
257+
258+ public Url posterSource (String posterSource ) {
259+ this .posterSource = posterSource ;
260+ return this ;
261+ }
262+
263+ public Url posterUrl (Url posterUrl ) {
264+ this .posterUrl = posterUrl ;
265+ return this ;
266+ }
267+
268+ public Url poster (Object poster ) {
269+ if (poster instanceof Transformation ) {
270+ return posterTransformation ((Transformation ) poster );
271+ } else if (poster instanceof List ) {
272+ return posterTransformation ((List ) poster );
273+ } else if (poster instanceof Map ) {
274+ return posterTransformation ((Map ) poster );
275+ } else if (poster instanceof Url ) {
276+ return posterUrl ((Url ) poster );
277+ } else if (poster instanceof String ) {
278+ return posterSource ((String ) poster );
279+ } else if (poster == null || poster .equals (Boolean .FALSE )){
280+ return posterSource ("" );
281+ } else {
282+ throw new IllegalArgumentException ("Illegal value type supplied to poster. must be one of: <Transformation>, <List<Map>>, <Map>, <Url>, <String>" );
283+ }
284+ }
173285
174286 public String generate () {
175287 return generate (null );
@@ -195,16 +307,17 @@ public String generate(String source) {
195307 }
196308 }
197309
198-
199-
200310 if (source == null ) {
201311 if (publicId == null ) {
202- return null ;
312+ if (this .source == null ) {
313+ return null ;
314+ }
315+ source = this .source ;
316+ } else {
317+ source = publicId ;
203318 }
204- source = publicId ;
205319 }
206320
207-
208321 if (source .toLowerCase (Locale .US ).matches ("^https?:/.*" )) {
209322 if (StringUtils .isEmpty (type ) || "asset" .equals (type ) ) {
210323 return source ;
@@ -252,6 +365,8 @@ public String generate(String source) {
252365 signature = "s--" + signature .substring (0 , 8 ) + "--" ;
253366 }
254367
368+ String resourceType = this .resourceType ;
369+ if (resourceType == null ) resourceType = "image" ;
255370 String finalResourceType = finalizeResourceType (resourceType ,type ,urlSuffix ,useRootPath ,config .shorten );
256371 String prefix = unsignedDownloadUrlPrefix (source ,config .cloudName ,config .privateCdn ,config .cdnSubdomain ,config .secureCdnSubdomain ,config .cname ,config .secure ,config .secureDistribution );
257372
@@ -379,7 +494,7 @@ public String imageTag(String source) {
379494 }
380495
381496 public String imageTag (Map <String , String > attributes ) {
382- return imageTag ("" , attributes );
497+ return imageTag (null , attributes );
383498 }
384499
385500 public String imageTag (String source , Map <String , String > attributes ) {
@@ -416,6 +531,118 @@ public String imageTag(String source, Map<String, String> attributes) {
416531 builder .append ("/>" );
417532 return builder .toString ();
418533 }
534+
535+ public String videoTag () {
536+ return videoTag ("" , new HashMap <String , String >());
537+ }
538+
539+ public String videoTag (Map <String , String > attributes ) {
540+ return videoTag ("" , attributes );
541+ }
542+
543+ private String finalizePosterUrl (String source ) {
544+ String posterUrl = null ;
545+ if (this .posterUrl != null ) {
546+ posterUrl = this .posterUrl .generate ();
547+ } else if (this .posterTransformation != null ) {
548+ posterUrl = this .clone ().format ("jpg" ).transformation (new Transformation (this .posterTransformation ))
549+ .generate (source );
550+ } else if (this .posterSource != null ) {
551+ if (!StringUtils .isEmpty (this .posterSource ))
552+ posterUrl = this .clone ().format ("jpg" ).generate (this .posterSource );
553+ } else {
554+ posterUrl = this .clone ().format ("jpg" ).generate (source );
555+ }
556+ return posterUrl ;
557+ }
558+
559+ private void appendVideoSources (StringBuilder html , String source , String sourceType ) {
560+ Url sourceUrl = this .clone ();
561+ if (this .sourceTransformation != null ) {
562+ Transformation transformation = this .transformation ;
563+ Transformation sourceTransformation = null ;
564+ if (this .sourceTransformation .get (sourceType ) != null )
565+ sourceTransformation = new Transformation (this .sourceTransformation .get (sourceType ));
566+ if (transformation == null ) {
567+ transformation = sourceTransformation ;
568+ } else if (sourceTransformation != null ) {
569+ transformation = transformation .chainWith (sourceTransformation );
570+ }
571+ sourceUrl .transformation (transformation );
572+ }
573+ String src = sourceUrl .format (sourceType ).generate (source );
574+ String videoType = sourceType ;
575+ if (sourceType .equals ("ogv" ))
576+ videoType = "ogg" ;
577+ String mimeType = "video/" + videoType ;
578+ html .append ("<source src='" ).append (src ).append ("' type='" ).append (mimeType ).append ("'>" );
579+ }
580+
581+ public String videoTag (String source , Map <String , String > attributes ) {
582+ if (StringUtils .isEmpty (source ))
583+ source = this .source ;
584+ if (StringUtils .isEmpty (source ))
585+ source = publicId ;
586+ if (StringUtils .isEmpty (source ))
587+ throw new IllegalArgumentException ("must supply source or public id" );
588+ source = VIDEO_EXTENSION_RE .matcher (source ).replaceFirst ("" );
589+
590+ if (this .resourceType == null ) this .resourceType = "video" ;
591+ attributes = new TreeMap <String , String >(attributes ); // Make sure they are ordered.
592+
593+ String [] sourceTypes = this .sourceTypes ;
594+
595+ if (sourceTypes == null ) {
596+ sourceTypes = DEFAULT_VIDEO_SOURCE_TYPES ;
597+ }
598+
599+ String posterUrl = this .finalizePosterUrl (source );
600+
601+ if (!StringUtils .isEmpty (posterUrl ))
602+ attributes .put ("poster" , posterUrl );
603+
604+ StringBuilder html = new StringBuilder ().append ("<video" );
605+
606+ String url = null ;
607+
608+ boolean multiSource = sourceTypes .length > 1 ;
609+ if (!multiSource ) {
610+ url = generate (source + "." + sourceTypes [0 ]);
611+ attributes .put ("src" , url );
612+ } else {
613+ generate (source );
614+ }
615+
616+ if (this .transformation .getHtmlHeight () != null )
617+ attributes .put ("height" , this .transformation .getHtmlHeight ());
618+ if (attributes .containsKey ("html_height" ))
619+ attributes .put ("height" , attributes .remove ("html_height" ));
620+ if (this .transformation .getHtmlWidth () != null )
621+ attributes .put ("width" , this .transformation .getHtmlWidth ());
622+ if (attributes .containsKey ("html_width" ))
623+ attributes .put ("width" , attributes .remove ("html_width" ));
624+
625+ for (Map .Entry <String , String > attr : attributes .entrySet ()) {
626+ html .append (" " ).append (attr .getKey ());
627+ if (attr .getValue () != null ) {
628+ String value = ObjectUtils .asString (attr .getValue ());
629+ html .append ("='" ).append (value ).append ("'" );
630+ }
631+ }
632+
633+ html .append (">" );
634+
635+ if (multiSource ) {
636+ for (String sourceType : sourceTypes ) {
637+ this .appendVideoSources (html , source , sourceType );
638+ }
639+ }
640+
641+ if (this .fallbackContent != null )
642+ html .append (this .fallbackContent );
643+ html .append ("</video>" );
644+ return html .toString ();
645+ }
419646
420647 public String generateSpriteCss (String source ) {
421648 this .type = "sprite" ;
0 commit comments