Quantcast
Channel: Cloudinary Blog
Viewing all 601 articles
Browse latest View live

Meet The Visual Web: World’s Biggest Collection of Images and Rich Media Resources

$
0
0

cover image
Image Source: Vecteezy

Images and video are a core component of websites, mobile apps and our digital life. They deliver exciting content and improve the user experience by delivering the best format, size and quality to meet each web visitor’s unique needs

At the same time, high-quality images and video can be a major challenge for site owners and developers who need to dynamically generate large volumes of images, store them, optimize them for quality and performance, and deliver them to users around the world in an instant.

As a developer, there’s no doubt you want to stay on the cutting-edge of new techniques and best practices for image and video management. You may want to know how to address particular challenges, such as the best way to achieve a desired effect, how to find a balance between image quality and site speed, or how to upload multiple images to different platforms. But often, you may have to bounce between sites and forums to find answers to your questions.

That’s why Cloudinay created The Visual Web. This online community is designed as a one-stop resource where you can discover what’s new in image and video management, and how to apply it to your own projects. Here you can learn more about various image and video formats, optimization, delivery, storage techniques, and best practices for media manipulation and adjustment for websites.

This Visual Web is a collaborative knowledge base that maps out the image and rich media management landscape. The site includes a carefully curated index of thousands of resources on a wide range of image, media and web development topics, written by leading bloggers, developers and technology experts.

Cloudinary took on this project to make the available content about media management easily accessible. The site started with an intensive research project – the first stage was building a tree of more than 300 sub-topics around image and video management, including media use cases, media management and operations, image delivery and content delivery networks (CDNs), image and video manipulation techniques and video formats and transcoding.

The Cloudinary team collected more than 100,000 web pages that cover these subjects, hand-picked the most relevant ones for each category, and divided them into “content types,” such as “how to” articles, real-life examples, vendor information and product comparisons.

The site currently includes about 70 pages of content and is growing fast! If you feel we left something out, let us know and we’ll add it to the list of resources.

Explore, learn and kindly spread the word!


Viral Images: Securing Images and Video uploads to your systems

$
0
0

Viral Images

When was the last time you got paid $40,000 for a few days of work? That is what happened last year to Russian independent security researcher Andrey Leonov, who discovered that if you upload a specially constructed image file to Facebook, you can make Facebook's internal servers, nested deep within their firewalls, run arbitrary commands to expose sensitive internal files in a way that could easily lead to a data breach.

Facebook was lucky that this exploit was discovered by an ethical hacker and not a criminal, and gladly paid him one of the highest bounties ever published for a security bug disclosure.

The bug that Leonov discovered was quite embarrassing, as it revealed that Facebook neglected to update the image processing libraries on its servers. This was the case even though the vulnerability, dubbed Imagetragick, was discovered five months earlier, was widely discussed in tech press and recognized as a high risk threat, and security patches for affected operating systems were made available immediately(1). Last year, it took us 45 minutes to patch our systems against this bug, on the day it was announced, from the moment we found out about it: Imagemagick among other tools is used within some of our image processing pipelines, and we closely follow security announcements about these tools. This is not bragging - we’ve had cases as well where minor, less publicized issues, lingered for weeks before being patched. It does go to show that even one of the most admired software engineering organizations in the world can have its bad days and neglect a trivial patch. On the other hand, Facebook's willingness to publicly discuss and publish this vulnerability is admirable, and promotes the security of the web at large. Stories like this help raise public awareness of the risks in not properly maintaining your servers and software.

How do organizations protect themselves against such risks?

There are several technical and organizational procedural controls that are required for a development organization to stay on top of security risks. In the Facebook case, Leonov collected "low-hanging fruit" via a well-known, published bug. But what about the countless unknown bugs that lurk within the multitude of libraries, software packages and services that are required for an advanced image and video processing system? What about other file formats such as PDF and SVG, which both deserve a special place in the vulnerability hall of shame?

Organizations employ security teams that audit internal software libraries, 3rd-party tools and services, and take care to regularly patch operating systems and software stacks. However, media processing pipelines are especially sensitive to patches and upgrades that change the functionality and resulting outputs, so they are often 'frozen in time' to maintain the same outputs and not break the pipelines.

At Cloudinary, we employ the following principles to guard against risks:

  1. Assign responsibilities. Information security is considered a strategic issue, and is the responsibility of an executive management team member, coordinating the organization-wide efforts.
  2. Create, implement and enforce company wide procedures. We created and maintain a set of security procedures, compatible with the widely acclaimed ISO 27001 information security standard. These procedures apply to every level of company activity, from hiring procedures, to writing code.
  3. Get help. We employ a third-party consulting company specializing in information security standards and procedures to create, maintain and validate enforcement of the controls mentioned above.
  4. Monitor. No system is free of bugs, even if everybody in the dev org is a security expert and you run the best static code analysis and network monitoring tools. We hire external penetration testers to probe our APIs and web applications regularly. Our systems are patched automatically via daily updates whenever possible. Our security team subscribes to the CERT mailing lists in the countries where we have offices, and gets alerts on new vulnerabilities found in the core libraries and operating systems we use. We also regularly pay (albeit smaller that $40,000) bounties to independent researchers who report security issues in our systems. Watch this space for a future announcement of a public bug bounty program - there are several popular services that provide an infrastructure for such programs.
  5. Focus. As a SaaS/API provider, the philosophy of using best-of-breed external providers, where available, is ingrained into our culture. We prefer to focus on our own unique software offering than develop internal systems replicating functionality available elsewhere, so we use API and SaaS providers (after vetting them for security) whenever it is possible. Every line of code your programmers write, or library they use, is a potential bug, and using properly vetted providers can help you outsource non-core functionality to experts whose job is to focus on it.

Is this article designed to sell you on using external services for non-core functionality?

Yes, it absolutely is!

In the build vs. buy dilemma, security considerations have pluses and minuses for both sides. One important thing to consider is that security breaches are an eventuality, not a probability, as your website or app becomes more important and popular. If someone penetrates your systems via a malicious image, video or file upload - whose internal network would you rather be breached - yours or the SaaS provider's? If Facebook had used an external provider for image processing, it would save them from having to maintain, patch, continuously monitor and test their image processing pipelines, and dealing with implications of a hacker snooping around their internal networks. Plus, they would get more image and video processing features than they would ever consider developing in-house, allowing them to experiment with new functionality without having to invest development time and the efforts required to make these capabilities secure.


Footnotes (1) According to Leonov's blog, Facebook's un-patched image libraries were compounded by a misconfigured firewall that allowed internal information to leak out to the internet via DNS tunneling.

Google For Nigeria: We saw it all…

$
0
0

GDG Lagos

Note from Cloudinary: Christian Nwamba, a frequent Cloudinary contributor, recently attended, and was a main speaker, at the Google Developer Group (GDG) Conference in Lagos, Nigeria. Christian led a session teaching more than 500 developers how to “Build Offline Apps for the Next Billion Users.” The stack he used included JS (Vue), Firebase, Service Workers and Cloudinary. Below is his account of the conference and his talk.


When Sundar Pichai got on a plane to Nigeria to herald the latest edition of Google For Nigeria, he knew that there would be more than learning about and enjoying the rich culture of a vast and diverse nation. In addition, he would be meeting with a lot of brilliant minds who play crucial roles in Google’s vision for the internet and its next billion users – Nigerians, who are tech inclined and otherwise budding, vibrant and full of energy. It was this energy that attracted Pichai to the scene. Without the astounding effort from hardworking Nigerians, such as Titi Akinsanmi, head of Policy and Government Relations for Google Africa, the presence of the man who took the helm of Google some two years ago might have never been felt.

It was no surprise that Pichai decided to visit Lagos, the most populated city in Nigeria. While taking a walk in “Computer Village,” the city’s busiest market known for dealing in gadgets and technology, he couldn’t help but admire the versatility and diversity of the average Nigerian.

At Google For Nigeria, ways of improving digital access to the average African were discussed, with emphasis laid on Nigeria, Kenya and South Africa. Some of these concepts include:

Launchpad Accelerator Africa

In a bid to do more than just support African entrepreneurs in creating successful tech startups and content, Google’s Launchpad Accelerator program will make more than $3 million available to more than 60 African startups for equity-free funding, working space and access to expert advisers over three years. Google also created a new Google Launchpad space in Lagos. It’s the first space of it’s kind outside the United States, and it will be where rigorous quarterly programs are held twice a year.

Grants from Google

Google’s charitable arm, google.org, is committed to donating $20 million over the next five years to nonprofit organizations that are working to improve lives across Africa. It also pledged $2.5 million in initial grants to the nonprofit arms of African startups Gidi Mobile and Siyavula to provide free access to education for 400,000 low-income students from South Africa and Nigeria. Google.org will develop new digital learning materials that will be free for anyone to use. In a bid to create further positive impact in Africa, Google also plans to invite nonprofits from across the continent to share insights and innovations on how they could impact their community and beyond. For this purpose, Google will be launching a Google.org Impact Challenge in Africa in 2018 to award $5 million in grants. Applications will be open to any eligible nonprofit in Africa and the best ideas will be selected by online voting online open to anyone.

Digital Skills for Africa

Google exceeded expectations of training one million young people in Africa when they set out to help bridge the digital skills gap in the region 12 months ago. Now Google plans to expand this program, committing to prepare another 10 million people for jobs of the future in the next five years. The helpful digital skills being imparted include teaching people how to build a web presence, using search to find jobs, using social media, getting tips to enhance their curriculum vitae, among other topics. With an initial focus on Nigeria, Kenya and South Africa, Google also plans to provide mobile developer training to 100,000 Africans to develop world-class applications.

YouTube Go

A new innovation from Google – YouTube Go – lets users discover, save and share videos they love. YouTube Go improves the experience of watching videos over a slower network and gives control over the amount of data used in saving or streaming videos. Nigeria is the second country where this app has been actively tested and later this year, Google has plans to launch a beta version of the app, which will be available to all Nigerian users.

Lagos now on Street View in Google Maps

Having improved their address search experience in Lagos by adding thousands of new addresses and streets, outlines of more than a million buildings in commercial and residential areas and more than 100,000 additional Nigerian small businesses on Google Maps, Google then launched Lagos on Street View, with 10,000 kilometers of imagery. You can virtually drive from one location to the other in the city of Lagos, using your smartphone.

A few software developers had the opportunity to present their works and projects to Pichai after which helpful comments, insights and criticism were given. The day was rounded up with dances and displays to showcase Nigeria’s ethnic and cultural diversity.

Good to go

Startup stand-ups

PWA demos with Sundar

Feel free to explore the #googlefornigeria on Twitter for more exciting content from the event.

Google Developer Group Conference

The following day, July 28, was the GDG (Google Developer Group) Lagos Conference. This event featured a theme of “Building World Class Apps: Tools I Use and How.” GDG Lagos is a group for those who are interested in learning about, and developing solutions and apps, using Google technologies.

The conference provided insights on various solutions to different problems in the tech ecosystem, and focused on how to improve and enhance performance of web and mobile apps via different frameworks and methodologies. The audience comprised mainly of software engineers, designers, developer advocates and developers were from Nigeria and a variety of other Africa countries. There were a number of speakers were present, the most notable of which include:

  1. Tunde Dominic, co-author of Swallow.js, a JavaScript framework used to build Firebase projects, talked about using Firebase on the web.

    Tunde Dominic
  2. Moyin Adeyemi, an Android developer, explained how to work with, and achieve maximum results, with Android Studio.

    Moyin Adeyemi
  3. Timi Ajiboye, a full stack developer at helloworld.ng, talked about getting started with GraphQL.

    Timi Ajiboye
  4. Adewale Abati, a freelance full stack developer and designer, discussed getting started with Gathered; an open source app for meetups.

    Adewale Abati
  5. Sunday Akinsete, also a co-author of Swallow.js. Sunday's session, showcased Android apps built on Firebase.

    Sunday Akinsete
  6. Christian Nwamba, JavaScript preacher and community builder, shared how to build offline apps for the next billion users.

    Christian Nwamba

Building Offline Apps for the Next Billion Users

Bearing in mind that Nigeria and Africa as a whole is comprised of a lot of developing and under developed societies with huge populations, the Next Billion Users initiative (introduced by Google) is a perfect fit. This led to my talk on building apps that users in this part of the country can use effectively despite the lack of internet connectivity.

My presentation started by describing the problems African users face. An average citizen can only afford a 3G connection both at home, and sometimes, at their offices. Even with these limits, average citizens are aware of content-intensive websites, like YouTube or Netflix. Bridging the gap between them and this content was the major problem.

With an understanding of the problem, I started digging into the Progressive Web Apps (PWA) concepts with some funny slides to help the massive audience appreciate the topic and not get bored from the complex technologies behind the concept. I also illustrated the concept by relating to their typical life stories as Nigerians and the challenges they face with no/poor internet connectivity.

Service workers, caches, web storage and manifest are terms that gets you thrown away when you attempt building PWAs. These concepts were explained with enticing examples. I also shared some statistics and success stories of Flipkart, AliExpress and Housing.com, which already use PWA concepts.

Before the demo, I sounded a slight warning — reminding the audience that PWAs are most useful on mobile phones. Hence, they need to take care when delivering content to the users. The audience was advised to optimize their content, especially media content using Cloudinary; pre-fetch resources during idle times; split bundles and cache vendor files; load images progressively with Cloudinary; use lazy load routes, and so on.

You can learn more from the slides.

Android Learning Community

Midway through the sessions, the first set of graduates from the ALC (Android Learning Community) were announced and celebrated. Outstanding performers were awarded gifts, such as Google Home.

The End

At this point the event was drawing to a close, while tech enthusiasts networked, exchanged ideas and took memorable photos, the host of the event – Femi Taiwo, a Google Developer Expert –thanked everyone for coming and thanked Google for making massive impact on the lives of Africans, and Nigerians in particular. The attendees then came together for one last group photograph and the curtains were drawn on a great gathering that will be remembered for years to come.

Thanks to Raphael Ugwu for helping gather event details!

Christian Nwamba Christian Nwamba (CodeBeast), is a JavaScript Preacher, Community Builder and Developer Evangelist. In his next life, Chris hopes to remain a computer programmer.

Your Web Image is Unnecessarily Bloated

$
0
0

Images make up a majority of web content.But a lot of websites and applications that we browse aren’t always delivering the most optimal image format, size, quality and dimension.. . .

As a developer, it seems inefficient to serve a 2000kb JPEG image when we could compress images to optimize the performance without degrading the visual quality.

We are not new to this kind of responsibility. But our productivity will end up being questioned if we do not deliver fast. In order to do so, the community has devised several patterns to help improve productivity. Let's review few of these patterns based on their categories:

  • Optimized formats
  • Third-party Libraries
  • Third-party APIs

Optimized Formats

It is natural to use image formats such as PNG, JPEG and GIF. These three have been around for several years, but they each have characteristics that would make you choose one over another in certain cases. Additional formats have been introduced and some are being standardized in order to provide a better way to serve images in their best optimization level.

Examples

  • WebP
  • JPEG-XR
  • BPG

These optimized formats are, as a matter fact, a very good attempt at reducing the image bloat. Image formats are the means for achieving convenient optimization and compression without resorting to third-party solutions. Unfortunately, the image format battle continues, as there is inadequate support across all browsers, limitations, policies and other factors that make it hard to ensure every image format is consistently optimized.

Third-Party Libraries

You can also use third-party libraries to compress images. These libraries provide an exposed API method to which you can pass your image file, specify some compression argument, and have a compressed image returned for your configuration.

There is nothing wrong with writing your own solution, but we are trying to save time as well. These libraries are written based on some standard algorithms so it's fine to rely on them to some extent. However, they may not give you enough flexibility in terms of how compression is handled. It's hard to find a third-party library that takes a PNG file and delivers an optimally compressed lossless image.

Third-Party APIs

There are public and premium third-party APIs that could be used to process a bloated image, which is then returned to you as an HTTP response. This so far is the most easier to use because it just requires basic knowledge of HTTP to get your images compressed. It's not language or platform dependent which makes it a little more flexible than the others.

Examples:

Plus, you encounter the same issue as you do with third-party SDKs: It's hard to find that all-in-one-solution that is flexible enough to address your problem.

A Better Choice That Offers CDN

While the platforms discussed above are commonly used for compressing images, not one offers a comprehensive, intelligent solution for compression and optimization. Image delivery and upload may even become more complex when you need to serve your images via a content delivery network (CDN). This adds another layer of complexity to the problem.

But, what if there was a single tool that could:

  1. Upload, store and deliver images via CDN
  2. Transform images (dimension, color adjustment, etc)
  3. Compress images losslessly

Cloudinary is that solution. It’s a comprehensive image management tool that quickly and easily addresses your image concerns, eliminating the challenges and risks of image storage and management.

Cloudinary also intelligently addresses our image compression challenges. It uses image transformation features to give you control over the quality of your images, while giving you the option to automatically select quality and format.

What's most interesting about Cloudinary is that all these features are URL-based, which means you simply need to alter the URL to achieve the configurations you need. You also can use the provided SDKs if you prefer not to do URL manipulation.

Image Quality Transformation

The following image of a woman was delivered using Cloudinary (the upload mechanism is not covered but you can read about that here):

Ruby:
cl_image_tag("woman.jpg")
PHP:
cl_image_tag("woman.jpg")
Python:
CloudinaryImage("woman.jpg").image()
Node.js:
cloudinary.image("woman.jpg")
Java:
cloudinary.url().imageTag("woman.jpg")
JS:
cl.imageTag('woman.jpg').toHtml();
jQuery:
$.cloudinary.image("woman.jpg")
React:
<Image publicId="woman.jpg" >

</Image>
Angular:
<cl-image public-id="woman.jpg" >

</cl-image>
.Net:
cloudinary.Api.UrlImgUp.BuildImageTag("woman.jpg")

It's delivered with this URL:

http://res.cloudinary.com/demo/image/upload/woman.jpg

This image weighs about 569kb. This is not a bad quality, but we can do better. Let's see how much we can adjust this image without losing visual quality:

Ruby:
cl_image_tag("woman.jpg", :quality=>90)
PHP:
cl_image_tag("woman.jpg", array("quality"=>90))
Python:
CloudinaryImage("woman.jpg").image(quality=90)
Node.js:
cloudinary.image("woman.jpg", {quality: 90})
Java:
cloudinary.url().transformation(new Transformation().quality(90)).imageTag("woman.jpg")
JS:
cl.imageTag('woman.jpg', {quality: 90}).toHtml();
jQuery:
$.cloudinary.image("woman.jpg", {quality: 90})
React:
<Image publicId="woman.jpg" >
  <Transformation quality="90" />
</Image>
Angular:
<cl-image public-id="woman.jpg" >
  <cl-transformation quality="90">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Quality(90)).BuildImageTag("woman.jpg")
http://res.cloudinary.com/demo/image/upload/q_90/woman.jpg

The q transformation property takes a variety of values, one of which is a range of 1 to 100 that indicates the quality in percentages. This is what we have applied above and the image was trimmed down to 123kb. We just eliminated 446kb, which is a great example of what we mean by the title of this article: "Your Web Image is Unnecessarily Bloated."

Let's go hard on this image and see what's the worst that can happen:

Ruby:
cl_image_tag("woman.jpg", :quality=>40)
PHP:
cl_image_tag("woman.jpg", array("quality"=>40))
Python:
CloudinaryImage("woman.jpg").image(quality=40)
Node.js:
cloudinary.image("woman.jpg", {quality: 40})
Java:
cloudinary.url().transformation(new Transformation().quality(40)).imageTag("woman.jpg")
JS:
cl.imageTag('woman.jpg', {quality: 40}).toHtml();
jQuery:
$.cloudinary.image("woman.jpg", {quality: 40})
React:
<Image publicId="woman.jpg" >
  <Transformation quality="40" />
</Image>
Angular:
<cl-image public-id="woman.jpg" >
  <cl-transformation quality="40">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Quality(40)).BuildImageTag("woman.jpg")
http://res.cloudinary.com/demo/image/upload/q_40/woman.jpg

I just took the quality down to 40 and trimmed down the size to 38kb, yet the visual quality of the image is partially degraded. By now you could imagine the bandwidth you’d be wasting because of the lack of compression.

Let's try 10 percent.

Ruby:
cl_image_tag("woman.jpg", :quality=>10)
PHP:
cl_image_tag("woman.jpg", array("quality"=>10))
Python:
CloudinaryImage("woman.jpg").image(quality=10)
Node.js:
cloudinary.image("woman.jpg", {quality: 10})
Java:
cloudinary.url().transformation(new Transformation().quality(10)).imageTag("woman.jpg")
JS:
cl.imageTag('woman.jpg', {quality: 10}).toHtml();
jQuery:
$.cloudinary.image("woman.jpg", {quality: 10})
React:
<Image publicId="woman.jpg" >
  <Transformation quality="10" />
</Image>
Angular:
<cl-image public-id="woman.jpg" >
  <cl-transformation quality="10">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Quality(10)).BuildImageTag("woman.jpg")
http://res.cloudinary.com/demo/image/upload/q_10/woman.jpg

Now the image is visually poor. This doesn't mean we can tell for sure that 40 percent is the perfect point. It could be 30, it could be 20, but we don’t know exactly To find a perfect quality, we can use the auto quality value rather than a rigid value:

Ruby:
cl_image_tag("woman.jpg", :quality=>"auto")
PHP:
cl_image_tag("woman.jpg", array("quality"=>"auto"))
Python:
CloudinaryImage("woman.jpg").image(quality="auto")
Node.js:
cloudinary.image("woman.jpg", {quality: "auto"})
Java:
cloudinary.url().transformation(new Transformation().quality("auto")).imageTag("woman.jpg")
JS:
cl.imageTag('woman.jpg', {quality: "auto"}).toHtml();
jQuery:
$.cloudinary.image("woman.jpg", {quality: "auto"})
React:
<Image publicId="woman.jpg" >
  <Transformation quality="auto" />
</Image>
Angular:
<cl-image public-id="woman.jpg" >
  <cl-transformation quality="auto">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Quality("auto")).BuildImageTag("woman.jpg")
http://res.cloudinary.com/demo/image/upload/q_auto/woman.jpg

The auto value produces an image that weighs 45kb. This is easier and quicker than using a rigid value and guessing the perfect compression rate.

Auto Formats

In addition to quality transformations, you also can define automatic transcoding for your images. This enables Cloudinary to choose a suitable, and optimal, image format for the browser rendering the image:

Ruby:
cl_image_tag("woman", :fetch_format=>:auto)
PHP:
cl_image_tag("woman", array("fetch_format"=>"auto"))
Python:
CloudinaryImage("woman").image(fetch_format="auto")
Node.js:
cloudinary.image("woman", {fetch_format: "auto"})
Java:
cloudinary.url().transformation(new Transformation().fetchFormat("auto")).imageTag("woman")
JS:
cl.imageTag('woman', {fetch_format: "auto"}).toHtml();
jQuery:
$.cloudinary.image("woman", {fetch_format: "auto"})
React:
<Image publicId="woman" >
  <Transformation fetch_format="auto" />
</Image>
Angular:
<cl-image public-id="woman" >
  <cl-transformation fetch_format="auto">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().FetchFormat("auto")).BuildImageTag("woman")
http://res.cloudinary.com/demo/image/upload/f_auto/woman

Browsers, such as Chrome, support specific formats, like WebP, to improve performance . Cloudinary always knows when and how best to do this without losing the visual quality of the image.

Conclusion

You website images might be bloated or optimized to a certain level. You can use image formats, third-party libraries and APIs to optimize images, but they might not be as flexible as you need them to be.

Cloudinary offers a comprehensive image management solution that delivers and transforms images via URL-based APIs. You can utilize the transformation features for image optimization and compression.

You can sign up for a free Cloudinary account and see how easy it is to optimize images with its URL-based solution.

Christian Nwamba Christian Nwamba (CodeBeast), is a JavaScript Preacher, Community Builder and Developer Evangelist. In his next life, Chris hopes to remain a computer programmer.

Build A Miniflix in 10 Minutes

$
0
0

Developers are constantly faced with challenges of building complex products every single day. And there are constraints on the time needed to build out the features of these products.

Engineering and Product managers want to beat deadlines for projects daily. CEOs want to roll out new products as fast as possible. Entrepreneurs need their MVPs like yesterday. With this in mind, what should developers do?

In this tutorial, we’ll quickly build out a Mini Netflix in 10 minutes. In fact, I think we might build it less time.

MVP Challenge

An excited entrepreneur just approached you to build a video service. A service where users can quickly upload short videos and share on twitter for their friends to view. Let’s list out the features of this app.

Features

  • Users should be able to sign up and log in.
  • Registered/Logged-in users should be able to upload short videos of about 20 - 30 seconds.
  • Registered/Non-registered users should be able to view all uploaded videos on the platform on a dashboard.
  • Users should be able to share any of the videos on twitter.

Now, here’s the catch! T’challa of Wakanda wants to invest in some startups today, so the entrepreneur needs to demo the MVP of the video service in 10 minutes from now.

I know you are screaming your heart right now. It’s totally okay to cry and let the world know about your problems and challenges, but after much ado shedding tears, will the app be ready in 8 minutes? Well, sorry - tears can’t build an app!

Solution

It’s possible to build the MVP our entrepreneur is asking for. Let me show you how! Ready your editor, your command line and anxious fingers. Let’s get to work!!!

Step 1. Flesh Out The App

We’ll use React to build out the app. Facebook has a tool, create-react-app that can scaffold a progressive web app out of the box in less than a minute. If you don’t have it installed, please install and run the command below in your terminal:

create-react-app miniflix
cd miniflix

Go ahead and open up public/index.html. Pull in bootstrap and add it just after the link to the favicon.

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

Step 2. Set up Authentication & Views

Go ahead and install the following packages from your terminal:

npm install auth0-js react-router@3.0.0 jwt-decode axios
  • auth0-js - For authentication
  • react-router - For routing within our app
  • jwt-decode - For decoding the JSON Web Token in our app
  • axios - For making network requests.

Open up your src directory and create a components and utils folder. In the utils folder, create a file, AuthService.js and add the code here to it. I explained how to handle the authentication in this tutorial, so check it out to ensure you are on the right track.

We’ll create 4 components in the components folder. Callback.js, Display.js, Nav.js and Upload.js

The Callback component basically stores our authentication credentials and redirects back to the upload route in our app.

The Display component will be dashboard for viewing all videos.

The Nav component will be the navigation that all pages in the app will share.

The Upload component will handle uploading of videos by registered users.

Add this piece of code below to components/Callback.js:

import { Component } from 'react';
import { setIdToken, setAccessToken } from '../utils/AuthService';

class Callback extends Component {

  componentDidMount() {
    setAccessToken();
    setIdToken();
    window.location.href = "/";
  }

  render() {
    return null;
  }
}

export default Callback;

Add this piece of code to components/Nav.js:

import React, { Component } from 'react';
import { Link } from 'react-router';
import { login, logout, isLoggedIn } from '../utils/AuthService';
import '../App.css';

class Nav extends Component {

  render() {
    return (
      <nav className="navbar navbar-default">
        <div className="navbar-header">
          <Link className="navbar-brand" to="/">Miniflix</Link>
        </div>
        <ul className="nav navbar-nav">
          <li>
            <Link to="/">All Videos</Link>
          </li>
          <li>
            {
             ( isLoggedIn() ) ? <Link to="/upload">Upload Videos</Link> :  ''
            }
          </li>
        </ul>
        <ul className="nav navbar-nav navbar-right">
          <li>
           {
             (isLoggedIn()) ? ( <button className="btn btn-danger log" onClick={() => logout()}>Log out </button> ) : ( <button className="btn btn-info log" onClick={() => login()}>Log In</button> )
           }
          </li>
        </ul>
      </nav>
    );
  }
}
export default Nav;

In the Nav component, you must have observed that we imported a css file. Open the App.css file and add this code here to it.

Add this piece of code to components/Display.js:

import React, { Component } from 'react';
import { Link } from 'react-router';
import Nav from './Nav';
import { isLoggedIn } from '../utils/AuthService';
import axios from 'axios';

class Display extends Component {

  render() {

    return (
      <div>
        <Nav />
        <h3 className="text-center"> Latest Videos on Miniflix </h3>
        <hr/>

        <div className="col-sm-12">

        </div>
      </div>
    );
  }
}

export default Display;

Add this piece of code to components/Upload.js:

import React, { Component } from 'react';
import { Link } from 'react-router';
import Nav from './Nav';

class Upload extends Component {


  render() {

    return (
      <div>
        <Nav />
        <h3 className="text-center">Upload Your 20-second Video in a Jiffy</h3>
        <hr/>

        <div className="col-sm-12">
          <div className="jumbotron text-center">
            <button className="btn btn-lg btn-info"> Upload Video</button>
          </div>
        </div>
      </div>
    );
  }
}

export default Upload;

Lastly, open up index.js and add replace it with the code here to set up your routes.

Now, when you run your app with npm start, you should have views like this:

Step 3. Upload Videos

We need a storage space for the videos our users will upload. Cloudinary is a cloud-based service that provides an end-to-end image and video management solution including uploads, storage, administration, manipulation and delivery. Head over to Cloudinary.com and create an account for free.

Let’s make use of Cloudinary’s Upload Widget. This widget allows you to upload videos or any type of file from your local computer, facebook, dropbox and Google Photos. Wow, very powerful. And the integration is seamless.

Go ahead and reference the cloudinary widget script in your index.html:

 <script src="//widget.cloudinary.com/global/all.js" type="text/javascript"></script>

Note: You can add it just after the links.

Now in Upload.js, modify the code to look like this:

import React, { Component } from 'react';
import { Link } from 'react-router';
import Nav from './Nav';

class Upload extends Component {

  uploadWidget = () => {
    window.cloudinary.openUploadWidget(
      { cloud_name: 'cloud_name',
        upload_preset: '<unsigned-preset>',
        tags: ['miniflix'],
        sources: ['local', 'url', 'google_photos', 'facebook', 'image_search']
      },
      function(error, result) {
          console.log("This is the result of the last upload", result);
      });
  }

  render() {
    return (
      <div>
        <Nav />
        <h3 className="text-center">Upload Your 20-second Video in a Jiffy</h3>
        <hr/>

        <div className="col-sm-12">
          <div className="jumbotron text-center">
            <button onClick={this.uploadWidget} className="btn btn-lg btn-info"> Upload Video</button>
          </div>
        </div>
      </div>
    );
  }
}

export default Upload;

In the code above, we added a third argument, tags. Cloudinary provides this for automatic video tagging. Every video that is uploaded to this app will be automatically tagged, miniflix. In addition, you can provide as many tags as you want. This feature is very useful when you want to search for videos too!

In the uploadWidget function, we called the cloudinary.openUploadWidget function and attached it to the “Upload Video” button. When the user clicks the button, it opens the widget. Replace the cloudname & uploadpreset values with your credentials from Cloudinary dashboard.

Sign in to your app, head over to the upload videos route and try uploading a video.

Upload Widget

Uploading the video...

It uploads the video straight to Cloudinary and returns a response object about the recently uploaded video that contains so many parameters such as the unique **publicid, secureurl, url, originalfilename, thumbnailurl, createdat**_, duration and so many others.

Step 4. Display Videos

We need a dashboard to display all the videos uploaded for users to see at a glance. Here, we will make use of Cloudinary’s react component. Install it:

npm install cloudinary-react

Now, open up components/Display.js and modify the code to be this below:

import React, { Component } from 'react';
import { Link } from 'react-router';
import Nav from './Nav';
import { isLoggedIn } from '../utils/AuthService';
import { CloudinaryContext, Transformation, Video } from 'cloudinary-react';
import axios from 'axios';

class Display extends Component {

  state = { videos: [] };

  getVideos() {
    axios.get('http://res.cloudinary.com/unicodeveloper/video/list/miniflix.json')
          .then(res => {
            console.log(res.data.resources);
            this.setState({ videos: res.data.resources});
    });
  }

  componentDidMount() {
    this.getVideos();
  }

  render() {

    const { videos }  = this.state;

    return (
      <div>
        <Nav />
        <h3 className="text-center"> Latest Videos on Miniflix </h3>
        <hr/>

        <div className="col-sm-12">
          <CloudinaryContext cloudName="unicodeveloper">
            { videos.map((data, index) => (
                <div className="col-sm-4" key={index}>
                  <div className="embed-responsive embed-responsive-4by3">
                    <Video publicId={data.public_id} width="300" height="300" controls></Video>
                  </div>
                  <div> Created at {data.created_at} </div>

                </div>
              ))
            }
          </CloudinaryContext>
        </div>
      </div>
    );
  }
}

export default Display;

In the getVideos code above, we take advantage of a very slick Cloudinary trick that helps grab all videos with a particular tag, when using just one tag. Check it out again:

http://res.cloudinary.com/unicodeveloper/video/list/miniflix.json

So we if had a tag like vimeo, our url will end up with .../vimeo.json. So in the code below, we got all the videos and stored in the videos state.

axios.get('http://res.cloudinary.com/unicodeveloper/video/list/miniflix.json')
          .then(res => {
            console.log(res.data.resources);
            this.setState({ videos: res.data.resources});
    });

The Cloudinary React SDK has 4 major components, Image, Video, Transformation and CloudinaryContext. We are interested in the Video and CloudinaryContext for now. Christian explained how these components work here.

In the render method, we simply just looped through the videos state and passed the publicid of each video into the Cloudinary Video component. The Video component does the job of resolving the publicid from Cloudinary, getting the video url, and displaying it using HTML5 video on the webpage. An added advantage is this: Cloudinary automatically determines the best video type for your browser. Furthermore, it allows the user have the best experience possible by choosing the best from the range of available video types and resolutions.

Run your app, and try to see the list of all videos. It should be similar to this:

You can also manipulate your videos on the fly, with the help of Cloudinary via the Transformation component.

Step 5. Share on Twitter

Go ahead install the react twitter widget component:

npm install react-twitter-widgets

In the components/Display.js file, import the component at the top:

import { Share } from 'react-twitter-widgets'

Now, add this piece of code just after the div that shows the time the video was created.



<Share url={`http://res.cloudinary.com/unicodeveloper/video/upload/${data.public_id}.mp4`} />

Check your app again. It should be similar to this:

Now, try to tweet.

Simple! It’s really not that hard. The source code for this tutorial is on GitHub.

Conclusion

Our MVP is ready. Our entrepreneur. Now sit back, relax and watch your account become flooded with investor money! Wait a minute, there is a 90% probability that you’ll called to add more features to this app. Well, I think Cloudinary can still help you with more features such as:

  • Automatic Subtitles and translation
  • Video briefs - short video, based on few gif images that will extract from the uploaded video.
  • Automatic and/or manual video markers - marking specific locations in the video so the user can wait patiently to watch them, or jump directly to these points
  • Find Similar videos by automatic video tagging

Cloudinary provides many options for uploading, transforming and optimizing your videos. Feel free to dive in and explore them.


This article was originally posted on Scotch.io


Prosper Otemuyiwa Prosper Otemuyiwa is a Food Ninja, Open Source Advocate & Self-acclaimed Developer Evangelist.

Build the Back-End For Your Own Instagram-style App with Cloudinary

$
0
0

Github Repo


Managing media files (processing, storage and manipulation) is one of the biggest challenges we encounter as practical developers. These challenges include:

  • Media uploads
  • Resource management (leads to heavy costing)
  • Media storage
  • Media manipulation
  • Poor delivery
  • Administration

A great service called Cloudinary can help us overcome many of these challenges. Together with Cloudinary, let's work on solutions to these challenges and hopefully have a simpler mental model towards media management.

The process to building this solution is a hands on example which will get our hands dirty but a practical approach. Instagram and Flickr are known for heavy image processing so we can take inspiration from these apps and build something similar.

What We'll Build

The image above shows what we are going to be building. We are going to build the app using Node.js, Express (for routing) and EJS (for templating).

The big question is, how do we intend to solve the problems we listed earlier while building this app?

Meet Cloudinary

Meet Cloudinary, if you haven't!

"Cloudinary is the media back-end for web and mobile developers. An end-to-end solution for all your image and video needs."

Cloudinary is a powerful tool for managing not just images but also videos. The interesting thing about this tool is that it abstracts a lot (actually all) of the challenges we encounter when managing images and other media files, including the ones we listed above.

We are not just going to discuss how Cloudinary helps us figure these puzzles, rather, we will use Cloudinary in building the above app which will expose all the solutions to these puzzles.

Cloudinary gives us the power to:

  • Handle image uploads effectively using a customizable widget
  • Optimize images for web and mobile consumption using transformations
  • Perform administrative operations including: renaming, deleting, etc
  • Store and backup images/videos
  • Manipulate media files
  • Deliver media files

Project Setup, Directory Structure and Dependencies

First things, first. Let's setup a project environment for our app to live in. The GitHub URL provided in the beginning and end of this tutorial is multi-branched. Each branch is prefixed with step-{step-number}- where "step-number" is the an increment for each step.

The first step is step-0-project-structure so you can switch to that branch and follow along from there. The master branch contains the final solution.

To have an overview of the project, it is always a good idea to present a directory structure which we can then build upon:

|---app
|------controller.js // Route handlers
|------model.js // Mongoose model
|---public // Public contents (style, js, imgs)
|---views // EJS views
|-----admin
|-------index.ejs // Admin home page
|-----pages
|-------index.ejs // Home page
|-------new.ejs // New post page
|-------edit.ejs // Edit post page
|-------single.ejs // Preview post page
|-----partials
|-------header.ejs // Header partial
|-------head.ejs // Styles partial
|-------scripts.ejs // Scripts partial
|---package.json
|---routes.js // Routes file
|---server.js // Entry

Something minimal and less overwhelming so we can focus on discussing on the features we are implementing rather than spend time moving codes around.

Update package.json dependencies with the third-party libraries that we will be working with:

"dependencies": {
    "body-parser": "^1.15.2",
    "cloudinary": "^1.4.2",
    "connect-multiparty": "^2.0.0",
    "ejs": "^2.5.2",
    "express": "^4.14.0",
    "mongoose": "^4.6.0"
  }

You can install the dependencies by running:

npm install

We are focusing on backend in this tutorial but that doesn't mean we can't afford a good looking design for the frontend. Rather than waste time crafting that, we can use Semantic UI by updating head.ejs and scripts.ejs:

<!-- ./views/partials/head.ejs -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css">
<!-- ./views/partials/scripts.ejs -->
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js"></script>

API Access with SDKs

You get redirected to your Cloudinary dashboard once you create an account by signing up for free. This is what the dashboard looks like:

The dashboard shows a list of SDKs that you can use to talk to Cloudinary in most of the popular languages including Node.js.

Cloudinary core exposes APIs based on your cloud name and all these SDKs do is serve as a language wrapper to these URL. So instead of littering your app with these URLs, you have a better intuitive language based method APIs to work with.

The cloud name is not your name but the name you chose when signing up as cloud name:

Installing the Node.js Cloudinary SDK

We are interested in the Node.js SDK so let's install it in our existing project:

npm install cloudinary --save

Handling Image Uploads

Getting your images to the server/cloud is the first and most important stage in managing images in your project. In this section, we are will cover

  • how to upload files from the web in your browser/mobile
  • upload using Cloudinary's upload widget
  • transform images
  • display images
  • and more...

Custom Image Upload

Let's see how we can upload images to the cloud using the SDK we have installed. Image upload will always require some form of input to grab the image data, so let's create a page that:

<!-- ./views/pages/new.ejs -->
<html lang="en">
<head>
    <!-- Head partial -->
    <% include ../partials/head %>
</head>
<body class="container">

<header>
    <!-- Header partial -->
    <% include ../partials/header %>
</header>

<main>
    <div class="ui grid container">
        <div class="ui two column centered grid">
            <div class="column">
                <!-- Enctype is multipart to support file upload -->
                <form action="/create" method="post" enctype="multipart/form-data" class="ui form">
                    <div class="field">
                        <label>Title</label>
                        <input name="title" type="text" placeholder="Title" />
                    </div>
                    <div class="field">
                        <label>Description</label>
                        <textarea rows="4" name="description" placeholder="Description"></textarea>
                    </div>
                    <div class="field">
                        <label>Image</label>
                        <input name="image" type="file" />
                    </div>
                    <button class="ui primary button" type="submit">Post</button>
                </form>
            </div>
        </div>
    </div>
</main>

<!-- Scripts partial -->
<% include ../partials/scripts %>
</body>
</html>

The form is not just a regular form. As you can see, the enctype property is set to multipart/form-data so as to properly support and handle file uploads via the form.

You can see how we are injecting our template partials into the HTML document. We have already seen the head and scripts partials so what is left is the header partial. The partial just holds the nav bar:

<div class="ui secondary pointing menu">
    <a class="item" href="/">
        Home
    </a>
    <a class="item" href="/new">
        New Post
    </a>
    <a class="item">
        About Scotchgram
    </a>
</div>

We have our markup all set, but we can't serve the markup yet because there is route handling that. Let's create a route and controller action method to do so:

// ./routes.js
var controller = require('./app/controller');

module.exports = function (app) {
    app.get('/new', controller.new);
};

We are pointing to a none existing controller and action method. This action method contains logic that renders the ejs so we can create that now:

// ./app/controller.js

module.exports = {
  new: function (req, res) {
      res.render('pages/new');
  }
};

With that, we can run the app and see our form at /new:

Of course our customers can not consume a form that is just staring back at them. They would want to fill it out and click the Post button. When the click what happens?

On submission, the the form data is collected and sent to /create endpoint which at the moment we are yet to create the route, so let's start doing something about that:

// ./routes.js
var multipart = require('connect-multiparty');
var multipartMiddleware = multipart();
var controller = require('./app/controller');

module.exports = function (app) {
    app.get('/new', controller.new);
    // Use middleware to handle uploaded files and access
    // uploaded files using req.file
    app.post('/create', multipartMiddleware, controller.create);
};

We have not just added a new route but also configured a middleware to help us process and get details about an uploaded file. With the multipartMiddleware, we could access any uploaded file from req.file.

So we have a route, but routes point need logics to handle incoming requests. The controller's create action method is where this logic will live:

// ./app/controller.js
// Dependencies
var cloudinary = require('cloudinary');
// Mongoose Model
var Model = require('./model');

// Configure Cloudinary
// with credentials available on
// your Cloudinary account dashboard
cloudinary.config({
    cloud_name: 'CLOUD_NAME',
    api_key: 'API_KEY',
    api_secret: 'SECRET'
});

module.exports = {
  new: function (req, res) {
      res.render('pages/new');
  },
  create: function (req, res) {
      // Use Cloudinary uploader to upload to cloudinary sever
      // Access files uploaded from the browser using req.files
      cloudinary.uploader.upload(req.files.image.path, function(result) {
          // Create a post model
          // by assembling all data as object
          // and passing to Model instance
          var post = new Model({
              title: req.body.title,
              description: req.body.description,
              created_at: new Date(),
              // Store the URL in a DB for future use
              image: result.url
              image_id: result.public_id
          });
          // Persist by saving
          post.save(function (err) {
              if(err){
                  res.send(err)
              }
              // Redirect
              res.redirect('/');
          });
      });
  }
};

First we required cloudinary and our mongoose model (which we will create soon), then we configured Cloudinary using credentials available on the dashboard. Once the route is hit, we upload the file to Cloudinary server using the SDK's uploader.upload API and persist the post body including the URL returned from Cloudinary to the MongoDB using Mongoose.

You can learn how to work with Mongo DB and Mongoose here."

Once all that is successful, we return to the homepage, else, we send back an error to the browser about the failure.

Let's add the model to complete the flow:

// app/model.js
// Dependencies
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

// create a schema
var postSchema = new Schema({
    title: String,
    description: String,
    image: String,
    image_id: String,
    created_at: Date
});

// the schema is useless so far
// we need to create a model using it
var Post = mongoose.model('Post', postSchema);

// make this available to our users in our Node applications
module.exports = Post;

To confirm that everything is working fine, I have installed Robomongo, a Mongo DB visualization tool. With Robomongo and can confirm that all my fields were persisted:

RoboMongo

Widget Image Upload (Even Easier Uploads)

We can choose to make life easier for us by using the widget provided by Cloudinary. I left the this for later so you can appreciate the feature after going through the long process of the custom upload.

To use Cloudinary's widget, include it in your script:

<!-- ./views/partials/scripts.ejs -->
<script src="//widget.cloudinary.com/global/all.js" type="text/javascript"></script>

With the widget script loaded, we can setup a handler for for that:

<!-- ./views/partials/scripts.ejs -->
<script src="//widget.cloudinary.com/global/all.js" type="text/javascript"></script>
<script>
    document.getElementById("upload_widget_opener").addEventListener("click", function() {

        cloudinary.openUploadWidget({ cloud_name: 'CLOUD_NAME', upload_preset: 'UPLAOD_PRESET'},
                function(error, result) {
                    console.log(error, result)
                    // Push URL into text input
                    document.getElementById('url_text').value = result[0].url;
                });

    }, false);
</script>

We attached a click event listener to button on the form (yet to be created). When this event occurs, we open the upload widget by calling openUploadWidget and passing in our cloud name and and upload preset.

The upload preset is just a pre-configuration to what we could have been setting up via parameters in the REST URLs. We set this configuration and using a unique ID to differentiate them from each other. To set yours, go to Settings >> Upload Tab >> Upload Presets >> Enable:

Enabling Presets

The callback function for the upload gives us the result which we can play around with. What I have done is push the result into our text input so it can be sent to the server.

Then, on the server, we can persist the URL to our database:

// ./app/controller.js
// Truncated
create: function (req, res) {
      var post = new Model({
          title: req.body.title,
          description: req.body.description,
          created_at: new Date(),
          // Now we are requesting the image
          // from a form text input
          image: req.body.image
      });

      post.save(function (err) {
          if(err){
              res.send(err)
          }
          res.redirect('/');
      });
  }

The form can now be updated to include the upload button and substitute the file input with a text input that stores the URL temporarily:

NOTE: A more real life approach to this is using a hidden input

<!-- views/pages/new.ejs -->
<!-- Truncated -->
 <form action="/create" method="post" enctype="multipart/form-data" class="ui form">
                    <div class="field">
                        <label>Title</label>
                        <input name="title" type="text" placeholder="Title" />
                    </div>
                    <div class="field">
                        <label>Description</label>
                        <textarea rows="4" name="description" placeholder="Description"></textarea>
                    </div>
                    <div class="field">
                        <label>Image</label>
                        <input name="image" type="text" placeholder="Image URL" id="url_text"/>
                    </div>
                    <button class="ui button" type="button" id="upload_widget_opener">Upload with Widget</button>
                    <button class="ui primary button" type="submit">Post</button>
                </form>

Progress and Preview

Most times, it's a good UX practice to show progress of image upload or preview of images being uploaded. With Cloudinary's jQuery plugin, we can get going with this in few minutes.

First thing to do as usual is load the required scripts/dependencies:

<!-- ./views/partials/scripts.ejs -->
<!-- Truncated for brevity -->

<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js"></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/blueimp-file-upload/9.12.5/js/jquery.iframe-transport.js' type='text/javascript'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/blueimp-file-upload/9.12.5/js/jquery.fileupload.js' type='text/javascript'></script>
<script src='https://cdn.jsdelivr.net/jquery.cloudinary/1.0.18/jquery.cloudinary.min.js' type='text/javascript'></script>

The above is an arsenal of tools that will help us accomplish previewing images and showing a progress bar.

Update the view to provide accommodation for the preview thumbnails and progress bar (with semantic):

<!-- views/pages/new.ejs -->
<!-- Truncated for brevity -->
<div class="field">
  <label>Image</label>
    <input name="file" type="file" class="upload_field"/>
</div>

<!-- Image thumbnails will be loaded here -->
<div class="thumbnails"></div>

<div class="ui teal progress" class="progress">
    <div class="bar"></div>
</div>

We can now update our script logic to support preview and progress:

// Configure Cloudinary
$.cloudinary.config({ cloud_name: 'CLOUD_NAME', api_key: 'KEY'})
// Perform unsigned upload
$('.upload_field').unsigned_cloudinary_upload("UPLOAD_PRESET",
            { cloud_name: 'CLOUD_NAME',},
            { multiple: true }
    )
    .bind('cloudinarydone', function(e, data) {
        // Populate thumbnails when upload is finished
        $('.thumbnails').append($.cloudinary.image(data.result.public_id,
                { format: 'jpg', width: 150, height: 100,
                    crop: 'thumb', gravity: 'face', effect: 'saturation:50' } ))
                    })
       .bind('cloudinaryprogress', function(e, data) {
        // Update progress bar with upload progress
        $('.progress').progress({
            percent: Math.round((data.loaded * 100.0) / data.total)
        });

The idea is that the Cloudinary jQuery plugin provides special custom events which let's us hook into each stage of the upload process and do what ever pleases us.

The cloudinarydone event is called once the upload is complete, giving us the power to grab uploaded data and append to the view.

The cloudinaryprogress is called during the upload intervals making it easier for us to build a progress bar around the upload process.

We can as well send the data returned on cloudinarydone back to the server if we wish to persist the URL as we have been doing in previous steps.

Incoming Transformation

Transformations in Cloudinary are like database rules or Express middleware. This is because, they can interfere upload, to manipulate uploaded content before sending to the cloud.

Assuming we want a ration of 2:1 applied to our images with 1000 x 500 dimension before they are uploaded, we can apply this rule (transformation) in our upload logic:

// ./app/controller.js
// Truncated
create: function (req, res) {
     cloudinary.v2.uploader.upload(req.files.image.path,
          //Apply transformation
          { width: 1000, height: 500, crop: "limit" },
          function(err, result) {
           // Handler here
      });
  }

We are using v2 to support transformation.

In a real project that handles a lot of this kind of request, it could be a good idea to use queues/jobs (whichever your environment supports) to abstract this transaction and push it to a later time if the image being transformed is not going to be used instantly.

One thing to keep in mind with this kind of transformation is that it does not save the original image which means that it transforms the image and then stores. To persist the original image, see the next section

Eager Transformation

This kind of transformation unlike the one we saw previously will store both the original image and the transformed. The transformed image can then be accessed with a different endpoint:

// ./app/controller.js
// Truncated
create: function (req, res) {
     cloudinary.v2.uploader.upload(req.files.image.path,
          //Apply transformation
          { eager: [
            { width: 2000, height: 1000, crop: "pad" }, 
            { width: 750, height: 300, crop: "crop", gravity: "north"} ]}, 
          function(err, result) {
           // Handler here
      });
  }

Upload Preset

Most times we pass in a lot of configuration to while trying to upload images especially transformation based configuration. If find yourself in a situation where you have to pass in the same configuration in more than one situations, then upload preset is for you.

With upload preset, you can create configuration from your dashboard that can be re-used at different point in your application. The presets are assigned a unique ID and then you can tell the SDK which preset it should apply by passing it that ID.

To configure a preset, first enable it via Settings >> Upload Tap >> Upload Presets >> Enable and then you can start adding new presets based on your taste. You will be provided with an already existing preset for default purposes but you can add more as you wish.

When you have a preset configured and you have grabbed the ID, you can use it in your app:

// ./app/controller.js
// Truncated
create: function (req, res) {
     cloudinary.v2.uploader.upload(req.files.image.path,
          //Apply Upload preset
          { upload_preset: "PRESET_ID" }, 
          function(err, result) {
           // Handler here
      });
  }

To start performing admin tasks, let us first of all create a list of cards on the home page to show all our images.

We have no route for / yet and that is what we need right now. So let's add that to our existing routes configuration:

// ./routes.js
// Truncated for brevity
app.get('/', controller.index);

Then we can go ahead to create an index action method in our controller:

// ./app/controller.js
// Truncated for brevity
index: function (req, res) {
      Model.find({}, function (err, posts) {
          if(err) res.send(err);

          res.render('pages/index', {posts: posts});
      });
  }
  //...

With the controller rendering a view with the post data, we need to create this view and present the data on the view:

<!-- views/pages/about.ejs -->

<html lang="en">
<head>
    <% include ../partials/head %>
</head>
<body class="container">

<header>
    <% include ../partials/header %>
</header>

<main>
    <div class="ui grid container">
        <div class="ui three column centered grid">
            <% posts.forEach(function(post, index) { %>

                    <div class="column">
                    <div class="ui card">
                        <div class="image">
                            <img src="<%= post.image %>" style="max-height: 150px">
                        </div>
                        <div class="content">
                            <a class="header"><%= post.title %></a>
                            <div class="meta">
                                <span class="date">
                                    <i class="calendar icon"></i> <%= post.created_at.getFullYear() %>
                                    <br>
                                    <i class="image icon"></i> <%= post.image_id %>
                                </span>
                            </div>
                            <div class="description">
                                <%= post.description %>
                            </div>
                        </div>
                       <div class="extra content">
                            <form action="/destroy" method="post" style="display: inline" id="destroy_form<%= index %>">
                                <input type="hidden" name="image_id" value="<%= post.image_id %>">
                                <a onclick="document.getElementById('destroy_form<%= index %>').submit(); return false;">
                                    <i class="remove icon"></i> Remove
                                </a>
                            </form>
                            <a href="/edit/<%= post.image_id %>">
                                <i class="edit icon"></i> Update
                            </a>
                        </div>
                    </div>
                </div>

            <% }); %>
        </div>
    </div>

<% include ../partials/scripts %>
</body>
</html>

Image of Fleshed Homepage

It's important to note that the remove link is submitting a parent form which just send the ID of the image we want to remove to the server while the update link takes us to an edit page (yet to be created).

Deleting Images

To delete images, we have to first delete from cloudinary server, wait for a response, and if successful remove from our database. The API for removing from the cloud using the SDK is destroy:

As usual, we first create a route:

// ./routes.js
// Truncated for brevity
app.post('/destroy', controller.destroy);

Then we create the action method in our controller:

// ./app/controller.js
// Truncated for brevity
destroy: function (req, res) {
      var imageId = req.body.image_id;
      // The destroy method takes the image ID
      // which we need to remove
      cloudinary.v2.uploader.destroy(imageId, function (result) {
              // We also delete this
              // image details from our database
              Model.findOneAndRemove({ image_id: imageId }, function(err) {
                  if (err) res.send(err);

                  res.redirect('/');
              });
          });
  }

We first remove the image by calling the destroy method and passing it the ID of the image we want to remove. When that is completed, we also remove the image details from our database.

Renaming Images

When an image is uploaded, it is assigned a random generated image ID. If for some reason, this image ID matters to you, we can change it to a real name. While doing so, we can use the opportunity to also update the image details.

Let's make a route to render the edit form page which basically looks like that of new form but very few variation:

// ./routes.js
// Truncated for brevity
app.post('/edit', controller.edit);
// Handle submitted updates
app.post('/update', controller.update);

We used the opportunity to add an extra route which will handle the update request from the edit form.

Next we create the controller actions for the above routes, edit and update:

// ./app/controller.js
// Truncated for brevity
  /***
  * Edit action method
  ***/
edit: function (req, res) {
      Model.find({image_id: req.params.id}, function (err, posts) {
          if(err) res.send(err);
            // Render edit form
            //with existing post
          res.render('pages/edit', {post: posts[0]});
      });
  },
  /***
  * Update action method
  ***/
  update: function (req, res) {
      var oldName = req.body.old_id
      var newName = req.body.image_id;
      cloudinary.v2.uploader.rename(oldName, newName,
          function(error, result) {
              if (error) res.send(error);
              Model.findOneAndUpdate({image_id: oldName}, 
                  Object.assign({}, req.body, {image: result.url}), 
                  function (err) {
                  if (err) res.send(err);

                  res.redirect('/');
              })
          })

  },

We use Cloudinary's rename API method to update image name on the cloud. It takes the existing name (to find the image on the cloud), the new name and a callback as arguments.

See how we are using Object.assign to update the model with req.body while updating the image property with the latest URL. This is because, after renaming an image on the cloud, the URL also changes because an image ID is part of the little pieces that composes an image URL.

Now we can happily create the edit view and everything will just work:

<!-- views/pages/new.ejs -->

<html lang="en">
<head>
    <% include ../partials/head %>
</head>
<body class="container">

<header>
    <% include ../partials/header %>
    <h3 class="ui center aligned icon header">
        Edit: <%= post.title %>
    </h3>
</header>

<main>
    <div class="ui grid container">
        <div class="ui two column centered grid">
            <div class="column">
                <img class="ui medium centered image" src="<%= post.image %>">
                <form action="/update" method="post"  class="ui form">
                    <div class="field">
                        <label>Title</label>
                        <input name="title" type="text" placeholder="Title" value="<%= post.title %>"/>
                    </div>
                    <div class="field">
                        <label>Description</label>
                        <textarea rows="4" name="description" placeholder="Description"><%= post.description %></textarea>
                    </div>
                    <div class="field">
                        <label>Rename Image ID</label>
                        <input name="image_id" type="text" placeholder="image_id" value="<%= post.image_id %>"/>
                    </div>
                    <input type="hidden" value="<%= post.image %>" name="image">
                    <input type="hidden" value="<%= post.image_id %>" name="old_id">
                    <button class="ui primary button" type="submit">Post</button>
                </form>
            </div>
        </div>
    </div>
</main>

<% include ../partials/scripts %>
</body>
</html>

Edit Image

Tagging

Just like tags in a blog post, we can categorize images by assigning tags to the. Thereafter, the images can be pulled up based on the assigned tag(s). Tagging improves organization of images and we can perform specific actions to a set of images identified by a tag.

To tag images, we can either do that when uploading them or update the image at a later time with the tags. Let's play around by adding tags during upload.

Update the new form view to have an extra field for submitting tags:

<div class="field">
    <label>Tags</label>
    <div class="ui fluid multiple search selection dropdown" id="tag">
        <input name="tags" type="hidden">
        <i class="dropdown icon"></i>
        <div class="default text">Tags</div>
        <div class="menu">
            <div class="item" data-value="puppy">puppy</div>
            <div class="item" data-value="kitten">kitten</div>
        </div>
    </div>
</div>

We are using Semantic's dropdown multi-select widget and it can only work fine with a script:

 $('#tag').dropdown({
    allowAdditions: true
 });

Now to the real thing -- when the form is submitted, we would grab the input from tags input and pass it as an option (just like we did for transformation) to Cloudinary's upload API method in our controller's create action method:

cloudinary.v2.uploader.upload(req.files.image.path,
          {tags: req.body.tags },
//... truncated

At the end, the form will look like this:

Create Tag

When the image is uploaded, from our management console we can see the tags:

Tags in management console

Queries & Search

We are already doing a great job and I would like to show off some of resource browsing features we can get with Cloudinary.

It's a good thing we are storing data in a database but Cloudinary is generous enough to allow us to store additional information known as metadata. We can use Cloudinary's listing features to filter images and their metadata.

A possible use case in our application is listing only the images in our server for administrative use. At the moment, we are only listing the images created by clients via browser uploads which there metadata are persisted in Mongo.

We begin with an admin route which is just like every other ones we have seen:

// ./routes.js
/*
 * Admin Routes
 *
 * */
 app.get('/admin', controller.admin.index);

Thereafter, we can create the admin.index action method which just list all images from the cloud:

// ./app/controller.js
admin:{
        index: function (req, res) {
            var q = req.query.q;
            var callback = function(result){
                // This is value is used to
                // populate the search input box
                var searchValue = '';
                if(q){
                    searchValue = q;
                }
                res.render('admin/index', {posts: result.resources, searchValue: searchValue});
            };
            if(q){
                // Filter based on search input
                // if provided
                cloudinary.api.resources(callback,
                    { type: 'upload', prefix: q });
            } else {
                // If no search input, list all
                cloudinary.api.resources(callback);
            }
        }
    }

We also implemented a search functionality which is simple one. We use Cloudinary's resources API method to fetch all images, if a query parameter was passed in, we use the prefix option to search for it, else, we just spit all the images.

Our view just looks like what we had in pages/index but with different properties and a search box:

<!-- views/admin/index.ejs -->

<html lang="en">
<head>
    <% include ../partials/head %>
</head>
<body class="container">

<header>
    <% include ../partials/header %>
    <h3 class="ui center aligned icon header">
        Administrator
    </h3>
</header>

<main>
    <div class="ui grid container">
        <div class="ui two column centered grid">
            <div class="column">
                <form class="ui form">
                    <div class="field">
                        <input name="q" type="text" placeholder="Search"  value="<%= searchValue %>"/>
                    </div>
                </form>
            </div>
        </div>
    </div>
    <div class="ui grid container">
        <div class="ui three column centered grid">
            <% posts.forEach(function(post, index) { %>

            <div class="column">
                <div class="ui card">
                    <div class="image">
                        <img src="<%= post.url %>" style="max-height: 150px">
                    </div>
                    <div class="content">
                        <a class="header"><%= post.public_id %></a>
                        <div class="meta">
                                <span class="date">
                                    <i class="calendar icon"></i> <%= post.created_at %>
                                </span>
                        </div>
                        <div class="description">
                           Dimension: <%= post.width %> X <%= post.height %>
                        </div>
                    </div>
                    <div class="extra content">
                        <form action="/admin/destroy" method="post" style="display: inline" id="destroy_form<%= index %>">
                            <input type="hidden" name="image_id" value="<%= post.public_id %>">
                            <a onclick="document.getElementById('destroy_form<%= index %>').submit(); return false;">
                                <i class="remove icon"></i> Remove
                            </a>
                        </form>
                        <a href="/edit/<%= post.public_id %>">
                            <i class="edit icon"></i> Edit
                        </a>
                    </div>
                </div>
            </div>

            <% }); %>
        </div>
    </div>

    <% include ../partials/scripts %>
</body>
</html>

Search UI

Image Categorization

Remember when we added tags to images and we discussed that tags help us organize images? Image organization becomes possible because we are able to categorize them using tags.

For the sake of simplicity, we won't add this feature into our existing app but for your consumption, you can create a tag cloud by fetching the list of tags available in your cloud from Cloudinary:

cloudinary.api.tags(function(result){
    res.render('pages/index', {tags: result.resources})
});

With that, you can loop through all your tags and create a tag cloud with them. Cloudinary's tags API did the magic.

You can also filter your images based on a specified tags. For instance:

resources_by_tag takes a tag name as one of it's argument which it uses to filter your images and return the ones that has the same tag that was passed in.

Moderating Image Uploads

In public facing systems that content quality and kind matters a lot, moderation becomes very important. With moderation feature, you can approve or decline uploaded images if they do not reach a particular requirement.

An example of such requirement is when adding profile image at Upwork. During registration, the user adds his/her photo and waits for few days for an admin to consider if the image is a head-shot before approving.

Achieving moderation with Cloudinary is a breeze. You just switch the moderation option to manual when making uploads:

create: function (req, res) {
      cloudinary.v2.uploader.upload(req.files.image.path,
          { width: 300, height: 300, crop: "limit", tags: req.body.tags,
          // Turn on moderation
           moderation:'manual' },
          function(err, result) {
              console.log(result);
              //... Brevity sake
 //... Brevity sake

When you open your media library, you can now see that as an admin, there is a button to accept or decline this image.

Moderation

Backing up Images

Backing up your web resources and any data in general has always been a recommended practice since day one. Cloudinary is no exception. Backing up in Cloudinary just entails turning on a switch.

Backups as expected, takes extra space and for that reason, it is turned off by default so you can enable when you think you cloud deserves backups. Turning Automatic Backup ensures that images uploaded to your cloud are backed up.

To turn Automatic Backup on, got the Upload Settings from the management dashboard and change Automatic backup to Enabled.

Backing up

You can also explicitly specify that you want a given image to be backed up when it is being uploaded. To do so, set the backup option to true when making the upload as shown below:

cloudinary.uploader.upload(req.files.image.path, 
    // Backs up this particular image
    { backup: true },
    function(result) { 
        console.log(result);
     });

Manipulating Images

Let's see how we can combine everything we have seen including transformation to manipulate images while retrieving them from the cloud.

We have been using the .upload method to send images and now to retrieve them, we use .image or .url method. The difference between image and url is, the former composes a HTML image tag while the latter generate the image's URL.

Embedding Images to Web Pages

We have already started embedding images to our web pages by getting the URL we stored in our database. What if we had no means of persisting data to a database and all we could afford is the image cloud? How do we get the images?

The methods we saw above answers the question. It just takes in the image ID that we need to retrieve and an optional configuration object to manipulate the image.

To demonstrate how we can embed images, let's add another feature to our app which displays a single post.

Route first:

// ./routes
// Truncated
app.get('/:id', controller.find);

Then the controller's action method, find:

find: function (req, res) {
      var id = req.params.id;
      Model.findOne({image_id: id}, function (err, post) {
          if (err) res.send(err);

          res.render('pages/single', {post: post, image: cloudinary.image, image_url: cloudinary.url});
      })
  },

We use Mongoose's findOne to retrieve a single post with the image ID which is passed in as param. When rendering, we are not just passing the post down to the view but also extracting the image and url methods from Cloudinary, aliasing them and passing them to the view as well.

Have a look at what the view now loos like:

<!-- views/pages/single.ejs -->

<html lang="en">
<head>
    <% include ../partials/head %>
</head>
<body class="container">

<header>
    <% include ../partials/header %>
    <h3 class="ui center aligned icon header">
        <%= post.title %>
    </h3>
</header>

<main>
    <div class="ui grid container">
        <div class="ui three column centered grid">
            <div class="column">
                <!-- Use cloudinary.url to get image url -->
                <img class="ui medium centered image" src="<%= image_url(post.image_id) %>" style="border: 7px solid lightgrey">
                <br>
                <p><strong>Title: </strong> <%= post.title %></p>
                <p><strong>Description: </strong> <%= post.description %></p>
                <p><strong>Public ID: </strong> <%= post.image_id %></p>
            </div>
        </div>
    </div>

    <% include ../partials/scripts %>
</body>
</html>

The new thing in the snippet above is that instead of using post.image to get the image as returned from our database, we use cloudinary.url which we already aliased as image_url to retrieve the image.

Single post

Resizing & Cropping Images

Remember I mentioned we can transform images both when uploading or retrieving when we discussed image transformation. Let's start seeing how we can transform images while fetching them:

<img 
    class="ui medium centered image" 
    src="<%= image_url(post.image_id, {width: 200, height 100}) %>">

That will resize the image to 200 x 100 without considering the quality. We can also crop the image like so:

<img 
    class="ui medium centered image" 
    src="<%= image_url(post.image_id, {width: 200, height 100, crop: 'scale'}) %>">

The scale type of cropping will change the size of the image exactly to the given width and height without necessarily retaining the original aspect ratio. There are other cropping techniques and you can read more about them here.

Face Detection Cropping

With Cloudinary's face detection algorithm, we can crop an image based on where a face is positioned on the image.

Assuming we have an image of a child in a very wide background of about 2000 X 1200 dimension and we need to get this down to about 100 x 100 with the face in the middle of the dimension, we can do this:

<img 
    class="ui medium centered image" 
    src="<%= image_url(post.image_id, {width: 100, height 100, crop: 'thumb', gravity: 'face'}) %>">

The gravity property is used to target the face when cropping an image by specifying it's value to face.

Auto Cropping

Cloudinary just keeps getting amazing! We can use a technique known as automatic cropping to crop an image down to contain only the most important part and trimming down the redundant pieces. This is very much like face detection cropping but this time, we are not looking for a face but the important content:

<img 
    class="ui medium centered image" 
    src="<%= image_url(post.image_id, {width: 100, height 100, gravity: "auto", crop: "fill"}) %>">

By setting the gravity to auto instead of face as we saw previously, we are able to crop down the image in an intelligent manner leaving us with the most important portion of the image.

Automatic Format Selection & Quality

Images are universally recognized by there popular formats (JPEG, PNG, etc) but some native environment have there own supported formats which is a more optimized approach.

An example of such environment is Chrome (including the browser and other implementation like Electron or Node Webkit). Chrome supports a format type called WebP and it performs better than the universal formats.

When fetching images, you can set the fetch_format property to auto so it can render the images based on the native format rather than the universal. If no native format is supported, it defaults to universal.

<img 
    class="ui medium centered image" 
    src="<%= image_url(post.image_id, {width: 100, height 100, gravity: "auto", crop: "fill" fetch_format: "auto"}) %>">

Format selection is not the only automated process we can get from Cloudinary. Image quality is also achievable, automatically. By setting quality to auto, Cloudinary will analyze a given image to find the best quality compression level and optimal encoding settings based on the image content and the viewing browser, in order to produce an image with good visual quality while minimizing the file size:

<img 
    class="ui medium centered image" 
    src="<%= image_url(post.image_id, {width: 100, height 100, gravity: "auto", crop: "fill" quality: "auto"}) %>">

Image Shapes, Styles and Filters

What I have been doing is applying styles explicitly using CSS. Cloudinary is always trying to make our jobs and lives easy which us why we can change the shapes and styles of these images using the API.

Let's see how we can change the shape of our images in the single post view to a rounded images and also add the grey borders using Cloudinary API:

<img 
    class="ui medium centered 
        image" src="<%= image_url(post.image_id, 
                {
                    width: 200, 
                    height: 200, 
                    radius: 100, 
                    crop: "fill", 
                    border: "10px_solid_grey"
                }
            ) %>" />

The radius is responsible for setting the image shape to circle and border adds the image borders. The border property is just like what we get with CSS but with the spaces replaced with an underscore (_).

Shapes and Styles

There are lots more under image and shapes and you can visit Cloudinary to explore your options

Just like adding the styles and updating the shape, we can add fun filters to the image. My favourite is changing the image color to greyscale:

<img 
    class="ui medium centered 
        image" src="<%= image_url(post.image_id, 
                {
                    width: 200, 
                    height: 200, 
                    radius: 100, 
                    crop: "fill", 
                    border: "10px_solid_grey",
                    effect: "grayscale"
                }
            ) %>" />

Setting the effect property to greyscale gives us a black and white kinda thing:

Greyscale Image

Feel free to explore other options including: hue, red, blue, green, negate, brightness, brightness_hsb, colorize, etc.

Making Images Responsive

If you are still building fixed width websites, then you may be out of touch with current trends. Fluid design and responsive content should be a primary focus for every developer because the web has gone from solely desktop systems to include mobile devices, such as smartphones and tablets of all sizes.

It's difficult to build sites that adapt to a variety of device sizes. By default, text reflows to automatically fit the design, but other contents, particularly images, do not.

Cloudinary provides a handful of options when it comes to making images responsive. Let's explore few of these options:

Automatic Responsive Images: This is achieved using Client Hints technology which allows web browsers to inform servers (or CDN layers) with the required dimensions and pixel densities of each specific image download request.

With that kind of information, the server can then send a suitable image dimension for every giving device width.

Chrome, Android and Opera are the browsers that support Client Hint and hints can be provided in the markup document via a meta tag:

<meta http-equiv="Accept-CH" content="DPR, Viewport-Width, Width">

It is identified by the http-equiv which the value is set to Accept-CH (Accept Client Hint). The relevant hints are the DPR (Device Pixel Ratio) value, the Width available for the specific image in the responsive layout, and the Viewport-Width of the browser's window.

Now that hints are being sent to the cloud, we can make request for our images and Cloudinary will be smart enough to send us the perfect image for our device width:

<img 
    class="ui medium centered
        image" src="<%= image_url(post.image_id, 
                {
                    client_hints: true, 
                    transformation: [
                      {
                          aspect_ratio: "16:9", 
                          crop: "fill"
                      },
                      {
                          width: "auto", 
                          dpr: "auto", 
                          crop: "scale"
                        }
                  ]}
            ) %>" />

JavaScript Based Detection: Another option which works better across browsers is using JavaScript to detect the viewport of a given client and then serve an image based on this information.

Cloudinary's JavaScript library already implemented this approach internally so we do not have to write any complex logic to get going. We already have the library included in our demo project.

We use data-src rather than src to request for the images. This enables Cloudinary to serve these images dynamically. You can set the src attribute to a placeholder image.

<img 
    data-src="<%= image_url(post.image_id) %>"
    src="http://placehold.it/200x300" />

Next, we call the JavaScript method to tell Cloudinary that we need to make use of the JavaScript-based detection:

<script type="text/javascript">$.cloudinary.responsive()</script>

Remote Fetch

All the goodies we have seen in Cloudinary does not only apply to images that are stored in Cloudinary cloud. We can apply it to any other remote image making it possible for us to just use the Cloudinary SDK even when our content is not hosted by Cloudinary:

<img 
    class="ui medium centered 
        image" src="<%= image_url('http://i.imgur.com/67iZh9H.jpg', 
                {
                    type: 'fetch'
                    width: 'auto', 
                    radius: 100, 
                    crop: "fill", 
                    border: "10px_solid_grey",
                    effect: "grayscale"
                }
            ) %>" />

By specifying the type option as fetch, we can pass in a URL rather than a Cloudinary image ID.

Conclusion

There are some other solutions out there but from my experience and that of over 140k+ happy Cloudinary users, satisfaction is guaranteed. This article will not just serve as a tutorial but a reference to run back to when building your awesome apps with Cloudinary.

Cloudinary documentations, blog posts and GitHub repos are also very helpful and most portion of this tutorial where gotten from them.

This article was originally posted on Scotch.io

Christian Nwamba Christian Nwamba (CodeBeast), is a JavaScript Preacher, Community Builder and Developer Evangelist. In his next life, Chris hopes to remain a computer programmer.

Embed Images in Email Campaigns at Scale

$
0
0

tl;dr

Cloudinary is a powerful image hosting solution for email marketing campaigns of any size. With features such as advanced image optimization and on-the-fly image transformation, backed by a global CDN, Cloudinary provides the base for a seamless user experience in your email campaigns leading to increased conversion and performance.


We live in an era of information overload and attention is the real currency. Marketers are constantly looking for new ways to reach you, to advertise products and services that they think could improve your lives.

SnapChat and Instagram Stories are the newest channels marketers are trying to leverage. But there’s one tried-and-true channel that has proven its dominance for reaching your audience… and it’s none other than email marketing.

There are times when Marketing and IT must work together to implement multiple email marketing campaigns under a predefined budget.

Here’s the catch - our email database is really big. Large enough that any traditional email marketing software, such as Aweber or MailChimp, would cost at least five times more than what we had budgeted.

Clearly, we needed a more feasible solution.

If you are in a similar situation, your options include:

  • Engaging with a marketing automation system such as Marketo and HubSpot
  • Sending emails from your company’s email account

Let’s say you’ve implemented an email delivery solution. The next thing you need to figure out is where to host the images for the HTML used in your email.

Since we know that attention is the real currency, emails must:

  1. Be well-designed
  2. Carry a visual impact
  3. Look seamless across all devices - mobile, tablet and desktop
  4. Load fast

Here’s the deal - points one and two depend on your team’s creativity, and is subjective in nature.

What we can control are points three and four. And that’s where Cloudinary comes in.

Cloudinary is end-to-end, cloud-based solution that enables you to optimize, manipulate and transform images on-the-fly. It is designed for almost any service (product, website or app) that requires reliable image delivery.

Too complex? Think of Cloudinary as the perfect email image hosting solution.

I’ve personally used Cloudinary for my email marketing campaigns, and wanted to share how it could help you with yours.

While I’m not going to dive into all the features Cloudinary has to offer, I will discuss the features that:

  • Help improve user experience
  • Reduce resource consumption
  • Do a combination of both

Let’s take a look at these features, shall we?

On-the-Fly Image Optimization

We all need high-quality images for email marketing that load fast. One of the very basic, yet fundamentally important, features Cloudinary offers is on-the-fly image manipulation capabilities.

For starters, every image you upload is optimized and stored in Cloudinary’s servers. Only the optimized version of the image (which, by the way has negligible loss in quality) is served to the end user. This approach saves bandwidth costs for both you and the end user. Plus, the email loads faster on the user’s device (mobile or desktop), resulting in a better user experience.

On-Demand Image Resizing and Manipulation

Mobile screens are smaller, and therefore can benefit from use of smaller images. You don’t need a 1080p image for a email that’s primarily going to be viewed on a phone.

Whatever the device, Cloudinary enables me to configure the image resolution by defining the single parameter in the request URL. I don’t need to upload two (or more) versions of the image to Cloudinary. By changing just one parameter in the URL, I can quickly test which resolution works best. This is incredibly useful in A/B testing and designing emails.

For example, I use this simple command to resize the image:

Ruby:
cl_image_tag("sample.jpg", :transformation=>[
  {:width=>300, :crop=>"scale"},
  {:opacity=>50}
  ])
PHP:
cl_image_tag("sample.jpg", array("transformation"=>array(
  array("width"=>300, "crop"=>"scale"),
  array("opacity"=>50)
  )))
Python:
CloudinaryImage("sample.jpg").image(transformation=[
  {"width": 300, "crop": "scale"},
  {"opacity": 50}
  ])
Node.js:
cloudinary.image("sample.jpg", {transformation: [
  {width: 300, crop: "scale"},
  {opacity: 50}
  ]})
Java:
cloudinary.url().transformation(new Transformation()
  .width(300).crop("scale").chain()
  .opacity(50)).imageTag("sample.jpg")
JS:
cl.imageTag('sample.jpg', {transformation: [
  {width: 300, crop: "scale"},
  {opacity: 50}
  ]}).toHtml();
jQuery:
$.cloudinary.image("sample.jpg", {transformation: [
  {width: 300, crop: "scale"},
  {opacity: 50}
  ]})
React:
<Image publicId="sample.jpg" >
  <Transformation width="300" crop="scale" />
  <Transformation opacity="50" />
</Image>
Angular:
<cl-image public-id="sample.jpg" >
  <cl-transformation width="300" crop="scale">
  </cl-transformation>
  <cl-transformation opacity="50">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation()
  .Width(300).Crop("scale").Chain()
  .Opacity(50)).BuildImageTag("sample.jpg")

The w_300 tells the Cloudinary API to resize the image to a width of 300px. The height is adjusted automatically, thus maintaining the aspect ratio of the image.

There are tons of other image manipulation features mentioned in Cloudinary’s documentation.

Changing the Image, But Keeping the Same URL

This feature is a lifesaver when it comes to email marketing. Imagine spending hours crafting the perfect email, designing it over the course of a week. You embed the image in the email’s HTML file. Everything looks good.

On the day the campaign kicks off, you’re a bit nervous and after several checks, you press the send (or schedule) button. You grab a coffee and moments later you realize that the header image has a unexcusable, hair-raising typo. Think “End of Season Sale. Grab yourself a umbrella.”

If you’ve scheduled the campaign, there’s still hope. You can pause and fix the error. But what if your campaign is already being sent?

Here’s what I did:

  • Quickly fix the image.
  • Head over to Cloudinary’s dashboard and update the image.
  • Continue enjoying the coffee.

Winning the Battle Against Broken CSS Support

Here’s the deal - CSS support is shaky in most email clients. Some work, some don’t. There just isn’t seamless support for CSS in them.

When experimenting with Cloudinary, I found two particularly useful, on-the-fly image transformation features.

Opacity Transformation

The first feature is opacity transformation. In simple terms, this feature enables you to change the opacity of the image, on-the-fly.

If you’re using a background image in your email, opacity plays a major role in the contrast.

You might wonder - which email client does not support CSS opacity? You’d be surprised.

Note: MailChimp has this handy resource on CSS compatibility in various email clients.

The following email clients do not support CSS opacity:

  1. Gmail and Yahoo! web clients
  2. Microsoft Outlook 2000 - 2013 (desktop versions for Windows)
  3. Windows Live Mail (for those still on Windows 7)
  4. Gmail app for Android and iOS phones

The only desktop clients that do support CSS opacity are Apple Mail and Outlook 2013 for Mac.

This is where Cloudinary comes in.

In Cloudinary, you can easily manipulate the opacity of the image by adding the o_XX parameter. XX represents the digits of the opacity percentage.

For example, to display an image at 50 percent opacity, we add the parameter o_50 in the following URL:

Ruby:
cl_image_tag("sample.jpg", :opacity=>50)
PHP:
cl_image_tag("sample.jpg", array("opacity"=>50))
Python:
CloudinaryImage("sample.jpg").image(opacity=50)
Node.js:
cloudinary.image("sample.jpg", {opacity: 50})
Java:
cloudinary.url().transformation(new Transformation().opacity(50)).imageTag("sample.jpg")
JS:
cl.imageTag('sample.jpg', {opacity: 50}).toHtml();
jQuery:
$.cloudinary.image("sample.jpg", {opacity: 50})
React:
<Image publicId="sample.jpg" >
  <Transformation opacity="50" />
</Image>
Angular:
<cl-image public-id="sample.jpg" >
  <cl-transformation opacity="50">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Opacity(50)).BuildImageTag("sample.jpg")
Example of an image at 50 percent opacity, transformed dynamically

Now you can forget about the compatibility issues and design the newsletter!

CSS Sprite Generation

You can combine multiple logos into a single sprite image by using Cloudinary’s sprite-generation capabilities. This makes image management easier and gives better performance.

The sprite generation feature is useful in the email footers, where you want to showcase your clientele or partners. It helps establish trust in the product you’re marketing and uplifts your brand’s image.

For example, let’s say you’re a cloud-aggregator startup, specializing in managed CMS hosting. In other words you help setup and manage your client’s websites in cloud hosting companies.

In this role, you support the top four cloud hosting companies - AWS, Microsoft Azure, Google Cloud and IBM Cloud. You upload their logos in Cloudinary with a common tag - let’s say “supported_cloud”.

With Cloudinary’s sprite generation feature, you could display a single image that combines all the images with the supported_cloud tag. The, with a few CSS commands, you can display the logos in various sections of the footer.

This improves user experience by reducing network overhead and bypassing download limitations.

On-the-Fly Image Manipulation

Image manipulation comes in handy when you need to add a watermark to your images. A classic example would be screenshots of your SaaS dashboard, used in a drip email campaign.

And the best part? I don’t have to add the watermark to the image myself. All I need to do is configure the image URL and Cloudinary serves the watermarked version of the image to the end user.

Advanced Reporting

Cloudinary provides an intuitive dashboard that gives you a bird’s-eye view of your image stats.

Screenshot of the Cloudinary Dashboard Report (Demo)

How many images were transformed on-the-fly? Storage quota consumed? Bandwidth used? These stats are presented in a beautiful graph. You can breakdown the report by month, or more granularly at a daily level. Interestingly I find that the bandwidth consumption correlates with the open-rate of my mailing lists.

Download API information in one-click from the Cloudinary dashboard

Note: Cloudinary displays your account details at the very top of the dashboard. Developers also can download the API access credentials in Python or YML in a single click.

Browser-based Image Format

This feature is, by far, one of my favorite things in Cloudinary. Browsers have evolved tremendously over the years. From being able to view simple text documents, to images and video. Today I am writing this article on Google Docs.

Each browser now supports a distinct type of image, that’s only applicable to, or supported by, that browser. For example, Google introduced the WebP image format back in 2010, which offers significant saving compared to JPEG. Only Google Chrome supports the WebP.

JPG Image
16.9KB JPG
WebP Automatic Image
6.9KB WebP

Similarly Microsoft announced JPEG-XR format in 2009, which offers better image compression than the original JPEG. It is supported in Internet Explorer 9 or above.

Here’s how Cloudinary creates the magic. With the Automatic Format Selection feature, Cloudinary automatically serves different formats of the image, based on the requesting browser. For example, it serves the WebP version of the image to Google Chrome users and JPEG-XR version of the image to Internet Explorer 9+ users. If the browser does not support these modern image formats, the default optimized JPEG is served.

Cloudinary does all of this on-the-fly, without us having to manually convert or upload multiple versions of the image. Simply add f_auto parameter to the requesting URL and the corresponding format would be served in the browser.

Ruby:
cl_image_tag("sample.jpg", :width=>300, :opacity=>50, :crop=>"scale")
PHP:
cl_image_tag("sample.jpg", array("width"=>300, "opacity"=>50, "crop"=>"scale"))
Python:
CloudinaryImage("sample.jpg").image(width=300, opacity=50, crop="scale")
Node.js:
cloudinary.image("sample.jpg", {width: 300, opacity: 50, crop: "scale"})
Java:
cloudinary.url().transformation(new Transformation().width(300).opacity(50).crop("scale")).imageTag("sample.jpg")
JS:
cl.imageTag('sample.jpg', {width: 300, opacity: 50, crop: "scale"}).toHtml();
jQuery:
$.cloudinary.image("sample.jpg", {width: 300, opacity: 50, crop: "scale"})
React:
<Image publicId="sample.jpg" >
  <Transformation width="300" opacity="50" crop="scale" />
</Image>
Angular:
<cl-image public-id="sample.jpg" >
  <cl-transformation width="300" opacity="50" crop="scale">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Width(300).Opacity(50).Crop("scale")).BuildImageTag("sample.jpg")
Using the Automatic Format Selection feature in Cloudinary, this same image would be served as WebP in Google Chrome or as JPEG in Firefox.

Browser-dependent image format rendering saves a lot of bandwidth when the email is viewed in a web browser, or when the HTML version of the email is viewed online.

Compress GIFs On-the-Fly

We started this article with the premise that attention is the most valuable currency in today’s world. When it comes to email marketing, getting someone to open the email depends on the quality of the subject line.

Our goal is to grab the user’s attention, right after he or she opens the email. That’s where an image comes in. A good header image can do wonders. What if we could take that one step further? What if we could crunch more relevant information into a single place, without requiring the user to scroll down?

That’s where videos come in. But unfortunately, videos are not supported in emails. That’s why you should use GIFs. You can keep the same information, and do away with the heavy size or the technical complexities of implementing the video.

Cloudinary supports animated GIFs, and with a few parameters, you can create winning emails!

Ruby:
cl_image_tag("kitten_fighting.gif")
PHP:
cl_image_tag("kitten_fighting.gif")
Python:
CloudinaryImage("kitten_fighting.gif").image()
Node.js:
cloudinary.image("kitten_fighting.gif")
Java:
cloudinary.url().imageTag("kitten_fighting.gif")
JS:
cl.imageTag('kitten_fighting.gif').toHtml();
jQuery:
$.cloudinary.image("kitten_fighting.gif")
React:
<Image publicId="kitten_fighting.gif" >

</Image>
Angular:
<cl-image public-id="kitten_fighting.gif" >

</cl-image>
.Net:
cloudinary.Api.UrlImgUp.BuildImageTag("kitten_fighting.gif")
Animated GIF served by Cloudinary

You can apply almost all the transformation effects to the GIFs (plus a few special ones). In our example, let’s serve the browser optimized format of the GIF.

Consider the following URL:

Ruby:
cl_image_tag("kitten_fighting.gif", :fetch_format=>:auto)
PHP:
cl_image_tag("kitten_fighting.gif", array("fetch_format"=>"auto"))
Python:
CloudinaryImage("kitten_fighting.gif").image(fetch_format="auto")
Node.js:
cloudinary.image("kitten_fighting.gif", {fetch_format: "auto"})
Java:
cloudinary.url().transformation(new Transformation().fetchFormat("auto")).imageTag("kitten_fighting.gif")
JS:
cl.imageTag('kitten_fighting.gif', {fetch_format: "auto"}).toHtml();
jQuery:
$.cloudinary.image("kitten_fighting.gif", {fetch_format: "auto"})
React:
<Image publicId="kitten_fighting.gif" >
  <Transformation fetch_format="auto" />
</Image>
Angular:
<cl-image public-id="kitten_fighting.gif" >
  <cl-transformation fetch_format="auto">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().FetchFormat("auto")).BuildImageTag("kitten_fighting.gif")

If you’re in Google Chrome or Opera, the f_auto parameter would have converted the GIF into an animated WebP file.

The Round-Up

Here’s a quick round-up of what we’ve covered so far:

  • Cloudinary automatically optimizes all the images on-the-fly.
  • You can resize and apply other on-demand transformation effects to your images.
  • You can change the image, while retaining the same URL.
  • Cloudinary’s opacity transformation and sprite generation features enables you to seamlessly design email campaigns that looks the same across all clients.
  • With on-the-fly image manipulation, you can generate watermarked versions of images, which are excellent to prevent email spams.
  • Cloudinary’s advanced analytics and reporting enables you to track your usage at all times.
  • Browser-based image format reporting delivers the image in the most optimal format, that is supported by the web browser.
  • Cloudinary also can optimize and apply various transformation effects to animated GIF images.

We’ve only begun to learn about Cloudinary’s amazing suite of image manipulation features. What we’ve discussed in the context of email marketing barely scratches the surface of the robust feature set offered by Cloudinary.

The good news is that all of these features would be applicable in multiple scenarios, in one way or the other. Did you know that Cloudinary serves these images via a global network of content delivery networks such as Akamai, Amazon Cloudfront and Fastly?

If you are looking to take your email marketing to the next level, Cloudinary is an excellent partner. The free Cloudinary account provides you enough quota to test the service and even possibly launch a trial email campaign.

Have you considered using Cloudinary in your email marketing programme? Let us know!

Sourav Kundu Sourav Kundu is an avid marketer with a passion for all things digital. From email marketing to app re-engagement campaigns, he loves putting on multiple hats during the workday. He's available for consultation over Twitter or his website.

The Future of Audio and Video on the Web

$
0
0

Web sites and platforms are becoming increasingly media-rich. Today, approximately 62 percent of internet traffic is made up of images, with audio and video constituting a growing percentage of the bytes.

The web’s ubiquity is making it possible to distribute engaging media experiences to users around the world. And the ecosystem of internet consumers and vendors have crafted APIs that enable developers to harness this power.

Cisco reports that 70 percent of current global internet traffic is video and it’s going to increase to 80 percent by 2020. This is huge!

In addition, considering the current trajectory of how media is consumed, it’s estimated that the next billion users will likely be all mobile. These new users will be diverse in every way; their location, cultural experience, level of computer expertise, connectivity and the types of devices via which they consume media content. Despite the facts about the next generation of users, the mobile web still has a lot of catching up to do.

Let’s take a look at these APIs and what the future holds for us with regard to audio and video on the web.

First, A Look Back

Consider how the mobile web has functioned overtime from the dot-com bubble. It didn’t play a big part in the audio and video innovation that has come so far. Why?

The mobile web was faced with a number of challenges:

  • Buffering: Flash was prevalent in the beginning of the web, when mobility wasn’t even a consideration. In most cases, we had large audio and video files sitting on servers. And when users tried to access them via the mobile web, it took a long time to download, thus making users wait endlessly for these files to play on their devices.

    Eventually individuals and companies began delivering small-sized video files to mobile users. There was no buffering, but the video quality was poor.

  • Bad Layout: Delivering video content via mobile was terrible in terms of the layout. In fact, there are many websites that still haven’t figured out the optimal way to deliver video content to mobile users. Most times, you still have to scroll the far end left or right of the mobile web page. Sometimes, a part of the video screen gets cut off except when you were using an IPhone.

  • No Offline Capabilities: There was no offline experience for users listening to audio or watching videos. Without internet connectivity, the video/audio stops playing. In many regions, low-end devices are common, and internet connections are very unreliable. In fact, many users in developing countries still find it hard to purchase data. But the mobile web had no capacity for caching and serving content while offline.

The Beginning of the Future

It’s no secret that the pace of innovation has accelerated for audio and video in the last decade and the mobile web is now providing a better user experience.

To ensure a great video experience, it must offer:

  • Fast playback
  • Ability to watch videos anywhere
  • Great user interface
  • High-quality video playback

Therefore, let’s take a good look at what is available today to provide great video experiences for the next billion users.

It’s well-known that if your video buffers, you lose viewers. The same goes when your video doesn’t start playing fast enough. So to please your viewers, you need to be able to quickly deliver videos.

One way of ensuring fast playback is using the Adaptive Bitrate Streaming technique. This technique enables videos to start quicker, with fewer buffering interruptions. Multiple streams of the video with different resolutions, qualities and bitrate are created, and during playback, the video player determines and selects the optimal stream in response to the end user’s internet connectivity. The video player automatically switches between variants (also known as streams) to adapt to changing network connections.

Today, we have services like Cloudinary that provide plug-and-play adaptive bitrate streaming with HLS and Mpeg-dash to enable fast video playback. And Cloudinary supports a lot of transformations you can apply to videosand deliver to users with minimal efforts.

In addition, startup-time for videos is very important. One good technique to employ when serving videos with little or no delay in startup time is precaching videos with service workers. With service workers, you can precache all your videos on page load before users start interacting with the videos. Cloudinary also delivers video via fast content delivery networks (CDNs), which provide advanced caching techniques that offloads the caching from the developer.

Ability to Watch Videos Anywhere

Users should be able to watch videos or listen to audio offline. Again, with Service Workers, you can control the logic of what version(s) of video or audio file users should download. A simple save for offline viewing/listening button can be created that once the user clicks it, the service worker caches the video and makes it available when there is no connectivity.

Another tool you can use with the service worker is background fetch. By default, service workers are killed when the user navigates away from a site or closes the browser. With background fetch, downloads are enabled in the background while the user navigates away.

“img1”
img1
“img2”
img2

Video Compression

Imagine being able to cut down your video file size by 40 percent, while preserving the same quality. Today, we have VP9, the WebM project’s next-generation open source video codec. VP9 offers:

  • A 15 percent better compression efficiency
  • Supported on more than 2 billion devices
  • Higher quality videos

Google’s VP9 codec was mainly used on YouTube before general adoption by the public. This compression technology amassed great gains for YouTube, which saw a video starting 15 to 18 percent faster using VP9 compared to other codecs, and 50 percent less buffering. Native support for VP9 video codec already exists across media devices, laptops and browsers. Another codec worthy of mention is HEVC (High Efficiency Video Coding), also known as H.265. It is a successor to AVC (Advanced Video Coding). It offers very good compression benefits while retaining quality. And it is processor intensive. The licensing fees are one of the main reasons HEVC adoption has been low on the web.

User Experience

Today, we have the opportunity to provide even better experiences for our users. One of these experiences is enabling the user to listen to/control an audio or video playlist from a device’s lock screen or from the notifications tab. How is that possible? It’s via the Media Session API. The Media Session API enables you to customize media notifications by providing metadata for the media your web app is playing. It also enables you to handle media-related events, such as seeking or track changing, which may come from notifications or media keys.

Source: developers.google.com

set media session

function setMediaSession() {
  if (!('mediaSession' in navigator)) {
    return;
  }
  let track = playlist[index];
  navigator.mediaSession.metadata = new MediaMetadata({
    title: track.title,
    artist: track.artist,
    artwork: track.artwork
  });
  navigator.mediaSession.setActionHandler('previoustrack', playPreviousVideo);
  navigator.mediaSession.setActionHandler('nexttrack', playNextVideo);
  navigator.mediaSession.setActionHandler('seekbackward', seekBackward);
  navigator.mediaSession.setActionHandler('seekforward', seekForward);
}
Without Media Session
Without Media Session
With Media Session
With Media Session

Source: developers.google.com

Another core part of an improved user experience is automatic fullscreen mode capabilities. When a user changes the orientation of the phone to landscape, the video player should automatically assume full screen mode. This capability is made possible by the [Screen Orientation API}(https://developer.mozilla.org/en/docs/Web/API/Screen/orientation).

var orientation = screen.orientation || screen.mozOrientation || screen.msOrientation;

orientation.addEventListener('change', () => {
  if (orientation.type.startWith('landscape')) {
    showFullScreenVideo();
  } else if (document.fullScreenElement) {
    document.exitFullscreen();
  }
});

The Future

We have explored various options available to provide an enhanced media experience for users in today’s world. Let’s quickly look at what we can expect in the future and how to prepare for it.

Color

There are new sets of standards designed to enhance the display of colors on our devices - from phones to TVs to monitors and more.

You may not realize that your display screens can not correctly reproduce all the colors you can see with your eyes. The new video standards around BT.2020 dramatically extend these colors.

Wider Range of Brightness

Today’s standard monitors have some challenges. They don’t have the capacity to display the full spectrum of brightness and blackness. In essence, blacks aren’t really that black and the brights aren’t really that bright. However, a new set of functions for converting digital values of brightness into what actually gets displayed on the screen are on the horizon.

The ability to know which devices can support High Dynamic Range (HDR) is key. Currently it is possible to do that with Chrome Canary.

var canPlay = MediaSource.isTypeSupported(VP9.0.0.1);

The Alliance Media group which consists of YouTube, Google, Amazon, Microsoft, Twitch, Mozilla, Hulu, Netflix and The BBC is now working on a new open source compression format to target HDR, wide color gamut, m4k, and 360 video, as well as providing the most demanding low bitrate solution imaginable for the billions of people who have slow internet connections.

This new format is the AV1 codec. As at the time of this writing, the codec is still in development, but there have been signs of significant progress so far. One of such noteworthy sign is that the AV1 codec is already achieving 20 percent better compression than VP9.

For VP9, there is support to determine if a device supports the codec and if it has the necessary hardware to render the frames and efficiently use power. Check this out:

if ('mediaCapabilities' in navigator) {
  // Get media capabilities for 640x480 at 30fps, 10kbps
  const configuration = { type: 'file', video: {
    contentType: 'video/webm; codecs="vp09.00.10.08"',
    width: 640,
    height: 480,
    bitrate: 10000,
    framerate: 30
  }};

  navigator.mediaCapabilities.decodingInfo(configuration).then((result) => {
    // Use VP9 if supported, smooth and power efficient
    vp9 = (result.supported && result.smooth && result.powerEfficient);
  });
}

360 Video

The demand for 360 videos and photos is gradually increasing. Social media has amplified the need for this type of videos. Furthermore, documentaries, adverts, real estate industries, wedding events have started adopting it more. Just recently, Vimeo announced the launch of 360 video on their platform. Users can now stream and sell 360 degree video content.

In the future, there will be massive adoption of 360 video content across different platforms. Kodak, Giroptic and various vendors are increasingly developing better and high quality cameras & devices to capture superior 360 video experience.

Live Streaming

Mark Zuckerberg once stated that Live video is the future. And he was not wrong! The likes of Facebook Live, Instagram Live, and Twitch has made more people content creators and brought a lot more people closer to the screen. Apps such as Periscope, Meerkat, YouNow and Blab are allowing people share their daily experiences live. In terms of growth of users and revenue, Youtube, Facebook and Twitter have seen massive adoption and experienced higher revenue as a result of incorporating live video features to their platform.

Right now, with a good 4G connection, you can just tap on a button on these social media platforms and you are live. In the future, it will be easier for people even with 3G connections or less to stream live or participate in a live video session!

Creation

The mobile web can do now what mobile apps already have the capability to do. A very good example is the mini-Snapchat clone, the mustache app created by Francois Beaufort. This web app can be added and launched from the home screen.

With Mustache, you can record a video of yourself with a mustache and shapes locating different parts of your face. It generates a preview of the recorded video and then you can share it with the world.

Before play/recording
Before play/recording
After play/recording
After play/recording

Note: You need to enable the experimental web platform API from your Chrome browser. Head over to chrome://flags and activate it from the list before trying to run the app.

Let’s analyze the APIs that made this mobile web app possible:

const faceDetector = new FaceDetector({ fastMode: true, maxDetectedFaces: 1 });

let faces = [];
let recorder;
let chunks = [];
let isDetectingFaces = false;
let easterEgg = false;
let showFace = false;

async function getUserMedia() {
  // Grab camera stream.
  const constraints = {
     video: {
       facingMode: 'user',
       frameRate: 60,
       width:  640,
       height: 480,
     }
  };
  video.srcObject = await navigator.mediaDevices.getUserMedia(constraints);
  await video.play();
  canvas.height = window.innerHeight;
  canvas.width = window.innerWidth;
  // HACK: Face Detector doesn't accept canvas whose width is odd.
  if (canvas.width % 2 == 1) {
    canvas.width += 1;
  }
  setTimeout(_ => { faces = []; }, 500);
  draw();
}

We have the FaceDetector API and and the mediaDevices API for camera video streaming available as experimental web platform APIs on Chrome, Mozilla and Microsoft edge.

Note: Cloudinary provides an advanced Facial Attribute detection API as an add-on that you can use in your web applications.

The experimental Shape Detection API enabled the possibility of drawing a mustache and hat at 60 fps on the screen while streaming the video. While, the Media Recorder API provides functionality to easily record media.

Note: Cloudinary provides an advanced OCR Text Detection and Extraction API as an add-on that you can use in your web applications.

In the source code of the mustache app, this is how the blob, which is the recorded video is been uploaded to the cloud:

async function uploadVideo(blob) {
  const url = new URL('https://www.googleapis.com/upload/storage/v1/b/pwa-mustache/o');
  url.searchParams.append('uploadType', 'media');
  url.searchParams.append('name', new Date().toISOString() + '.webm');
  // Upload video to Google Cloud Storage.
  const response = await fetch(url, {
    method: 'POST',
    body: blob,
    headers: new Headers({
      'Content-Type': 'video/webm',
      'Content-Length': blob.length
    })
  });
  const data = await response.json();
  shareButton.dataset.url = `https://storage.googleapis.com/${data.bucket}/${data.name}`;
}

With Cloudinary, you can upload videos directly from the browser to the cloud and have the video URL returned to you with a simple API. You also can apply transformations on the video.

The APIs mentioned in the building of this app are pretty experimental and are currently being developed. But we expect they will become mainstream and stable in the future. Check out the source code for the full implementation.

Conclusion

Over the last few years, the web has seen a lot of innovation and advancement. Flash will no longer be supported by Adobe in 2020, HTML5 is now mainstream, several APIs have been, or are currently being, developed to bring native capabilities to the web. Better video codecs have come on board to ensure smooth and more efficient delivery and display of audio and video to users.

I’m really excited for what the future holds. Much of the information in this article came from Google I/O’s powerful session on the future of audio and video on the web. Many thanks to Google and the developer community for always championing the progressive movement of the web.

Finally, a huge shout out to the amazing Paul Lewis for developing the progressive video-on-demand Biograf app. The source code is available on GitHub.

Prosper Otemuyiwa Prosper Otemuyiwa is a Food Ninja, Open Source Advocate & Self-acclaimed Developer Evangelist.

Make All Images on Your Website Responsive in 3 Easy Steps

$
0
0

Images are crucial to website performance, but most still don't implement responsive images. It’s not just about fitting an image on the screen, but also making the the image size relatively rational to the device. The srcset and sizes options, which are your best hit are hard to implement. Cloudinary provides an easier way, which we will discuss in this article.

In this blog, I’ll share a valuable shortcut that can help you turn all images responsive. But first, let’s talk a bit about what responsive images are: An image is considered responsive if it retains its quality on varying device sizes without having an unpleasant impact on performance.

To better understand this concept, let's take a look at some device dimensions:

Possible Full-Width Image Dimensions by Screen Sizes

  • Large/X-Large commercial screens: 2000+ pixels
  • Websites: 760 - 1200 pixels
  • Mobile Phones: < 760 pixels

Let’s assume you have a mobile-first strategy for building responsive apps. You may decide to use 760px images throughout, without considering larger screens. If these images are meant to take the full width of the devices on which they are rendered, then your content will look distorted and unprofessional on websites or commercial screens.

Your next attempt would be to use the largest possible image (2000px) and scale it down based on the screen sizes on which it is displayed.

Down-Scaling Images

Using CSS or JavaScript to down-scale images only makes them dimensionally responsive. The following image illustrates this better:

Down-scaling illustration
On mobile

Down-scaling illustration
On desktop

Both on web and mobile devices, the size is still 8.9MB. Bearing in mind that your mobile phones have less resources than your PC, we still have more work to do.

Using only a down-scaling approach is not ideal because it does not account for the size of the image; just its dimensions.

We have already seen that up-scaling wouldn't work either, hence we need something that handles both dimensions and size.

Size-Fitting

Our best option is to generate images based on the screen size and render them. This process can be extremely complex, but there’s a shortcut that can help you automate much of the process. You can make all images responsive in three easy steps with Cloudinary:

1. Include Cloudinary in Your Project

Add the Cloudinary SDK to your project simply by including it in your index.html using script tags:

<script src="https://cdnjs.cloudflare.com/ajax/libs/cloudinary-core/2.3.0/cloudinary-core-shrinkwrap.min.js"></script>

2. Add Images with data-src

You don't want the images rendered immediately, until JavaScript runs. Hence, include the image using the data-src attribute instead of src:

<img data-src="http://res.cloudinary.com/christekh/image/upload/w_auto,c_scale/v1501761946/pexels-photo-457044_etqwsd.jpg" alt="" class="cld-responsive">

Using this approach, Cloudinary analyzes your browser screen first, resizes the image saved in Cloudinary storage as provided in data-src, and then renders the image in the appropriate size and dimension using JavaScript.

Two things to note from the tag:

  • w_auto,c_scale transformation parameters tell Cloudinary to dynamically generate an image URL scaled to the correct width value, based on the detected width actually available for the image in the containing element.
  • The class cld-responsive tells Cloudinary which images to apply this feature too.

3. JavaScript Call

Finally, initialize a Cloudinary instance in your JavaScript files and call the responsive method on this instance:

// Initialize
var cl = cloudinary.Cloudinary.new({  cloud_name: '<Cloud Name>' });
// Call
cl.responsive();

Remember to create a free Cloudinary account so you can be handed a cloud name for configuring this instance.

This piece of code will walk through the DOM, find all image tags with the class cld-responsive to apply size and dimension fitting images on them.

Final Words

Always keep in mind that when you use CSS like the following code below to make images responsive, it does not guarantee a good user experience:

img {
  width: 100%;
  height: auto;
}

The sizes of these images remain the same. Large images on mobile devices eat up resources (like allocated memory and running processes) causing slow downloads or unexpected behavior on the user's device. Responsive images ensure that users save lots of data bandwidth & have great experiences when using your image-rich website or app.

Lastly, it’s good to keep in mind that the suggested approach relies on JavaScript. Therefore, preloading provided by srcset and sizes are sacrificed and your browser must have JavaScript active for this feature to work.

Christian Nwamba Christian Nwamba (CodeBeast), is a JavaScript Preacher, Community Builder and Developer Evangelist. In his next life, Chris hopes to remain a computer programmer.

Generate Waveform Images from Audio with Cloudinary

$
0
0

This is a reposting of an article written by David Walsh. Check out his blog HERE!
I've been working a lot with visualizations lately, which is a far cry from your normal webpage element interaction coding; you need advanced geometry knowledge, render and performance knowledge, and much more. It's been a great learning experience but it can be challenging and isn't always an interest of all web developers. That's why we use apps and services specializing in complex tasks like Cloudinary: we need it done quickly and by a tool written by an expert.

While my previous experiments have been with images (Image Optimization, Remove Photo Backgrounds, and Automatic Image Tagging), Cloudinary also has the ability to manipulate video and audio files, as well as optimize delivery. This next experiment will mix imagery and media: we'll generate waveform images from an audio file!

Step 1: Upload the File

The first step is uploading the media file to Cloudinary, which you can automate with code or manually do so within the Cloudinary control panel. Let's presume the file is up on Cloudinary.

Step 2: Generate Image

You can use any number of languages to interact with Cloudinary's API but for the sake of this experiment we'll use Node.js and JavaScript. And the JavaScript required to generate and retrieve the basic waveform image? Much less than you think:

var result = cloudinary.image("Lights_qh6vve.png", {
	height: 200,
	width: 500,
	flags: "waveform",
	resource_type: "video"
});

So what exactly happens with the code above? Let's go through it:

  • The first argument, Lights_qh6vve.png, is the name of the uploaded MP3 file, replacing .mp3 with .png
  • The second argument provides the desired image settings, customizing height and width of generated image...
  • ...while flags: waveform and resource_type: video let Cloudinary know you want to generate the waveform image

The result is an img tag:

<img src='https://res.cloudinary.com/david-wash-blog/video/upload/fl_waveform,h_200,w_500/Lights_qh6vve.png' height='200' width='500'/>

..which looks like:

Customizing the Image

Cloudinary provides flexibility in image generation so let's create a more customized waveform image. Let's play with the colors:

var result = cloudinary.image("Lights_qh6vve.png", {
	height: 200,
	width: 500,
	flags: "waveform",
	resource_type: "video",
	background: '#acd7e5',
	color: '#ffda7f'
});

These colors generate a waveform image that looks like this:

Next we can use offset properties to get just a snippet of the waveform image:

var result = cloudinary.image("Lights_qh6vve.png", {
	height: 200,
	width: 500,
	flags: "waveform",
	resource_type: "video",
	background: '#acd7e5',
	color: '#ffda7f',
	start_offset: 1, // in seconds
	end_offset: 240
});

Which gives us this sharp image:

This experimentation was a lot of fun, and proves waveform image creation is just another amazing function provided by Cloudinary. Cloudinary is (an awesome) one-stop shop for uploading manipulating and delivering images and video. If you need to manipulate image or simply think you may need to do so in the future, give Cloudinary a good look -- they will do more than you think!

 

David Walsh David Walsh is Senior Software Engineer at Mozilla, having worked extensively on the Mozilla Developer Network, Firefox OS TV, WebVR, internal tooling, and several other Mozilla efforts. He shares his knowledge on his blog at http://davidwalsh.name. You can also find him at @davidwalshblog on Twitter.

SEO-Friendly Images: Rank Higher on Google and Facebook

$
0
0

Facebook recently made an announcement that every person in digital marketing should pay attention to: The faster your website loads, the more likely it is to show up in Facebook’s news feeds. (News Feed FYI: Showing You Stories That Link to Faster Loading Webpages )

Reducing your page load times is probably already a priority for a number of reasons:

  • Higher rankings from search engines like Google (SEO benefits),
  • Improved user experiences on your site
  • Increased customer engagement
  • Higher conversion rates and sales

News about Facebook’s upcoming changes adds another reason to speed up your site: Increasing the ROI from your social marketing team’s efforts.

As marketers we work incredibly hard to gain loyal, happy customers. And then we work even harder to encourage that customer to be an advocate and bring us additional customers, spending an ever-increasing amount of time and money in social strategies and marketing efforts. Typically the highest value traffic is sourced when one customer trusts your brand enough to advocate on your behalf. You shouldn’t let something like your page load speed prevent other people from ever seeing that hard-earned “like” or “share.”

If you haven’t already, here is one quick tip to improve your page load speeds – optimize your images. Don’t believe me? Run your website through WebPageTest.org and then scroll down to the Content Breakdown section. (I ran this test on Vikings.com – hopefully they read this, improve their website performance, and then use the gained fan engagement to bring a Super Bowl trophy to Minnesota.)

Content Breakdown

In this example, 88 percent of the site’s weight are images – that’s no small percentage. Think about that for a second. A 50 percent reduction in image weight would reduce the overall page weight by close to half. If you’re looking for a place to start, for just about every brand out there, it could be a matter of tackling their image and video weights and load times.

Next, click on the Image Analysis tab. Now instead of theoretical improvements, you’re able to look at your page and analyze if image optimization truly would provide any gains.

Webspedtest

Note: Cloudinary has the ability to create SEO-friendly image URLs


The bottom line

A reduction in page weight like this could result in faster loading - by multiple seconds, depending on the site - which can translate into millions of dollars of increased revenue for an average, mid-sized retailer. Rather than re-hash all the research, here is a great article from the smart folks at Kiss Metrics on how page-load speed can impact revenue.

Now that we’ve identified an area for improvement, it’s time to implement. The good news is that it’s relatively straight-forward, quick and painless for IT teams to get Cloudinary in place and start down the road to reducing page weight and load time. The better news is that you can create a free account to try this out and prove its potential.

Bringing Women Who Code Together to Learn About Cloudinary

$
0
0

For the last few years I’ve been active as a member of the Silicon Valley chapter of Women Who Code, a nonprofit that encourages women in tech careers. I’ve loved being a part of it, meeting new people, working with mentors and going to meet-ups where I can learn skills or become more proficient in my iOS expertise.

So, when I joined Cloudinary as a developer support engineer in May 2017, I thought it would be great to host Women Who Code at our office and share how our service makes developers lives so much easier. In just a few short months that dream became a reality – we hosted our first such meet-up on August 24 at our headquarters here in Sunnyvale.

“Shirly
Shirly Leading the Course
“A
A Review of Cloudinary

About 30 developers – who had varying degrees of knowledge of iOS, Xcode and Swift – attended our meet-up. We started the evening off with dinner, drinks and networking, then got down to business. The Cloudinary team taught participants about the differences between images, and explained how we make it easier to manipulate images and optimize them for faster download.

“Robert
Robert Showing the Basics

After a quick tutorial by Robert Moseley our Director of Solutions Engineer, showing Cloudinary in action improving images and video on-the-fly, participants got hands-on experience building their own working iOS app that included Cloudinary-manipulated images.

Using Cloudinary in the sample app was so easy that we had time left over to show everyone how to do even more transformations and answer their questions. At the end of the evening, each participant departed with a goody bag, filled with fun swag, like a Rubik’s Cube, camera cover, fidget spinner, bottle opener and stickers.

In preparing our fun, educational presentation, our goal was to help developers learn about the importance of optimizing images, and the best ways to do it. I think we succeeded. Since the meet-up, we’ve gotten a lot of positive feedback. In fact, one participant was so inspired by Cloudinary’s ease of use and ability to speed up apps that he is completely updating his app using our service.

Given the success of our first meet-up, we hope to host other similar events in the future. Stay tuned!

Responsive Images Guide, Part 3: Variable Image Encoding

$
0
0

A hand adjusting two dials – one which switches between multiple FORMATs, and another which dials QUALITY up and down

Welcome to the latest edition of the Responsive Images Guide!

In part 1, I laid out the big idea: a responsive image is a variable image – which adjusts itself to fit variable contexts.

In part 2, we looked at the most common way that an image can do exactly that: scaling itself up and down to fit viewports of different sizes and screens with different densities.

Now, we turn our attention to a different axis of variability: variable image encoding.

What do I mean when I say, “variable encoding”? Two things:

  1. Delivering an image in different formats (depending on the end-user’s browser).
  2. Striking different quality/compression tradeoffs (depending on the end user’s screen, connection speed, data plan, and/or preferences)

Let’s tackle both of these, in turn.

Variable Formats

A file whose extension changes between JPG, PNG, GIF, SVG, WEBP, JP2, and JXR

There are many (many) different image formats, and new ones are being developed all of the time.

This varied, evolving landscape has vexed the <img> tag from the very start. To wit—

I'd like to propose a new, optional HTML tag:

IMG

Required argument is SRC="url".

[…]

Browsers should be afforded flexibility as to which image formats they support. Xbm and Xpm are good ones to support, for example. If a browser cannot interpret a given format, it can do whatever it wants instead (X Mosaic will pop up a default bitmap as a placeholder).

[…] I know this is hazy wrt image format, but I don't see an alternative than to just say ``let the browser do what it can'' and wait for the perfect solution to come along (MIME, someday, maybe).

Let me know what you think.........

Marc Andreessen (February 1993)

“Just let the browser do what it can”

Let’s tug on this historical thread a bit and see where it goes, shall we?

Mosaic 0.10, the browser that Marc and his team released just three weeks (!) after proposing IMG to the world, supported two formats: Gif and XBM.

Support for anything else wasn’t suggested until almost a year later.

Hello,

Is there a way to include Inlined image not in gif format?

Pini Albilia (January 1994)

This innocent, 13-word question ignited a firestorm of responses. Some of them clung tightly to the idea of hypertext:

The goal of WWW is to build a distributed, global, hypertext system; not to implement every "feature du jour" that comes along. The correct, and current, solution to this problem is to simply create a hypertext link to the object in question.

Tony Sanders (January 1994)

One response proved remarkably prescient:

As a (still learng) ) HTML programmer, could I just request one possible extension for a future version of Mosaic, and that's JPEG handling?

For a specific (but very useful) category of images, they're very useful, basically because they can be far smaller as long as you don't mind a few artefacts. [...] Since bandwidth will always lag behind storage, I think this is quite significant for the 'Web.

Andy Holyer (January 1994)

But the most important reply, for our purposes, came from a programmer working on Mosaic:

The advantage to using Gifs, however, is that the document will be viewable in all browsers that support inlined images. If you add a new format, the document will only be viewble by a very limited set of people... At least in terms of Mosaic, we have currently only agreed to support gifs and xbms on all three platforms...

Jon E. Mittlehauser

Sound familiar?

You can’t use—

—on the web, because if you do, a majority of your users won’t be able to see anything at all. And browsers have strong incentives to be extremely conservative about rocking this boat of stable, universal support for a small set of formats.

Tale as old as time – or at least, <img>.

And so, after two decades, there are only four image formats that enjoy usably-universal support: GIF, JPEG, PNG, and SVG.

“(MIME, someday, maybe)”

Must we limit ourselves to serving up these lowest-common-denominators?

No!

In 2017, we can serve different formats to different users, depending on what their browser can handle. We can, for instance, send a WebP to Chrome, a JPEG-2000 to Safari, and a JPEG-XR to Edge – and still fall back to a trusty ol’ JPEG in Firefox.

How? In one of two ways.

On the client, with <picture> and <source type>

The first (and newest!) way to serve up adaptable-format images is via the <picture> and <source> elements, using the type attribute. Jason Grigsby has a nice write up of the details; in short, we can give the browser a bunch of different URLs and mark them up with those MIME types that Mark referenced way back in 1993. The browser loads the first URL whose type it supports.

On the server, with Accept

There’s also a much older way to accomplish more or less the same thing, called Content negotiation.

If you’re writing markup, <picture>/<source type> pattern is a lot more work than plain old <img src>. You have to generate a bunch of different resources, decide an order of preference, and write out a bunch of extra HTML. Content negotiation says: let’s do all of that work on the server. The client can tell the server which formats it supports up front, via a list of MIME types in the Accept HTTP header. Then the server can do all of the hard work of generating and managing alternate resources, and deciding which ones to send to which clients.

Unless you’re as handy on the back-end as you are on the front-end, this can be difficult to set up yourself. But if you use an image hosting/delivery service like Cloudinary, it’s as easy as adding a few characters to your URL. For instance, this:

<img src="https://res.cloudinary.com/demo/f_auto/sample.jpg" alt="A bee on a flower." />

Delivers a WebP to Chrome, a JPEG-XR to Edge, and a JPEG to Safari and Firefox – all from the same src.

Whether you use multiple URLs in <picture>/<source type> blocks, or a single URL pointing to a smart host, the ability to serve up multiple, alternate formats is not only great for users – it’s also great for the web. The ability to fall back safely to older formats gives developers the ability to safely leap forward and use cutting edge formats before they’re supported in every browser. The next twenty years should be a lot more exciting than the last twenty, when it comes to image formats on the web.

Variable quality/compression

A file, getting progressively more artifacty/clear as the quality dials up and down.

So that’s variable, responsive, image formats, sorted.

What else about the way that images are encoded might we be able to adapt on the fly, in order to give each user the best-possible experience?

Many things! Image compression is complicated and encoders provide a multitude of options, toggles, and switches which let you determine the nitty-gritty details of how your image will be compressed. Most encoders also wrap those myriad settings up into one big, friendly, user-facing setting, called “quality.” A high–“quality” file will be nearly indistinguishable from the original, but will also be quite large, file-size-wise. A low-“quality” file may contain distracting artifacts, but it will weigh much less than a high-quality file would. A well-engineered website strikes a judicious balance, serving up images that look fairly good, and load fairly fast.

In the bad old days when I used to “Save for Web” all of my images by hand out of Photoshop, I had a lucky JPEG quality number: 72. At some point, I’d done some quick-and-dirty trial-and-error and determined that, for most images, “unacceptable” compression artifacts started to creep in when Photoshop’s quality was below 60, but “Save for Web” qualities over 80 or so provided diminishing returns. So for years, I exported everything at 72 and never really thought much more about it.

How low can you go?

Imagine my surprise when a hot new responsive image technique started making the rounds, advocating for a new go-to quality level: zero.

Turns out, JPEG’s compression artifacts aren’t as noticeable when they’re rendered at Hi-DPI, and when browsers scale 2x, highly-compressed images down to fit 1x screens, most of the artifacts get scaled/blurred away, too.

Before we had good, standard, ways to serve up images in multiple, alternate resolutions, compressive images provided a promising path forward. Unfortunately, even though they saved bytes over the wire, compressive images did nothing to save browsers from all of the work that they have to do after they’ve downloaded an over-sized image, namely: decoding, storing, and resizing it. Double turns out, sending high-resolution images to everyone, even if they’re highly-compressed and reasonably light, can lead to slow paint times, janky scrolls, thrashed memory and trashed battery-life on low-powered devices.

So, compressive images were not the one true responsive images solution. But they did teach us something: we can and should send lower-quality/higher-compression images to hi-DPI screens, where compression artifacts are less visible. “Compressive images” provided the first, concrete use case for variable-compression images, and it wasn’t long until lights started going off in people’s heads about a much, much bigger one.

Your next billion users

Ilya Grigorick summarizes it nicely in this article about the Save-Data Client Hint.

In short, billions (billions!) of internet-connected people pay dearly – with their time and money – for what we in the wealthy west consider small amounts of data. Most of the world still connects to the internet via 2G, and the income-adjusted cost of loading a few hundred kilobytes to a user in, say, Afghanistan is staggering. Thus, almost 9% of all page loads and 41% of those from India come from a browser that most westerners have never heard of: UC Browser, which re-compresses and optimizes sites on proxy servers, before sending them to users. And the vast majority of gains that UC Browser and other proxy browsers like it provide come from – you guessed it – re-compressing images.

So: why not cut out the middleman? Why not do that extra compression for users with constrained connections ourselves, on our own servers? What if we could dynamically dial our image quality/compression up and down, per-user, in response to connection speed and user preferences?

This is the promise of the Save-Data Client Hint. The hint is currently only emitted by Blink-based browsers, but that shouldn’t stop you from using it to implement responsively-compressed images today. If you don’t have the ability to implement support for it on the back end, you can craft your own proxy-of-a-sort on the front-end using Service Workers – or just point to an image hosting/delivery service like Cloudinary, which will smartly handle variable compression for you, automatically.

Variable encodings for variable contexts

So, to review:

  • A responsive image is a variable image that adapts to meet variable contexts.
  • The most important axis of variability, when it comes to responsive images, is resolution.
  • In this article, we looked at another: variable image encoding. Which is really an umbrella term for two (related) things: format-switching, based on which formats a given browser supports, and quality/compression-switching, based on a user’s screen resolution, connection, and/or preferences.

So, so far, we’ve looked at dynamically adapting images’ height, width, format, and quality. What else might we want to change about our images in order to respond to users’ particular contexts? Join us next time, when we’ll reach the final frontier of responsive image variability: variable image content.


  1. Allow me to close the loop on our JPEG support story. Marc left the Mosaic project in ’93 and founded Netscape in April of ’94. When Netscape Navigator was announced to the public in the fall of ’94, JPEG support was one of its three big headline features. Mosaic quickly followed suit, shipping JPEG support six months later. So when Microsoft licensed Mosaic’s code and used it as the foundation of Internet Explorer 1.0, released in August of 1995, it supported JPEGs, too. Thus, supported by both major browsers, JPEG became the standard format for photographic images on the web. Twenty-two years later, it still is.

Google For Nigeria: We saw it all…

$
0
0

GDG Lagos

Note from Cloudinary: Christian Nwamba, a frequent Cloudinary contributor, recently attended, and was a main speaker, at the Google Developer Group (GDG) Conference in Lagos, Nigeria. Christian led a session teaching more than 500 developers how to “Build Offline Apps for the Next Billion Users.” The stack he used included JS (Vue), Firebase, Service Workers and Cloudinary. Below is his account of the conference and his talk.


When Sundar Pichai got on a plane to Nigeria to herald the latest edition of Google For Nigeria, he knew that there would be more than learning about and enjoying the rich culture of a vast and diverse nation. In addition, he would be meeting with a lot of brilliant minds who play crucial roles in Google’s vision for the internet and its next billion users – Nigerians, who are tech inclined and otherwise budding, vibrant and full of energy. It was this energy that attracted Pichai to the scene. Without the astounding effort from hardworking Nigerians, such as Titi Akinsanmi, head of Policy and Government Relations for Google Africa, the presence of the man who took the helm of Google some two years ago might have never been felt.

It was no surprise that Pichai decided to visit Lagos, the most populated city in Nigeria. While taking a walk in “Computer Village,” the city’s busiest market known for dealing in gadgets and technology, he couldn’t help but admire the versatility and diversity of the average Nigerian.

At Google For Nigeria, ways of improving digital access to the average African were discussed, with emphasis laid on Nigeria, Kenya and South Africa. Some of these concepts include:

Launchpad Accelerator Africa

In a bid to do more than just support African entrepreneurs in creating successful tech startups and content, Google’s Launchpad Accelerator program will make more than $3 million available to more than 60 African startups for equity-free funding, working space and access to expert advisers over three years. Google also created a new Google Launchpad space in Lagos. It’s the first space of it’s kind outside the United States, and it will be where rigorous quarterly programs are held twice a year.

Grants from Google

Google’s charitable arm, google.org, is committed to donating $20 million over the next five years to nonprofit organizations that are working to improve lives across Africa. It also pledged $2.5 million in initial grants to the nonprofit arms of African startups Gidi Mobile and Siyavula to provide free access to education for 400,000 low-income students from South Africa and Nigeria. Google.org will develop new digital learning materials that will be free for anyone to use. In a bid to create further positive impact in Africa, Google also plans to invite nonprofits from across the continent to share insights and innovations on how they could impact their community and beyond. For this purpose, Google will be launching a Google.org Impact Challenge in Africa in 2018 to award $5 million in grants. Applications will be open to any eligible nonprofit in Africa and the best ideas will be selected by online voting online open to anyone.

Digital Skills for Africa

Google exceeded expectations of training one million young people in Africa when they set out to help bridge the digital skills gap in the region 12 months ago. Now Google plans to expand this program, committing to prepare another 10 million people for jobs of the future in the next five years. The helpful digital skills being imparted include teaching people how to build a web presence, using search to find jobs, using social media, getting tips to enhance their curriculum vitae, among other topics. With an initial focus on Nigeria, Kenya and South Africa, Google also plans to provide mobile developer training to 100,000 Africans to develop world-class applications.

YouTube Go

A new innovation from Google – YouTube Go – lets users discover, save and share videos they love. YouTube Go improves the experience of watching videos over a slower network and gives control over the amount of data used in saving or streaming videos. Nigeria is the second country where this app has been actively tested and later this year, Google has plans to launch a beta version of the app, which will be available to all Nigerian users.

Lagos now on Street View in Google Maps

Having improved their address search experience in Lagos by adding thousands of new addresses and streets, outlines of more than a million buildings in commercial and residential areas and more than 100,000 additional Nigerian small businesses on Google Maps, Google then launched Lagos on Street View, with 10,000 kilometers of imagery. You can virtually drive from one location to the other in the city of Lagos, using your smartphone.

A few software developers had the opportunity to present their works and projects to Pichai after which helpful comments, insights and criticism were given. The day was rounded up with dances and displays to showcase Nigeria’s ethnic and cultural diversity.

Good to go

Startup stand-ups

PWA demos with Sundar

Feel free to explore the #googlefornigeria on Twitter for more exciting content from the event.

Google Developer Group Conference

The following day, July 28, was the GDG (Google Developer Group) Lagos Conference. This event featured a theme of “Building World Class Apps: Tools I Use and How.” GDG Lagos is a group for those who are interested in learning about, and developing solutions and apps, using Google technologies.

The conference provided insights on various solutions to different problems in the tech ecosystem, and focused on how to improve and enhance performance of web and mobile apps via different frameworks and methodologies. The audience comprised mainly of software engineers, designers, developer advocates and developers were from Nigeria and a variety of other Africa countries. There were a number of speakers were present, the most notable of which include:

  1. Tunde Dominic, co-author of Swallow.js, a JavaScript framework used to build Firebase projects, talked about using Firebase on the web.

    Tunde Dominic
  2. Moyin Adeyemi, an Android developer, explained how to work with, and achieve maximum results, with Android Studio.

    Moyin Adeyemi
  3. Timi Ajiboye, a full stack developer at helloworld.ng, talked about getting started with GraphQL.

    Timi Ajiboye
  4. Adewale Abati, a freelance full stack developer and designer, discussed getting started with Gathered; an open source app for meetups.

    Adewale Abati
  5. Sunday Akinsete, also a co-author of Swallow.js. Sunday's session, showcased Android apps built on Firebase.

    Sunday Akinsete
  6. Christian Nwamba, JavaScript preacher and community builder, shared how to build offline apps for the next billion users.

    Christian Nwamba

Building Offline Apps for the Next Billion Users

Bearing in mind that Nigeria and Africa as a whole is comprised of a lot of developing and under developed societies with huge populations, the Next Billion Users initiative (introduced by Google) is a perfect fit. This led to my talk on building apps that users in this part of the country can use effectively despite the lack of internet connectivity.

My presentation started by describing the problems African users face. An average citizen can only afford a 3G connection both at home, and sometimes, at their offices. Even with these limits, average citizens are aware of content-intensive websites, like YouTube or Netflix. Bridging the gap between them and this content was the major problem.

With an understanding of the problem, I started digging into the Progressive Web Apps (PWA) concepts with some funny slides to help the massive audience appreciate the topic and not get bored from the complex technologies behind the concept. I also illustrated the concept by relating to their typical life stories as Nigerians and the challenges they face with no/poor internet connectivity.

Service workers, caches, web storage and manifest are terms that gets you thrown away when you attempt building PWAs. These concepts were explained with enticing examples. I also shared some statistics and success stories of Flipkart, AliExpress and Housing.com, which already use PWA concepts.

Before the demo, I sounded a slight warning — reminding the audience that PWAs are most useful on mobile phones. Hence, they need to take care when delivering content to the users. The audience was advised to optimize their content, especially media content using Cloudinary; pre-fetch resources during idle times; split bundles and cache vendor files; load images progressively with Cloudinary; use lazy load routes, and so on.

You can learn more from the slides.

Android Learning Community

Midway through the sessions, the first set of graduates from the ALC (Android Learning Community) were announced and celebrated. Outstanding performers were awarded gifts, such as Google Home.

The End

At this point the event was drawing to a close, while tech enthusiasts networked, exchanged ideas and took memorable photos, the host of the event – Femi Taiwo, a Google Developer Expert –thanked everyone for coming and thanked Google for making massive impact on the lives of Africans, and Nigerians in particular. The attendees then came together for one last group photograph and the curtains were drawn on a great gathering that will be remembered for years to come.

Thanks to Raphael Ugwu for helping gather event details!

Christian Nwamba Christian Nwamba (CodeBeast), is a JavaScript Preacher, Community Builder and Developer Evangelist. In his next life, Chris hopes to remain a computer programmer.

Your Web Image is Unnecessarily Bloated

$
0
0

Images make up a majority of web content.But a lot of websites and applications that we browse aren’t always delivering the most optimal image format, size, quality and dimension.. . .

As a developer, it seems inefficient to serve a 2000kb JPEG image when we could compress images to optimize the performance without degrading the visual quality.

We are not new to this kind of responsibility. But our productivity will end up being questioned if we do not deliver fast. In order to do so, the community has devised several patterns to help improve productivity. Let's review few of these patterns based on their categories:

  • Optimized formats
  • Third-party Libraries
  • Third-party APIs

Optimized Formats

It is natural to use image formats such as PNG, JPEG and GIF. These three have been around for several years, but they each have characteristics that would make you choose one over another in certain cases. Additional formats have been introduced and some are being standardized in order to provide a better way to serve images in their best optimization level.

Examples

  • WebP
  • JPEG-XR
  • BPG

These optimized formats are, as a matter fact, a very good attempt at reducing the image bloat. Image formats are the means for achieving convenient optimization and compression without resorting to third-party solutions. Unfortunately, the image format battle continues, as there is inadequate support across all browsers, limitations, policies and other factors that make it hard to ensure every image format is consistently optimized.

Third-Party Libraries

You can also use third-party libraries to compress images. These libraries provide an exposed API method to which you can pass your image file, specify some compression argument, and have a compressed image returned for your configuration.

There is nothing wrong with writing your own solution, but we are trying to save time as well. These libraries are written based on some standard algorithms so it's fine to rely on them to some extent. However, they may not give you enough flexibility in terms of how compression is handled. It's hard to find a third-party library that takes a PNG file and delivers an optimally compressed lossless image.

Third-Party APIs

There are public and premium third-party APIs that could be used to process a bloated image, which is then returned to you as an HTTP response. This so far is the most easier to use because it just requires basic knowledge of HTTP to get your images compressed. It's not language or platform dependent which makes it a little more flexible than the others.

Examples:

Plus, you encounter the same issue as you do with third-party SDKs: It's hard to find that all-in-one-solution that is flexible enough to address your problem.

A Better Choice That Offers CDN

While the platforms discussed above are commonly used for compressing images, not one offers a comprehensive, intelligent solution for compression and optimization. Image delivery and upload may even become more complex when you need to serve your images via a content delivery network (CDN). This adds another layer of complexity to the problem.

But, what if there was a single tool that could:

  1. Upload, store and deliver images via CDN
  2. Transform images (dimension, color adjustment, etc)
  3. Compress images losslessly

Cloudinary is that solution. It’s a comprehensive image management tool that quickly and easily addresses your image concerns, eliminating the challenges and risks of image storage and management.

Cloudinary also intelligently addresses our image compression challenges. It uses image transformation features to give you control over the quality of your images, while giving you the option to automatically select quality and format.

What's most interesting about Cloudinary is that all these features are URL-based, which means you simply need to alter the URL to achieve the configurations you need. You also can use the provided SDKs if you prefer not to do URL manipulation.

Image Quality Transformation

The following image of a woman was delivered using Cloudinary (the upload mechanism is not covered but you can read about that here):

Ruby:
cl_image_tag("woman.jpg")
PHP:
cl_image_tag("woman.jpg")
Python:
CloudinaryImage("woman.jpg").image()
Node.js:
cloudinary.image("woman.jpg")
Java:
cloudinary.url().imageTag("woman.jpg")
JS:
cl.imageTag('woman.jpg').toHtml();
jQuery:
$.cloudinary.image("woman.jpg")
React:
<Image publicId="woman.jpg" >

</Image>
Angular:
<cl-image public-id="woman.jpg" >

</cl-image>
.Net:
cloudinary.Api.UrlImgUp.BuildImageTag("woman.jpg")
Android:
MediaManager.get().url().generate("woman.jpg")

It's delivered with this URL:

https://res.cloudinary.com/demo/image/upload/woman.jpg

This image weighs about 569kb. This is not a bad quality, but we can do better. Let's see how much we can adjust this image without losing visual quality:

Ruby:
cl_image_tag("woman.jpg", :quality=>90)
PHP:
cl_image_tag("woman.jpg", array("quality"=>90))
Python:
CloudinaryImage("woman.jpg").image(quality=90)
Node.js:
cloudinary.image("woman.jpg", {quality: 90})
Java:
cloudinary.url().transformation(new Transformation().quality(90)).imageTag("woman.jpg")
JS:
cl.imageTag('woman.jpg', {quality: 90}).toHtml();
jQuery:
$.cloudinary.image("woman.jpg", {quality: 90})
React:
<Image publicId="woman.jpg" >
  <Transformation quality="90" />
</Image>
Angular:
<cl-image public-id="woman.jpg" >
  <cl-transformation quality="90">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Quality(90)).BuildImageTag("woman.jpg")
Android:
MediaManager.get().url().transformation(new Transformation().quality(90)).generate("woman.jpg")
https://res.cloudinary.com/demo/image/upload/q_90/woman.jpg

The q transformation property takes a variety of values, one of which is a range of 1 to 100 that indicates the quality in percentages. This is what we have applied above and the image was trimmed down to 123kb. We just eliminated 446kb, which is a great example of what we mean by the title of this article: "Your Web Image is Unnecessarily Bloated."

Let's go hard on this image and see what's the worst that can happen:

Ruby:
cl_image_tag("woman.jpg", :quality=>40)
PHP:
cl_image_tag("woman.jpg", array("quality"=>40))
Python:
CloudinaryImage("woman.jpg").image(quality=40)
Node.js:
cloudinary.image("woman.jpg", {quality: 40})
Java:
cloudinary.url().transformation(new Transformation().quality(40)).imageTag("woman.jpg")
JS:
cl.imageTag('woman.jpg', {quality: 40}).toHtml();
jQuery:
$.cloudinary.image("woman.jpg", {quality: 40})
React:
<Image publicId="woman.jpg" >
  <Transformation quality="40" />
</Image>
Angular:
<cl-image public-id="woman.jpg" >
  <cl-transformation quality="40">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Quality(40)).BuildImageTag("woman.jpg")
Android:
MediaManager.get().url().transformation(new Transformation().quality(40)).generate("woman.jpg")
https://res.cloudinary.com/demo/image/upload/q_40/woman.jpg

I just took the quality down to 40 and trimmed down the size to 38kb, yet the visual quality of the image is partially degraded. By now you could imagine the bandwidth you’d be wasting because of the lack of compression.

Let's try 10 percent.

Ruby:
cl_image_tag("woman.jpg", :quality=>10)
PHP:
cl_image_tag("woman.jpg", array("quality"=>10))
Python:
CloudinaryImage("woman.jpg").image(quality=10)
Node.js:
cloudinary.image("woman.jpg", {quality: 10})
Java:
cloudinary.url().transformation(new Transformation().quality(10)).imageTag("woman.jpg")
JS:
cl.imageTag('woman.jpg', {quality: 10}).toHtml();
jQuery:
$.cloudinary.image("woman.jpg", {quality: 10})
React:
<Image publicId="woman.jpg" >
  <Transformation quality="10" />
</Image>
Angular:
<cl-image public-id="woman.jpg" >
  <cl-transformation quality="10">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Quality(10)).BuildImageTag("woman.jpg")
Android:
MediaManager.get().url().transformation(new Transformation().quality(10)).generate("woman.jpg")
https://res.cloudinary.com/demo/image/upload/q_10/woman.jpg

Now the image is visually poor. This doesn't mean we can tell for sure that 40 percent is the perfect point. It could be 30, it could be 20, but we don’t know exactly To find a perfect quality, we can use the auto quality value rather than a rigid value:

Ruby:
cl_image_tag("woman.jpg", :quality=>"auto")
PHP:
cl_image_tag("woman.jpg", array("quality"=>"auto"))
Python:
CloudinaryImage("woman.jpg").image(quality="auto")
Node.js:
cloudinary.image("woman.jpg", {quality: "auto"})
Java:
cloudinary.url().transformation(new Transformation().quality("auto")).imageTag("woman.jpg")
JS:
cl.imageTag('woman.jpg', {quality: "auto"}).toHtml();
jQuery:
$.cloudinary.image("woman.jpg", {quality: "auto"})
React:
<Image publicId="woman.jpg" >
  <Transformation quality="auto" />
</Image>
Angular:
<cl-image public-id="woman.jpg" >
  <cl-transformation quality="auto">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Quality("auto")).BuildImageTag("woman.jpg")
Android:
MediaManager.get().url().transformation(new Transformation().quality("auto")).generate("woman.jpg")
https://res.cloudinary.com/demo/image/upload/q_auto/woman.jpg

The auto value produces an image that weighs 45kb. This is easier and quicker than using a rigid value and guessing the perfect compression rate.

Auto Formats

In addition to quality transformations, you also can define automatic transcoding for your images. This enables Cloudinary to choose a suitable, and optimal, image format for the browser rendering the image:

Ruby:
cl_image_tag("woman", :fetch_format=>:auto)
PHP:
cl_image_tag("woman", array("fetch_format"=>"auto"))
Python:
CloudinaryImage("woman").image(fetch_format="auto")
Node.js:
cloudinary.image("woman", {fetch_format: "auto"})
Java:
cloudinary.url().transformation(new Transformation().fetchFormat("auto")).imageTag("woman")
JS:
cl.imageTag('woman', {fetch_format: "auto"}).toHtml();
jQuery:
$.cloudinary.image("woman", {fetch_format: "auto"})
React:
<Image publicId="woman" >
  <Transformation fetch_format="auto" />
</Image>
Angular:
<cl-image public-id="woman" >
  <cl-transformation fetch_format="auto">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().FetchFormat("auto")).BuildImageTag("woman")
Android:
MediaManager.get().url().transformation(new Transformation().fetchFormat("auto")).generate("woman")
https://res.cloudinary.com/demo/image/upload/f_auto/woman

Browsers, such as Chrome, support specific formats, like WebP, to improve performance . Cloudinary always knows when and how best to do this without losing the visual quality of the image.

Conclusion

You website images might be bloated or optimized to a certain level. You can use image formats, third-party libraries and APIs to optimize images, but they might not be as flexible as you need them to be.

Cloudinary offers a comprehensive image management solution that delivers and transforms images via URL-based APIs. You can utilize the transformation features for image optimization and compression.

You can sign up for a free Cloudinary account and see how easy it is to optimize images with its URL-based solution.

This post was originally published on Noupe.com

Christian Nwamba Christian Nwamba (CodeBeast), is a JavaScript Preacher, Community Builder and Developer Evangelist. In his next life, Chris hopes to remain a computer programmer.

Build A Miniflix in 10 Minutes

$
0
0

Developers are constantly faced with challenges of building complex products every single day. And there are constraints on the time needed to build out the features of these products.

Engineering and Product managers want to beat deadlines for projects daily. CEOs want to roll out new products as fast as possible. Entrepreneurs need their MVPs like yesterday. With this in mind, what should developers do?

In this tutorial, we’ll quickly build out a Mini Netflix in 10 minutes. In fact, I think we might build it less time.

MVP Challenge

An excited entrepreneur just approached you to build a video service. A service where users can quickly upload short videos and share on twitter for their friends to view. Let’s list out the features of this app.

Features

  • Users should be able to sign up and log in.
  • Registered/Logged-in users should be able to upload short videos of about 20 - 30 seconds.
  • Registered/Non-registered users should be able to view all uploaded videos on the platform on a dashboard.
  • Users should be able to share any of the videos on twitter.

Now, here’s the catch! T’challa of Wakanda wants to invest in some startups today, so the entrepreneur needs to demo the MVP of the video service in 10 minutes from now.

I know you are screaming your heart right now. It’s totally okay to cry and let the world know about your problems and challenges, but after much ado shedding tears, will the app be ready in 8 minutes? Well, sorry - tears can’t build an app!

Solution

It’s possible to build the MVP our entrepreneur is asking for. Let me show you how! Ready your editor, your command line and anxious fingers. Let’s get to work!!!

Step 1. Flesh Out The App

We’ll use React to build out the app. Facebook has a tool, create-react-app that can scaffold a progressive web app out of the box in less than a minute. If you don’t have it installed, please install and run the command below in your terminal:

create-react-app miniflix
cd miniflix

Go ahead and open up public/index.html. Pull in bootstrap and add it just after the link to the favicon.

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

Step 2. Set up Authentication & Views

Go ahead and install the following packages from your terminal:

npm install auth0-js react-router@3.0.0 jwt-decode axios
  • auth0-js - For authentication
  • react-router - For routing within our app
  • jwt-decode - For decoding the JSON Web Token in our app
  • axios - For making network requests.

Open up your src directory and create a components and utils folder. In the utils folder, create a file, AuthService.js and add the code here to it. I explained how to handle the authentication in this tutorial, so check it out to ensure you are on the right track.

We’ll create 4 components in the components folder. Callback.js, Display.js, Nav.js and Upload.js

The Callback component basically stores our authentication credentials and redirects back to the upload route in our app.

The Display component will be dashboard for viewing all videos.

The Nav component will be the navigation that all pages in the app will share.

The Upload component will handle uploading of videos by registered users.

Add this piece of code below to components/Callback.js:

import { Component } from 'react';
import { setIdToken, setAccessToken } from '../utils/AuthService';

class Callback extends Component {

  componentDidMount() {
    setAccessToken();
    setIdToken();
    window.location.href = "/";
  }

  render() {
    return null;
  }
}

export default Callback;

Add this piece of code to components/Nav.js:

import React, { Component } from 'react';
import { Link } from 'react-router';
import { login, logout, isLoggedIn } from '../utils/AuthService';
import '../App.css';

class Nav extends Component {

  render() {
    return (
      <nav className="navbar navbar-default">
        <div className="navbar-header">
          <Link className="navbar-brand" to="/">Miniflix</Link>
        </div>
        <ul className="nav navbar-nav">
          <li>
            <Link to="/">All Videos</Link>
          </li>
          <li>
            {
             ( isLoggedIn() ) ? <Link to="/upload">Upload Videos</Link> :  ''
            }
          </li>
        </ul>
        <ul className="nav navbar-nav navbar-right">
          <li>
           {
             (isLoggedIn()) ? ( <button className="btn btn-danger log" onClick={() => logout()}>Log out </button> ) : ( <button className="btn btn-info log" onClick={() => login()}>Log In</button> )
           }
          </li>
        </ul>
      </nav>
    );
  }
}
export default Nav;

In the Nav component, you must have observed that we imported a css file. Open the App.css file and add this code here to it.

Add this piece of code to components/Display.js:

import React, { Component } from 'react';
import { Link } from 'react-router';
import Nav from './Nav';
import { isLoggedIn } from '../utils/AuthService';
import axios from 'axios';

class Display extends Component {

  render() {

    return (
      <div>
        <Nav />
        <h3 className="text-center"> Latest Videos on Miniflix </h3>
        <hr/>

        <div className="col-sm-12">

        </div>
      </div>
    );
  }
}

export default Display;

Add this piece of code to components/Upload.js:

import React, { Component } from 'react';
import { Link } from 'react-router';
import Nav from './Nav';

class Upload extends Component {


  render() {

    return (
      <div>
        <Nav />
        <h3 className="text-center">Upload Your 20-second Video in a Jiffy</h3>
        <hr/>

        <div className="col-sm-12">
          <div className="jumbotron text-center">
            <button className="btn btn-lg btn-info"> Upload Video</button>
          </div>
        </div>
      </div>
    );
  }
}

export default Upload;

Lastly, open up index.js and add replace it with the code here to set up your routes.

Now, when you run your app with npm start, you should have views like this:

Step 3. Upload Videos

We need a storage space for the videos our users will upload. Cloudinary is a cloud-based service that provides an end-to-end image and video management solution including uploads, storage, administration, manipulation and delivery. Head over to Cloudinary.com and create an account for free.

Let’s make use of Cloudinary’s Upload Widget. This widget allows you to upload videos or any type of file from your local computer, facebook, dropbox and Google Photos. Wow, very powerful. And the integration is seamless.

Go ahead and reference the cloudinary widget script in your index.html:

 <script src="//widget.cloudinary.com/global/all.js" type="text/javascript"></script>

Note: You can add it just after the links.

Now in Upload.js, modify the code to look like this:

import React, { Component } from 'react';
import { Link } from 'react-router';
import Nav from './Nav';

class Upload extends Component {

  uploadWidget = () => {
    window.cloudinary.openUploadWidget(
      { cloud_name: 'cloud_name',
        upload_preset: '<unsigned-preset>',
        tags: ['miniflix'],
        sources: ['local', 'url', 'google_photos', 'facebook', 'image_search']
      },
      function(error, result) {
          console.log("This is the result of the last upload", result);
      });
  }

  render() {
    return (
      <div>
        <Nav />
        <h3 className="text-center">Upload Your 20-second Video in a Jiffy</h3>
        <hr/>

        <div className="col-sm-12">
          <div className="jumbotron text-center">
            <button onClick={this.uploadWidget} className="btn btn-lg btn-info"> Upload Video</button>
          </div>
        </div>
      </div>
    );
  }
}

export default Upload;

In the code above, we added a third argument, tags. Cloudinary provides this for automatic video tagging. Every video that is uploaded to this app will be automatically tagged, miniflix. In addition, you can provide as many tags as you want. This feature is very useful when you want to search for videos too!

In the uploadWidget function, we called the cloudinary.openUploadWidget function and attached it to the “Upload Video” button. When the user clicks the button, it opens the widget. Replace the cloudname & uploadpreset values with your credentials from Cloudinary dashboard.

Sign in to your app, head over to the upload videos route and try uploading a video.

Upload Widget

Uploading the video...

It uploads the video straight to Cloudinary and returns a response object about the recently uploaded video that contains so many parameters such as the unique **publicid, secureurl, url, originalfilename, thumbnailurl, createdat**_, duration and so many others.

Step 4. Display Videos

We need a dashboard to display all the videos uploaded for users to see at a glance. Here, we will make use of Cloudinary’s react component. Install it:

npm install cloudinary-react

Now, open up components/Display.js and modify the code to be this below:

import React, { Component } from 'react';
import { Link } from 'react-router';
import Nav from './Nav';
import { isLoggedIn } from '../utils/AuthService';
import { CloudinaryContext, Transformation, Video } from 'cloudinary-react';
import axios from 'axios';

class Display extends Component {

  state = { videos: [] };

  getVideos() {
    axios.get('https://res.cloudinary.com/unicodeveloper/video/list/miniflix.json')
          .then(res => {
            console.log(res.data.resources);
            this.setState({ videos: res.data.resources});
    });
  }

  componentDidMount() {
    this.getVideos();
  }

  render() {

    const { videos }  = this.state;

    return (
      <div>
        <Nav />
        <h3 className="text-center"> Latest Videos on Miniflix </h3>
        <hr/>

        <div className="col-sm-12">
          <CloudinaryContext cloudName="unicodeveloper">
            { videos.map((data, index) => (
                <div className="col-sm-4" key={index}>
                  <div className="embed-responsive embed-responsive-4by3">
                    <Video publicId={data.public_id} width="300" height="300" controls></Video>
                  </div>
                  <div> Created at {data.created_at} </div>

                </div>
              ))
            }
          </CloudinaryContext>
        </div>
      </div>
    );
  }
}

export default Display;

In the getVideos code above, we take advantage of a very slick Cloudinary trick that helps grab all videos with a particular tag, when using just one tag. Check it out again:

https://res.cloudinary.com/unicodeveloper/video/list/miniflix.json

So we if had a tag like vimeo, our url will end up with .../vimeo.json. So in the code below, we got all the videos and stored in the videos state.

axios.get('https://res.cloudinary.com/unicodeveloper/video/list/miniflix.json')
          .then(res => {
            console.log(res.data.resources);
            this.setState({ videos: res.data.resources});
    });

The Cloudinary React SDK has 4 major components, Image, Video, Transformation and CloudinaryContext. We are interested in the Video and CloudinaryContext for now. Christian explained how these components work here.

In the render method, we simply just looped through the videos state and passed the publicid of each video into the Cloudinary Video component. The Video component does the job of resolving the publicid from Cloudinary, getting the video url, and displaying it using HTML5 video on the webpage. An added advantage is this: Cloudinary automatically determines the best video type for your browser. Furthermore, it allows the user have the best experience possible by choosing the best from the range of available video types and resolutions.

Run your app, and try to see the list of all videos. It should be similar to this:

You can also manipulate your videos on the fly, with the help of Cloudinary via the Transformation component.

Step 5. Share on Twitter

Go ahead install the react twitter widget component:

npm install react-twitter-widgets

In the components/Display.js file, import the component at the top:

import { Share } from 'react-twitter-widgets'

Now, add this piece of code just after the div that shows the time the video was created.



<Share url={`https://res.cloudinary.com/unicodeveloper/video/upload/${data.public_id}.mp4`} />

Check your app again. It should be similar to this:

Now, try to tweet.

Simple! It’s really not that hard. The source code for this tutorial is on GitHub.

Conclusion

Our MVP is ready. Our entrepreneur. Now sit back, relax and watch your account become flooded with investor money! Wait a minute, there is a 90% probability that you’ll called to add more features to this app. Well, I think Cloudinary can still help you with more features such as:

  • Automatic Subtitles and translation
  • Video briefs - short video, based on few gif images that will extract from the uploaded video.
  • Automatic and/or manual video markers - marking specific locations in the video so the user can wait patiently to watch them, or jump directly to these points
  • Find Similar videos by automatic video tagging

Cloudinary provides many options for uploading, transforming and optimizing your videos. Feel free to dive in and explore them.


This article was originally posted on Scotch.io


Prosper Otemuyiwa Prosper Otemuyiwa is a Food Ninja, Open Source Advocate & Self-acclaimed Developer Evangelist.

Build the Back-End For Your Own Instagram-style App with Cloudinary

$
0
0

Github Repo


Managing media files (processing, storage and manipulation) is one of the biggest challenges we encounter as practical developers. These challenges include:

  • Media uploads
  • Resource management (leads to heavy costing)
  • Media storage
  • Media manipulation
  • Poor delivery
  • Administration

A great service called Cloudinary can help us overcome many of these challenges. Together with Cloudinary, let's work on solutions to these challenges and hopefully have a simpler mental model towards media management.

The process to building this solution is a hands on example which will get our hands dirty but a practical approach. Instagram and Flickr are known for heavy image processing so we can take inspiration from these apps and build something similar.

What We'll Build

The image above shows what we are going to be building. We are going to build the app using Node.js, Express (for routing) and EJS (for templating).

The big question is, how do we intend to solve the problems we listed earlier while building this app?

Meet Cloudinary

Meet Cloudinary, if you haven't!

"Cloudinary is the media back-end for web and mobile developers. An end-to-end solution for all your image and video needs."

Cloudinary is a powerful tool for managing not just images but also videos. The interesting thing about this tool is that it abstracts a lot (actually all) of the challenges we encounter when managing images and other media files, including the ones we listed above.

We are not just going to discuss how Cloudinary helps us figure these puzzles, rather, we will use Cloudinary in building the above app which will expose all the solutions to these puzzles.

Cloudinary gives us the power to:

  • Handle image uploads effectively using a customizable widget
  • Optimize images for web and mobile consumption using transformations
  • Perform administrative operations including: renaming, deleting, etc
  • Store and backup images/videos
  • Manipulate media files
  • Deliver media files

Project Setup, Directory Structure and Dependencies

First things, first. Let's setup a project environment for our app to live in. The GitHub URL provided in the beginning and end of this tutorial is multi-branched. Each branch is prefixed with step-{step-number}- where "step-number" is the an increment for each step.

The first step is step-0-project-structure so you can switch to that branch and follow along from there. The master branch contains the final solution.

To have an overview of the project, it is always a good idea to present a directory structure which we can then build upon:

|---app
|------controller.js // Route handlers
|------model.js // Mongoose model
|---public // Public contents (style, js, imgs)
|---views // EJS views
|-----admin
|-------index.ejs // Admin home page
|-----pages
|-------index.ejs // Home page
|-------new.ejs // New post page
|-------edit.ejs // Edit post page
|-------single.ejs // Preview post page
|-----partials
|-------header.ejs // Header partial
|-------head.ejs // Styles partial
|-------scripts.ejs // Scripts partial
|---package.json
|---routes.js // Routes file
|---server.js // Entry

Something minimal and less overwhelming so we can focus on discussing on the features we are implementing rather than spend time moving codes around.

Update package.json dependencies with the third-party libraries that we will be working with:

"dependencies": {
    "body-parser": "^1.15.2",
    "cloudinary": "^1.4.2",
    "connect-multiparty": "^2.0.0",
    "ejs": "^2.5.2",
    "express": "^4.14.0",
    "mongoose": "^4.6.0"
  }

You can install the dependencies by running:

npm install

We are focusing on backend in this tutorial but that doesn't mean we can't afford a good looking design for the frontend. Rather than waste time crafting that, we can use Semantic UI by updating head.ejs and scripts.ejs:

<!-- ./views/partials/head.ejs -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css">
<!-- ./views/partials/scripts.ejs -->
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js"></script>

API Access with SDKs

You get redirected to your Cloudinary dashboard once you create an account by signing up for free. This is what the dashboard looks like:

The dashboard shows a list of SDKs that you can use to talk to Cloudinary in most of the popular languages including Node.js.

Cloudinary core exposes APIs based on your cloud name and all these SDKs do is serve as a language wrapper to these URL. So instead of littering your app with these URLs, you have a better intuitive language based method APIs to work with.

The cloud name is not your name but the name you chose when signing up as cloud name:

Installing the Node.js Cloudinary SDK

We are interested in the Node.js SDK so let's install it in our existing project:

npm install cloudinary --save

Handling Image Uploads

Getting your images to the server/cloud is the first and most important stage in managing images in your project. In this section, we are will cover

  • how to upload files from the web in your browser/mobile
  • upload using Cloudinary's upload widget
  • transform images
  • display images
  • and more...

Custom Image Upload

Let's see how we can upload images to the cloud using the SDK we have installed. Image upload will always require some form of input to grab the image data, so let's create a page that:

<!-- ./views/pages/new.ejs -->
<html lang="en">
<head>
    <!-- Head partial -->
    <% include ../partials/head %>
</head>
<body class="container">

<header>
    <!-- Header partial -->
    <% include ../partials/header %>
</header>

<main>
    <div class="ui grid container">
        <div class="ui two column centered grid">
            <div class="column">
                <!-- Enctype is multipart to support file upload -->
                <form action="/create" method="post" enctype="multipart/form-data" class="ui form">
                    <div class="field">
                        <label>Title</label>
                        <input name="title" type="text" placeholder="Title" />
                    </div>
                    <div class="field">
                        <label>Description</label>
                        <textarea rows="4" name="description" placeholder="Description"></textarea>
                    </div>
                    <div class="field">
                        <label>Image</label>
                        <input name="image" type="file" />
                    </div>
                    <button class="ui primary button" type="submit">Post</button>
                </form>
            </div>
        </div>
    </div>
</main>

<!-- Scripts partial -->
<% include ../partials/scripts %>
</body>
</html>

The form is not just a regular form. As you can see, the enctype property is set to multipart/form-data so as to properly support and handle file uploads via the form.

You can see how we are injecting our template partials into the HTML document. We have already seen the head and scripts partials so what is left is the header partial. The partial just holds the nav bar:

<div class="ui secondary pointing menu">
    <a class="item" href="/">
        Home
    </a>
    <a class="item" href="/new">
        New Post
    </a>
    <a class="item">
        About Scotchgram
    </a>
</div>

We have our markup all set, but we can't serve the markup yet because there is route handling that. Let's create a route and controller action method to do so:

// ./routes.js
var controller = require('./app/controller');

module.exports = function (app) {
    app.get('/new', controller.new);
};

We are pointing to a none existing controller and action method. This action method contains logic that renders the ejs so we can create that now:

// ./app/controller.js

module.exports = {
  new: function (req, res) {
      res.render('pages/new');
  }
};

With that, we can run the app and see our form at /new:

Of course our customers can not consume a form that is just staring back at them. They would want to fill it out and click the Post button. When the click what happens?

On submission, the the form data is collected and sent to /create endpoint which at the moment we are yet to create the route, so let's start doing something about that:

// ./routes.js
var multipart = require('connect-multiparty');
var multipartMiddleware = multipart();
var controller = require('./app/controller');

module.exports = function (app) {
    app.get('/new', controller.new);
    // Use middleware to handle uploaded files and access
    // uploaded files using req.file
    app.post('/create', multipartMiddleware, controller.create);
};

We have not just added a new route but also configured a middleware to help us process and get details about an uploaded file. With the multipartMiddleware, we could access any uploaded file from req.file.

So we have a route, but routes point need logics to handle incoming requests. The controller's create action method is where this logic will live:

// ./app/controller.js
// Dependencies
var cloudinary = require('cloudinary');
// Mongoose Model
var Model = require('./model');

// Configure Cloudinary
// with credentials available on
// your Cloudinary account dashboard
cloudinary.config({
    cloud_name: 'CLOUD_NAME',
    api_key: 'API_KEY',
    api_secret: 'SECRET'
});

module.exports = {
  new: function (req, res) {
      res.render('pages/new');
  },
  create: function (req, res) {
      // Use Cloudinary uploader to upload to cloudinary sever
      // Access files uploaded from the browser using req.files
      cloudinary.uploader.upload(req.files.image.path, function(result) {
          // Create a post model
          // by assembling all data as object
          // and passing to Model instance
          var post = new Model({
              title: req.body.title,
              description: req.body.description,
              created_at: new Date(),
              // Store the URL in a DB for future use
              image: result.url
              image_id: result.public_id
          });
          // Persist by saving
          post.save(function (err) {
              if(err){
                  res.send(err)
              }
              // Redirect
              res.redirect('/');
          });
      });
  }
};

First we required cloudinary and our mongoose model (which we will create soon), then we configured Cloudinary using credentials available on the dashboard. Once the route is hit, we upload the file to Cloudinary server using the SDK's uploader.upload API and persist the post body including the URL returned from Cloudinary to the MongoDB using Mongoose.

You can learn how to work with Mongo DB and Mongoose here."

Once all that is successful, we return to the homepage, else, we send back an error to the browser about the failure.

Let's add the model to complete the flow:

// app/model.js
// Dependencies
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

// create a schema
var postSchema = new Schema({
    title: String,
    description: String,
    image: String,
    image_id: String,
    created_at: Date
});

// the schema is useless so far
// we need to create a model using it
var Post = mongoose.model('Post', postSchema);

// make this available to our users in our Node applications
module.exports = Post;

To confirm that everything is working fine, I have installed Robomongo, a Mongo DB visualization tool. With Robomongo and can confirm that all my fields were persisted:

RoboMongo

Widget Image Upload (Even Easier Uploads)

We can choose to make life easier for us by using the widget provided by Cloudinary. I left the this for later so you can appreciate the feature after going through the long process of the custom upload.

To use Cloudinary's widget, include it in your script:

<!-- ./views/partials/scripts.ejs -->
<script src="//widget.cloudinary.com/global/all.js" type="text/javascript"></script>

With the widget script loaded, we can setup a handler for for that:

<!-- ./views/partials/scripts.ejs -->
<script src="//widget.cloudinary.com/global/all.js" type="text/javascript"></script>
<script>
    document.getElementById("upload_widget_opener").addEventListener("click", function() {

        cloudinary.openUploadWidget({ cloud_name: 'CLOUD_NAME', upload_preset: 'UPLAOD_PRESET'},
                function(error, result) {
                    console.log(error, result)
                    // Push URL into text input
                    document.getElementById('url_text').value = result[0].url;
                });

    }, false);
</script>

We attached a click event listener to button on the form (yet to be created). When this event occurs, we open the upload widget by calling openUploadWidget and passing in our cloud name and and upload preset.

The upload preset is just a pre-configuration to what we could have been setting up via parameters in the REST URLs. We set this configuration and using a unique ID to differentiate them from each other. To set yours, go to Settings >> Upload Tab >> Upload Presets >> Enable:

Enabling Presets

The callback function for the upload gives us the result which we can play around with. What I have done is push the result into our text input so it can be sent to the server.

Then, on the server, we can persist the URL to our database:

// ./app/controller.js
// Truncated
create: function (req, res) {
      var post = new Model({
          title: req.body.title,
          description: req.body.description,
          created_at: new Date(),
          // Now we are requesting the image
          // from a form text input
          image: req.body.image
      });

      post.save(function (err) {
          if(err){
              res.send(err)
          }
          res.redirect('/');
      });
  }

The form can now be updated to include the upload button and substitute the file input with a text input that stores the URL temporarily:

NOTE: A more real life approach to this is using a hidden input

<!-- views/pages/new.ejs -->
<!-- Truncated -->
 <form action="/create" method="post" enctype="multipart/form-data" class="ui form">
                    <div class="field">
                        <label>Title</label>
                        <input name="title" type="text" placeholder="Title" />
                    </div>
                    <div class="field">
                        <label>Description</label>
                        <textarea rows="4" name="description" placeholder="Description"></textarea>
                    </div>
                    <div class="field">
                        <label>Image</label>
                        <input name="image" type="text" placeholder="Image URL" id="url_text"/>
                    </div>
                    <button class="ui button" type="button" id="upload_widget_opener">Upload with Widget</button>
                    <button class="ui primary button" type="submit">Post</button>
                </form>

Progress and Preview

Most times, it's a good UX practice to show progress of image upload or preview of images being uploaded. With Cloudinary's jQuery plugin, we can get going with this in few minutes.

First thing to do as usual is load the required scripts/dependencies:

<!-- ./views/partials/scripts.ejs -->
<!-- Truncated for brevity -->

<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js"></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/blueimp-file-upload/9.12.5/js/jquery.iframe-transport.js' type='text/javascript'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/blueimp-file-upload/9.12.5/js/jquery.fileupload.js' type='text/javascript'></script>
<script src='https://cdn.jsdelivr.net/jquery.cloudinary/1.0.18/jquery.cloudinary.min.js' type='text/javascript'></script>

The above is an arsenal of tools that will help us accomplish previewing images and showing a progress bar.

Update the view to provide accommodation for the preview thumbnails and progress bar (with semantic):

<!-- views/pages/new.ejs -->
<!-- Truncated for brevity -->
<div class="field">
  <label>Image</label>
    <input name="file" type="file" class="upload_field"/>
</div>

<!-- Image thumbnails will be loaded here -->
<div class="thumbnails"></div>

<div class="ui teal progress" class="progress">
    <div class="bar"></div>
</div>

We can now update our script logic to support preview and progress:

// Configure Cloudinary
$.cloudinary.config({ cloud_name: 'CLOUD_NAME', api_key: 'KEY'})
// Perform unsigned upload
$('.upload_field').unsigned_cloudinary_upload("UPLOAD_PRESET",
            { cloud_name: 'CLOUD_NAME',},
            { multiple: true }
    )
    .bind('cloudinarydone', function(e, data) {
        // Populate thumbnails when upload is finished
        $('.thumbnails').append($.cloudinary.image(data.result.public_id,
                { format: 'jpg', width: 150, height: 100,
                    crop: 'thumb', gravity: 'face', effect: 'saturation:50' } ))
                    })
       .bind('cloudinaryprogress', function(e, data) {
        // Update progress bar with upload progress
        $('.progress').progress({
            percent: Math.round((data.loaded * 100.0) / data.total)
        });

The idea is that the Cloudinary jQuery plugin provides special custom events which let's us hook into each stage of the upload process and do what ever pleases us.

The cloudinarydone event is called once the upload is complete, giving us the power to grab uploaded data and append to the view.

The cloudinaryprogress is called during the upload intervals making it easier for us to build a progress bar around the upload process.

We can as well send the data returned on cloudinarydone back to the server if we wish to persist the URL as we have been doing in previous steps.

Incoming Transformation

Transformations in Cloudinary are like database rules or Express middleware. This is because, they can interfere upload, to manipulate uploaded content before sending to the cloud.

Assuming we want a ration of 2:1 applied to our images with 1000 x 500 dimension before they are uploaded, we can apply this rule (transformation) in our upload logic:

// ./app/controller.js
// Truncated
create: function (req, res) {
     cloudinary.v2.uploader.upload(req.files.image.path,
          //Apply transformation
          { width: 1000, height: 500, crop: "limit" },
          function(err, result) {
           // Handler here
      });
  }

We are using v2 to support transformation.

In a real project that handles a lot of this kind of request, it could be a good idea to use queues/jobs (whichever your environment supports) to abstract this transaction and push it to a later time if the image being transformed is not going to be used instantly.

One thing to keep in mind with this kind of transformation is that it does not save the original image which means that it transforms the image and then stores. To persist the original image, see the next section

Eager Transformation

This kind of transformation unlike the one we saw previously will store both the original image and the transformed. The transformed image can then be accessed with a different endpoint:

// ./app/controller.js
// Truncated
create: function (req, res) {
     cloudinary.v2.uploader.upload(req.files.image.path,
          //Apply transformation
          { eager: [
            { width: 2000, height: 1000, crop: "pad" }, 
            { width: 750, height: 300, crop: "crop", gravity: "north"} ]}, 
          function(err, result) {
           // Handler here
      });
  }

Upload Preset

Most times we pass in a lot of configuration to while trying to upload images especially transformation based configuration. If find yourself in a situation where you have to pass in the same configuration in more than one situations, then upload preset is for you.

With upload preset, you can create configuration from your dashboard that can be re-used at different point in your application. The presets are assigned a unique ID and then you can tell the SDK which preset it should apply by passing it that ID.

To configure a preset, first enable it via Settings >> Upload Tap >> Upload Presets >> Enable and then you can start adding new presets based on your taste. You will be provided with an already existing preset for default purposes but you can add more as you wish.

When you have a preset configured and you have grabbed the ID, you can use it in your app:

// ./app/controller.js
// Truncated
create: function (req, res) {
     cloudinary.v2.uploader.upload(req.files.image.path,
          //Apply Upload preset
          { upload_preset: "PRESET_ID" }, 
          function(err, result) {
           // Handler here
      });
  }

To start performing admin tasks, let us first of all create a list of cards on the home page to show all our images.

We have no route for / yet and that is what we need right now. So let's add that to our existing routes configuration:

// ./routes.js
// Truncated for brevity
app.get('/', controller.index);

Then we can go ahead to create an index action method in our controller:

// ./app/controller.js
// Truncated for brevity
index: function (req, res) {
      Model.find({}, function (err, posts) {
          if(err) res.send(err);

          res.render('pages/index', {posts: posts});
      });
  }
  //...

With the controller rendering a view with the post data, we need to create this view and present the data on the view:

<!-- views/pages/about.ejs -->

<html lang="en">
<head>
    <% include ../partials/head %>
</head>
<body class="container">

<header>
    <% include ../partials/header %>
</header>

<main>
    <div class="ui grid container">
        <div class="ui three column centered grid">
            <% posts.forEach(function(post, index) { %>

                    <div class="column">
                    <div class="ui card">
                        <div class="image">
                            <img src="<%= post.image %>" style="max-height: 150px">
                        </div>
                        <div class="content">
                            <a class="header"><%= post.title %></a>
                            <div class="meta">
                                <span class="date">
                                    <i class="calendar icon"></i> <%= post.created_at.getFullYear() %>
                                    <br>
                                    <i class="image icon"></i> <%= post.image_id %>
                                </span>
                            </div>
                            <div class="description">
                                <%= post.description %>
                            </div>
                        </div>
                       <div class="extra content">
                            <form action="/destroy" method="post" style="display: inline" id="destroy_form<%= index %>">
                                <input type="hidden" name="image_id" value="<%= post.image_id %>">
                                <a onclick="document.getElementById('destroy_form<%= index %>').submit(); return false;">
                                    <i class="remove icon"></i> Remove
                                </a>
                            </form>
                            <a href="/edit/<%= post.image_id %>">
                                <i class="edit icon"></i> Update
                            </a>
                        </div>
                    </div>
                </div>

            <% }); %>
        </div>
    </div>

<% include ../partials/scripts %>
</body>
</html>

Image of Fleshed Homepage

It's important to note that the remove link is submitting a parent form which just send the ID of the image we want to remove to the server while the update link takes us to an edit page (yet to be created).

Deleting Images

To delete images, we have to first delete from cloudinary server, wait for a response, and if successful remove from our database. The API for removing from the cloud using the SDK is destroy:

As usual, we first create a route:

// ./routes.js
// Truncated for brevity
app.post('/destroy', controller.destroy);

Then we create the action method in our controller:

// ./app/controller.js
// Truncated for brevity
destroy: function (req, res) {
      var imageId = req.body.image_id;
      // The destroy method takes the image ID
      // which we need to remove
      cloudinary.v2.uploader.destroy(imageId, function (result) {
              // We also delete this
              // image details from our database
              Model.findOneAndRemove({ image_id: imageId }, function(err) {
                  if (err) res.send(err);

                  res.redirect('/');
              });
          });
  }

We first remove the image by calling the destroy method and passing it the ID of the image we want to remove. When that is completed, we also remove the image details from our database.

Renaming Images

When an image is uploaded, it is assigned a random generated image ID. If for some reason, this image ID matters to you, we can change it to a real name. While doing so, we can use the opportunity to also update the image details.

Let's make a route to render the edit form page which basically looks like that of new form but very few variation:

// ./routes.js
// Truncated for brevity
app.post('/edit', controller.edit);
// Handle submitted updates
app.post('/update', controller.update);

We used the opportunity to add an extra route which will handle the update request from the edit form.

Next we create the controller actions for the above routes, edit and update:

// ./app/controller.js
// Truncated for brevity
  /***
  * Edit action method
  ***/
edit: function (req, res) {
      Model.find({image_id: req.params.id}, function (err, posts) {
          if(err) res.send(err);
            // Render edit form
            //with existing post
          res.render('pages/edit', {post: posts[0]});
      });
  },
  /***
  * Update action method
  ***/
  update: function (req, res) {
      var oldName = req.body.old_id
      var newName = req.body.image_id;
      cloudinary.v2.uploader.rename(oldName, newName,
          function(error, result) {
              if (error) res.send(error);
              Model.findOneAndUpdate({image_id: oldName}, 
                  Object.assign({}, req.body, {image: result.url}), 
                  function (err) {
                  if (err) res.send(err);

                  res.redirect('/');
              })
          })

  },

We use Cloudinary's rename API method to update image name on the cloud. It takes the existing name (to find the image on the cloud), the new name and a callback as arguments.

See how we are using Object.assign to update the model with req.body while updating the image property with the latest URL. This is because, after renaming an image on the cloud, the URL also changes because an image ID is part of the little pieces that composes an image URL.

Now we can happily create the edit view and everything will just work:

<!-- views/pages/new.ejs -->

<html lang="en">
<head>
    <% include ../partials/head %>
</head>
<body class="container">

<header>
    <% include ../partials/header %>
    <h3 class="ui center aligned icon header">
        Edit: <%= post.title %>
    </h3>
</header>

<main>
    <div class="ui grid container">
        <div class="ui two column centered grid">
            <div class="column">
                <img class="ui medium centered image" src="<%= post.image %>">
                <form action="/update" method="post"  class="ui form">
                    <div class="field">
                        <label>Title</label>
                        <input name="title" type="text" placeholder="Title" value="<%= post.title %>"/>
                    </div>
                    <div class="field">
                        <label>Description</label>
                        <textarea rows="4" name="description" placeholder="Description"><%= post.description %></textarea>
                    </div>
                    <div class="field">
                        <label>Rename Image ID</label>
                        <input name="image_id" type="text" placeholder="image_id" value="<%= post.image_id %>"/>
                    </div>
                    <input type="hidden" value="<%= post.image %>" name="image">
                    <input type="hidden" value="<%= post.image_id %>" name="old_id">
                    <button class="ui primary button" type="submit">Post</button>
                </form>
            </div>
        </div>
    </div>
</main>

<% include ../partials/scripts %>
</body>
</html>

Edit Image

Tagging

Just like tags in a blog post, we can categorize images by assigning tags to the. Thereafter, the images can be pulled up based on the assigned tag(s). Tagging improves organization of images and we can perform specific actions to a set of images identified by a tag.

To tag images, we can either do that when uploading them or update the image at a later time with the tags. Let's play around by adding tags during upload.

Update the new form view to have an extra field for submitting tags:

<div class="field">
    <label>Tags</label>
    <div class="ui fluid multiple search selection dropdown" id="tag">
        <input name="tags" type="hidden">
        <i class="dropdown icon"></i>
        <div class="default text">Tags</div>
        <div class="menu">
            <div class="item" data-value="puppy">puppy</div>
            <div class="item" data-value="kitten">kitten</div>
        </div>
    </div>
</div>

We are using Semantic's dropdown multi-select widget and it can only work fine with a script:

 $('#tag').dropdown({
    allowAdditions: true
 });

Now to the real thing -- when the form is submitted, we would grab the input from tags input and pass it as an option (just like we did for transformation) to Cloudinary's upload API method in our controller's create action method:

cloudinary.v2.uploader.upload(req.files.image.path,
          {tags: req.body.tags },
//... truncated

At the end, the form will look like this:

Create Tag

When the image is uploaded, from our management console we can see the tags:

Tags in management console

Queries & Search

We are already doing a great job and I would like to show off some of resource browsing features we can get with Cloudinary.

It's a good thing we are storing data in a database but Cloudinary is generous enough to allow us to store additional information known as metadata. We can use Cloudinary's listing features to filter images and their metadata.

A possible use case in our application is listing only the images in our server for administrative use. At the moment, we are only listing the images created by clients via browser uploads which there metadata are persisted in Mongo.

We begin with an admin route which is just like every other ones we have seen:

// ./routes.js
/*
 * Admin Routes
 *
 * */
 app.get('/admin', controller.admin.index);

Thereafter, we can create the admin.index action method which just list all images from the cloud:

// ./app/controller.js
admin:{
        index: function (req, res) {
            var q = req.query.q;
            var callback = function(result){
                // This is value is used to
                // populate the search input box
                var searchValue = '';
                if(q){
                    searchValue = q;
                }
                res.render('admin/index', {posts: result.resources, searchValue: searchValue});
            };
            if(q){
                // Filter based on search input
                // if provided
                cloudinary.api.resources(callback,
                    { type: 'upload', prefix: q });
            } else {
                // If no search input, list all
                cloudinary.api.resources(callback);
            }
        }
    }

We also implemented a search functionality which is simple one. We use Cloudinary's resources API method to fetch all images, if a query parameter was passed in, we use the prefix option to search for it, else, we just spit all the images.

Our view just looks like what we had in pages/index but with different properties and a search box:

<!-- views/admin/index.ejs -->

<html lang="en">
<head>
    <% include ../partials/head %>
</head>
<body class="container">

<header>
    <% include ../partials/header %>
    <h3 class="ui center aligned icon header">
        Administrator
    </h3>
</header>

<main>
    <div class="ui grid container">
        <div class="ui two column centered grid">
            <div class="column">
                <form class="ui form">
                    <div class="field">
                        <input name="q" type="text" placeholder="Search"  value="<%= searchValue %>"/>
                    </div>
                </form>
            </div>
        </div>
    </div>
    <div class="ui grid container">
        <div class="ui three column centered grid">
            <% posts.forEach(function(post, index) { %>

            <div class="column">
                <div class="ui card">
                    <div class="image">
                        <img src="<%= post.url %>" style="max-height: 150px">
                    </div>
                    <div class="content">
                        <a class="header"><%= post.public_id %></a>
                        <div class="meta">
                                <span class="date">
                                    <i class="calendar icon"></i> <%= post.created_at %>
                                </span>
                        </div>
                        <div class="description">
                           Dimension: <%= post.width %> X <%= post.height %>
                        </div>
                    </div>
                    <div class="extra content">
                        <form action="/admin/destroy" method="post" style="display: inline" id="destroy_form<%= index %>">
                            <input type="hidden" name="image_id" value="<%= post.public_id %>">
                            <a onclick="document.getElementById('destroy_form<%= index %>').submit(); return false;">
                                <i class="remove icon"></i> Remove
                            </a>
                        </form>
                        <a href="/edit/<%= post.public_id %>">
                            <i class="edit icon"></i> Edit
                        </a>
                    </div>
                </div>
            </div>

            <% }); %>
        </div>
    </div>

    <% include ../partials/scripts %>
</body>
</html>

Search UI

Image Categorization

Remember when we added tags to images and we discussed that tags help us organize images? Image organization becomes possible because we are able to categorize them using tags.

For the sake of simplicity, we won't add this feature into our existing app but for your consumption, you can create a tag cloud by fetching the list of tags available in your cloud from Cloudinary:

cloudinary.api.tags(function(result){
    res.render('pages/index', {tags: result.resources})
});

With that, you can loop through all your tags and create a tag cloud with them. Cloudinary's tags API did the magic.

You can also filter your images based on a specified tags. For instance:

resources_by_tag takes a tag name as one of it's argument which it uses to filter your images and return the ones that has the same tag that was passed in.

Moderating Image Uploads

In public facing systems that content quality and kind matters a lot, moderation becomes very important. With moderation feature, you can approve or decline uploaded images if they do not reach a particular requirement.

An example of such requirement is when adding profile image at Upwork. During registration, the user adds his/her photo and waits for few days for an admin to consider if the image is a head-shot before approving.

Achieving moderation with Cloudinary is a breeze. You just switch the moderation option to manual when making uploads:

create: function (req, res) {
      cloudinary.v2.uploader.upload(req.files.image.path,
          { width: 300, height: 300, crop: "limit", tags: req.body.tags,
          // Turn on moderation
           moderation:'manual' },
          function(err, result) {
              console.log(result);
              //... Brevity sake
 //... Brevity sake

When you open your media library, you can now see that as an admin, there is a button to accept or decline this image.

Moderation

Backing up Images

Backing up your web resources and any data in general has always been a recommended practice since day one. Cloudinary is no exception. Backing up in Cloudinary just entails turning on a switch.

Backups as expected, takes extra space and for that reason, it is turned off by default so you can enable when you think you cloud deserves backups. Turning Automatic Backup ensures that images uploaded to your cloud are backed up.

To turn Automatic Backup on, got the Upload Settings from the management dashboard and change Automatic backup to Enabled.

Backing up

You can also explicitly specify that you want a given image to be backed up when it is being uploaded. To do so, set the backup option to true when making the upload as shown below:

cloudinary.uploader.upload(req.files.image.path, 
    // Backs up this particular image
    { backup: true },
    function(result) { 
        console.log(result);
     });

Manipulating Images

Let's see how we can combine everything we have seen including transformation to manipulate images while retrieving them from the cloud.

We have been using the .upload method to send images and now to retrieve them, we use .image or .url method. The difference between image and url is, the former composes a HTML image tag while the latter generate the image's URL.

Embedding Images to Web Pages

We have already started embedding images to our web pages by getting the URL we stored in our database. What if we had no means of persisting data to a database and all we could afford is the image cloud? How do we get the images?

The methods we saw above answers the question. It just takes in the image ID that we need to retrieve and an optional configuration object to manipulate the image.

To demonstrate how we can embed images, let's add another feature to our app which displays a single post.

Route first:

// ./routes
// Truncated
app.get('/:id', controller.find);

Then the controller's action method, find:

find: function (req, res) {
      var id = req.params.id;
      Model.findOne({image_id: id}, function (err, post) {
          if (err) res.send(err);

          res.render('pages/single', {post: post, image: cloudinary.image, image_url: cloudinary.url});
      })
  },

We use Mongoose's findOne to retrieve a single post with the image ID which is passed in as param. When rendering, we are not just passing the post down to the view but also extracting the image and url methods from Cloudinary, aliasing them and passing them to the view as well.

Have a look at what the view now loos like:

<!-- views/pages/single.ejs -->

<html lang="en">
<head>
    <% include ../partials/head %>
</head>
<body class="container">

<header>
    <% include ../partials/header %>
    <h3 class="ui center aligned icon header">
        <%= post.title %>
    </h3>
</header>

<main>
    <div class="ui grid container">
        <div class="ui three column centered grid">
            <div class="column">
                <!-- Use cloudinary.url to get image url -->
                <img class="ui medium centered image" src="<%= image_url(post.image_id) %>" style="border: 7px solid lightgrey">
                <br>
                <p><strong>Title: </strong> <%= post.title %></p>
                <p><strong>Description: </strong> <%= post.description %></p>
                <p><strong>Public ID: </strong> <%= post.image_id %></p>
            </div>
        </div>
    </div>

    <% include ../partials/scripts %>
</body>
</html>

The new thing in the snippet above is that instead of using post.image to get the image as returned from our database, we use cloudinary.url which we already aliased as image_url to retrieve the image.

Single post

Resizing & Cropping Images

Remember I mentioned we can transform images both when uploading or retrieving when we discussed image transformation. Let's start seeing how we can transform images while fetching them:

<img 
    class="ui medium centered image" 
    src="<%= image_url(post.image_id, {width: 200, height 100}) %>">

That will resize the image to 200 x 100 without considering the quality. We can also crop the image like so:

<img 
    class="ui medium centered image" 
    src="<%= image_url(post.image_id, {width: 200, height 100, crop: 'scale'}) %>">

The scale type of cropping will change the size of the image exactly to the given width and height without necessarily retaining the original aspect ratio. There are other cropping techniques and you can read more about them here.

Face Detection Cropping

With Cloudinary's face detection algorithm, we can crop an image based on where a face is positioned on the image.

Assuming we have an image of a child in a very wide background of about 2000 X 1200 dimension and we need to get this down to about 100 x 100 with the face in the middle of the dimension, we can do this:

<img 
    class="ui medium centered image" 
    src="<%= image_url(post.image_id, {width: 100, height 100, crop: 'thumb', gravity: 'face'}) %>">

The gravity property is used to target the face when cropping an image by specifying it's value to face.

Auto Cropping

Cloudinary just keeps getting amazing! We can use a technique known as automatic cropping to crop an image down to contain only the most important part and trimming down the redundant pieces. This is very much like face detection cropping but this time, we are not looking for a face but the important content:

<img 
    class="ui medium centered image" 
    src="<%= image_url(post.image_id, {width: 100, height 100, gravity: "auto", crop: "fill"}) %>">

By setting the gravity to auto instead of face as we saw previously, we are able to crop down the image in an intelligent manner leaving us with the most important portion of the image.

Automatic Format Selection & Quality

Images are universally recognized by there popular formats (JPEG, PNG, etc) but some native environment have there own supported formats which is a more optimized approach.

An example of such environment is Chrome (including the browser and other implementation like Electron or Node Webkit). Chrome supports a format type called WebP and it performs better than the universal formats.

When fetching images, you can set the fetch_format property to auto so it can render the images based on the native format rather than the universal. If no native format is supported, it defaults to universal.

<img 
    class="ui medium centered image" 
    src="<%= image_url(post.image_id, {width: 100, height 100, gravity: "auto", crop: "fill" fetch_format: "auto"}) %>">

Format selection is not the only automated process we can get from Cloudinary. Image quality is also achievable, automatically. By setting quality to auto, Cloudinary will analyze a given image to find the best quality compression level and optimal encoding settings based on the image content and the viewing browser, in order to produce an image with good visual quality while minimizing the file size:

<img 
    class="ui medium centered image" 
    src="<%= image_url(post.image_id, {width: 100, height 100, gravity: "auto", crop: "fill" quality: "auto"}) %>">

Image Shapes, Styles and Filters

What I have been doing is applying styles explicitly using CSS. Cloudinary is always trying to make our jobs and lives easy which us why we can change the shapes and styles of these images using the API.

Let's see how we can change the shape of our images in the single post view to a rounded images and also add the grey borders using Cloudinary API:

<img 
    class="ui medium centered 
        image" src="<%= image_url(post.image_id, 
                {
                    width: 200, 
                    height: 200, 
                    radius: 100, 
                    crop: "fill", 
                    border: "10px_solid_grey"
                }
            ) %>" />

The radius is responsible for setting the image shape to circle and border adds the image borders. The border property is just like what we get with CSS but with the spaces replaced with an underscore (_).

Shapes and Styles

There are lots more under image and shapes and you can visit Cloudinary to explore your options

Just like adding the styles and updating the shape, we can add fun filters to the image. My favourite is changing the image color to greyscale:

<img 
    class="ui medium centered 
        image" src="<%= image_url(post.image_id, 
                {
                    width: 200, 
                    height: 200, 
                    radius: 100, 
                    crop: "fill", 
                    border: "10px_solid_grey",
                    effect: "grayscale"
                }
            ) %>" />

Setting the effect property to greyscale gives us a black and white kinda thing:

Greyscale Image

Feel free to explore other options including: hue, red, blue, green, negate, brightness, brightness_hsb, colorize, etc.

Making Images Responsive

If you are still building fixed width websites, then you may be out of touch with current trends. Fluid design and responsive content should be a primary focus for every developer because the web has gone from solely desktop systems to include mobile devices, such as smartphones and tablets of all sizes.

It's difficult to build sites that adapt to a variety of device sizes. By default, text reflows to automatically fit the design, but other contents, particularly images, do not.

Cloudinary provides a handful of options when it comes to making images responsive. Let's explore few of these options:

Automatic Responsive Images: This is achieved using Client Hints technology which allows web browsers to inform servers (or CDN layers) with the required dimensions and pixel densities of each specific image download request.

With that kind of information, the server can then send a suitable image dimension for every giving device width.

Chrome, Android and Opera are the browsers that support Client Hint and hints can be provided in the markup document via a meta tag:

<meta http-equiv="Accept-CH" content="DPR, Viewport-Width, Width">

It is identified by the http-equiv which the value is set to Accept-CH (Accept Client Hint). The relevant hints are the DPR (Device Pixel Ratio) value, the Width available for the specific image in the responsive layout, and the Viewport-Width of the browser's window.

Now that hints are being sent to the cloud, we can make request for our images and Cloudinary will be smart enough to send us the perfect image for our device width:

<img 
    class="ui medium centered
        image" src="<%= image_url(post.image_id, 
                {
                    client_hints: true, 
                    transformation: [
                      {
                          aspect_ratio: "16:9", 
                          crop: "fill"
                      },
                      {
                          width: "auto", 
                          dpr: "auto", 
                          crop: "scale"
                        }
                  ]}
            ) %>" />

JavaScript Based Detection: Another option which works better across browsers is using JavaScript to detect the viewport of a given client and then serve an image based on this information.

Cloudinary's JavaScript library already implemented this approach internally so we do not have to write any complex logic to get going. We already have the library included in our demo project.

We use data-src rather than src to request for the images. This enables Cloudinary to serve these images dynamically. You can set the src attribute to a placeholder image.

<img 
    data-src="<%= image_url(post.image_id) %>"
    src="http://placehold.it/200x300" />

Next, we call the JavaScript method to tell Cloudinary that we need to make use of the JavaScript-based detection:

<script type="text/javascript">$.cloudinary.responsive()</script>

Remote Fetch

All the goodies we have seen in Cloudinary does not only apply to images that are stored in Cloudinary cloud. We can apply it to any other remote image making it possible for us to just use the Cloudinary SDK even when our content is not hosted by Cloudinary:

<img 
    class="ui medium centered 
        image" src="<%= image_url('http://i.imgur.com/67iZh9H.jpg', 
                {
                    type: 'fetch'
                    width: 'auto', 
                    radius: 100, 
                    crop: "fill", 
                    border: "10px_solid_grey",
                    effect: "grayscale"
                }
            ) %>" />

By specifying the type option as fetch, we can pass in a URL rather than a Cloudinary image ID.

Conclusion

There are some other solutions out there but from my experience and that of over 140k+ happy Cloudinary users, satisfaction is guaranteed. This article will not just serve as a tutorial but a reference to run back to when building your awesome apps with Cloudinary.

Cloudinary documentations, blog posts and GitHub repos are also very helpful and most portion of this tutorial where gotten from them.

This article was originally posted on Scotch.io

Christian Nwamba Christian Nwamba (CodeBeast), is a JavaScript Preacher, Community Builder and Developer Evangelist. In his next life, Chris hopes to remain a computer programmer.

Embed Images in Email Campaigns at Scale

$
0
0

tl;dr

Cloudinary is a powerful image hosting solution for email marketing campaigns of any size. With features such as advanced image optimization and on-the-fly image transformation, backed by a global CDN, Cloudinary provides the base for a seamless user experience in your email campaigns leading to increased conversion and performance.


We live in an era of information overload and attention is the real currency. Marketers are constantly looking for new ways to reach you, to advertise products and services that they think could improve your lives.

SnapChat and Instagram Stories are the newest channels marketers are trying to leverage. But there’s one tried-and-true channel that has proven its dominance for reaching your audience… and it’s none other than email marketing.

There are times when Marketing and IT must work together to implement multiple email marketing campaigns under a predefined budget.

Here’s the catch - our email database is really big. Large enough that any traditional email marketing software, such as Aweber or MailChimp, would cost at least five times more than what we had budgeted.

Clearly, we needed a more feasible solution.

If you are in a similar situation, your options include:

  • Engaging with a marketing automation system such as Marketo and HubSpot
  • Sending emails from your company’s email account

Let’s say you’ve implemented an email delivery solution. The next thing you need to figure out is where to host the images for the HTML used in your email.

Since we know that attention is the real currency, emails must:

  1. Be well-designed
  2. Carry a visual impact
  3. Look seamless across all devices - mobile, tablet and desktop
  4. Load fast

Here’s the deal - points one and two depend on your team’s creativity, and is subjective in nature.

What we can control are points three and four. And that’s where Cloudinary comes in.

Cloudinary is end-to-end, cloud-based solution that enables you to optimize, manipulate and transform images on-the-fly. It is designed for almost any service (product, website or app) that requires reliable image delivery.

Too complex? Think of Cloudinary as the perfect email image hosting solution.

I’ve personally used Cloudinary for my email marketing campaigns, and wanted to share how it could help you with yours.

While I’m not going to dive into all the features Cloudinary has to offer, I will discuss the features that:

  • Help improve user experience
  • Reduce resource consumption
  • Do a combination of both

Let’s take a look at these features, shall we?

On-the-Fly Image Optimization

We all need high-quality images for email marketing that load fast. One of the very basic, yet fundamentally important, features Cloudinary offers is on-the-fly image manipulation capabilities.

For starters, every image you upload is optimized and stored in Cloudinary’s servers. Only the optimized version of the image (which, by the way has negligible loss in quality) is served to the end user. This approach saves bandwidth costs for both you and the end user. Plus, the email loads faster on the user’s device (mobile or desktop), resulting in a better user experience.

On-Demand Image Resizing and Manipulation

Mobile screens are smaller, and therefore can benefit from use of smaller images. You don’t need a 1080p image for a email that’s primarily going to be viewed on a phone.

Whatever the device, Cloudinary enables me to configure the image resolution by defining the single parameter in the request URL. I don’t need to upload two (or more) versions of the image to Cloudinary. By changing just one parameter in the URL, I can quickly test which resolution works best. This is incredibly useful in A/B testing and designing emails.

For example, I use this simple command to resize the image:

Ruby:
cl_image_tag("sample.jpg", :transformation=>[
  {:width=>300, :crop=>"scale"},
  {:opacity=>50}
  ])
PHP:
cl_image_tag("sample.jpg", array("transformation"=>array(
  array("width"=>300, "crop"=>"scale"),
  array("opacity"=>50)
  )))
Python:
CloudinaryImage("sample.jpg").image(transformation=[
  {"width": 300, "crop": "scale"},
  {"opacity": 50}
  ])
Node.js:
cloudinary.image("sample.jpg", {transformation: [
  {width: 300, crop: "scale"},
  {opacity: 50}
  ]})
Java:
cloudinary.url().transformation(new Transformation()
  .width(300).crop("scale").chain()
  .opacity(50)).imageTag("sample.jpg")
JS:
cl.imageTag('sample.jpg', {transformation: [
  {width: 300, crop: "scale"},
  {opacity: 50}
  ]}).toHtml();
jQuery:
$.cloudinary.image("sample.jpg", {transformation: [
  {width: 300, crop: "scale"},
  {opacity: 50}
  ]})
React:
<Image publicId="sample.jpg" >
  <Transformation width="300" crop="scale" />
  <Transformation opacity="50" />
</Image>
Angular:
<cl-image public-id="sample.jpg" >
  <cl-transformation width="300" crop="scale">
  </cl-transformation>
  <cl-transformation opacity="50">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation()
  .Width(300).Crop("scale").Chain()
  .Opacity(50)).BuildImageTag("sample.jpg")
Android:
MediaManager.get().url().transformation(new Transformation()
  .width(300).crop("scale").chain()
  .opacity(50)).generate("sample.jpg")

The w_300 tells the Cloudinary API to resize the image to a width of 300px. The height is adjusted automatically, thus maintaining the aspect ratio of the image.

There are tons of other image manipulation features mentioned in Cloudinary’s documentation.

Changing the Image, But Keeping the Same URL

This feature is a lifesaver when it comes to email marketing. Imagine spending hours crafting the perfect email, designing it over the course of a week. You embed the image in the email’s HTML file. Everything looks good.

On the day the campaign kicks off, you’re a bit nervous and after several checks, you press the send (or schedule) button. You grab a coffee and moments later you realize that the header image has a unexcusable, hair-raising typo. Think “End of Season Sale. Grab yourself a umbrella.”

If you’ve scheduled the campaign, there’s still hope. You can pause and fix the error. But what if your campaign is already being sent?

Here’s what I did:

  • Quickly fix the image.
  • Head over to Cloudinary’s dashboard and update the image.
  • Continue enjoying the coffee.

Winning the Battle Against Broken CSS Support

Here’s the deal - CSS support is shaky in most email clients. Some work, some don’t. There just isn’t seamless support for CSS in them.

When experimenting with Cloudinary, I found two particularly useful, on-the-fly image transformation features.

Opacity Transformation

The first feature is opacity transformation. In simple terms, this feature enables you to change the opacity of the image, on-the-fly.

If you’re using a background image in your email, opacity plays a major role in the contrast.

You might wonder - which email client does not support CSS opacity? You’d be surprised.

Note: MailChimp has this handy resource on CSS compatibility in various email clients.

The following email clients do not support CSS opacity:

  1. Gmail and Yahoo! web clients
  2. Microsoft Outlook 2000 - 2013 (desktop versions for Windows)
  3. Windows Live Mail (for those still on Windows 7)
  4. Gmail app for Android and iOS phones

The only desktop clients that do support CSS opacity are Apple Mail and Outlook 2013 for Mac.

This is where Cloudinary comes in.

In Cloudinary, you can easily manipulate the opacity of the image by adding the o_XX parameter. XX represents the digits of the opacity percentage.

For example, to display an image at 50 percent opacity, we add the parameter o_50 in the following URL:

Ruby:
cl_image_tag("sample.jpg", :opacity=>50)
PHP:
cl_image_tag("sample.jpg", array("opacity"=>50))
Python:
CloudinaryImage("sample.jpg").image(opacity=50)
Node.js:
cloudinary.image("sample.jpg", {opacity: 50})
Java:
cloudinary.url().transformation(new Transformation().opacity(50)).imageTag("sample.jpg")
JS:
cl.imageTag('sample.jpg', {opacity: 50}).toHtml();
jQuery:
$.cloudinary.image("sample.jpg", {opacity: 50})
React:
<Image publicId="sample.jpg" >
  <Transformation opacity="50" />
</Image>
Angular:
<cl-image public-id="sample.jpg" >
  <cl-transformation opacity="50">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Opacity(50)).BuildImageTag("sample.jpg")
Android:
MediaManager.get().url().transformation(new Transformation().opacity(50)).generate("sample.jpg")
Example of an image at 50 percent opacity, transformed dynamically

Now you can forget about the compatibility issues and design the newsletter!

CSS Sprite Generation

You can combine multiple logos into a single sprite image by using Cloudinary’s sprite-generation capabilities. This makes image management easier and gives better performance.

The sprite generation feature is useful in the email footers, where you want to showcase your clientele or partners. It helps establish trust in the product you’re marketing and uplifts your brand’s image.

For example, let’s say you’re a cloud-aggregator startup, specializing in managed CMS hosting. In other words you help setup and manage your client’s websites in cloud hosting companies.

In this role, you support the top four cloud hosting companies - AWS, Microsoft Azure, Google Cloud and IBM Cloud. You upload their logos in Cloudinary with a common tag - let’s say “supported_cloud”.

With Cloudinary’s sprite generation feature, you could display a single image that combines all the images with the supported_cloud tag. The, with a few CSS commands, you can display the logos in various sections of the footer.

This improves user experience by reducing network overhead and bypassing download limitations.

On-the-Fly Image Manipulation

Image manipulation comes in handy when you need to add a watermark to your images. A classic example would be screenshots of your SaaS dashboard, used in a drip email campaign.

And the best part? I don’t have to add the watermark to the image myself. All I need to do is configure the image URL and Cloudinary serves the watermarked version of the image to the end user.

Advanced Reporting

Cloudinary provides an intuitive dashboard that gives you a bird’s-eye view of your image stats.

Screenshot of the Cloudinary Dashboard Report (Demo)

How many images were transformed on-the-fly? Storage quota consumed? Bandwidth used? These stats are presented in a beautiful graph. You can breakdown the report by month, or more granularly at a daily level. Interestingly I find that the bandwidth consumption correlates with the open-rate of my mailing lists.

Download API information in one-click from the Cloudinary dashboard

Note: Cloudinary displays your account details at the very top of the dashboard. Developers also can download the API access credentials in Python or YML in a single click.

Browser-based Image Format

This feature is, by far, one of my favorite things in Cloudinary. Browsers have evolved tremendously over the years. From being able to view simple text documents, to images and video. Today I am writing this article on Google Docs.

Each browser now supports a distinct type of image, that’s only applicable to, or supported by, that browser. For example, Google introduced the WebP image format back in 2010, which offers significant saving compared to JPEG. Only Google Chrome supports the WebP.

JPG Image
16.9KB JPG
WebP Automatic Image
6.9KB WebP

Similarly Microsoft announced JPEG-XR format in 2009, which offers better image compression than the original JPEG. It is supported in Internet Explorer 9 or above.

Here’s how Cloudinary creates the magic. With the Automatic Format Selection feature, Cloudinary automatically serves different formats of the image, based on the requesting browser. For example, it serves the WebP version of the image to Google Chrome users and JPEG-XR version of the image to Internet Explorer 9+ users. If the browser does not support these modern image formats, the default optimized JPEG is served.

Cloudinary does all of this on-the-fly, without us having to manually convert or upload multiple versions of the image. Simply add f_auto parameter to the requesting URL and the corresponding format would be served in the browser.

Ruby:
cl_image_tag("sample.jpg", :width=>300, :opacity=>50, :crop=>"scale")
PHP:
cl_image_tag("sample.jpg", array("width"=>300, "opacity"=>50, "crop"=>"scale"))
Python:
CloudinaryImage("sample.jpg").image(width=300, opacity=50, crop="scale")
Node.js:
cloudinary.image("sample.jpg", {width: 300, opacity: 50, crop: "scale"})
Java:
cloudinary.url().transformation(new Transformation().width(300).opacity(50).crop("scale")).imageTag("sample.jpg")
JS:
cl.imageTag('sample.jpg', {width: 300, opacity: 50, crop: "scale"}).toHtml();
jQuery:
$.cloudinary.image("sample.jpg", {width: 300, opacity: 50, crop: "scale"})
React:
<Image publicId="sample.jpg" >
  <Transformation width="300" opacity="50" crop="scale" />
</Image>
Angular:
<cl-image public-id="sample.jpg" >
  <cl-transformation width="300" opacity="50" crop="scale">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Width(300).Opacity(50).Crop("scale")).BuildImageTag("sample.jpg")
Android:
MediaManager.get().url().transformation(new Transformation().width(300).opacity(50).crop("scale")).generate("sample.jpg")
Using the Automatic Format Selection feature in Cloudinary, this same image would be served as WebP in Google Chrome or as JPEG in Firefox.

Browser-dependent image format rendering saves a lot of bandwidth when the email is viewed in a web browser, or when the HTML version of the email is viewed online.

Compress GIFs On-the-Fly

We started this article with the premise that attention is the most valuable currency in today’s world. When it comes to email marketing, getting someone to open the email depends on the quality of the subject line.

Our goal is to grab the user’s attention, right after he or she opens the email. That’s where an image comes in. A good header image can do wonders. What if we could take that one step further? What if we could crunch more relevant information into a single place, without requiring the user to scroll down?

That’s where videos come in. But unfortunately, videos are not supported in emails. That’s why you should use GIFs. You can keep the same information, and do away with the heavy size or the technical complexities of implementing the video.

Cloudinary supports animated GIFs, and with a few parameters, you can create winning emails!

Ruby:
cl_image_tag("kitten_fighting.gif")
PHP:
cl_image_tag("kitten_fighting.gif")
Python:
CloudinaryImage("kitten_fighting.gif").image()
Node.js:
cloudinary.image("kitten_fighting.gif")
Java:
cloudinary.url().imageTag("kitten_fighting.gif")
JS:
cl.imageTag('kitten_fighting.gif').toHtml();
jQuery:
$.cloudinary.image("kitten_fighting.gif")
React:
<Image publicId="kitten_fighting.gif" >

</Image>
Angular:
<cl-image public-id="kitten_fighting.gif" >

</cl-image>
.Net:
cloudinary.Api.UrlImgUp.BuildImageTag("kitten_fighting.gif")
Android:
MediaManager.get().url().generate("kitten_fighting.gif")
Animated GIF served by Cloudinary

You can apply almost all the transformation effects to the GIFs (plus a few special ones). In our example, let’s serve the browser optimized format of the GIF.

Consider the following URL:

Ruby:
cl_image_tag("kitten_fighting.gif", :fetch_format=>:auto)
PHP:
cl_image_tag("kitten_fighting.gif", array("fetch_format"=>"auto"))
Python:
CloudinaryImage("kitten_fighting.gif").image(fetch_format="auto")
Node.js:
cloudinary.image("kitten_fighting.gif", {fetch_format: "auto"})
Java:
cloudinary.url().transformation(new Transformation().fetchFormat("auto")).imageTag("kitten_fighting.gif")
JS:
cl.imageTag('kitten_fighting.gif', {fetch_format: "auto"}).toHtml();
jQuery:
$.cloudinary.image("kitten_fighting.gif", {fetch_format: "auto"})
React:
<Image publicId="kitten_fighting.gif" >
  <Transformation fetch_format="auto" />
</Image>
Angular:
<cl-image public-id="kitten_fighting.gif" >
  <cl-transformation fetch_format="auto">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().FetchFormat("auto")).BuildImageTag("kitten_fighting.gif")
Android:
MediaManager.get().url().transformation(new Transformation().fetchFormat("auto")).generate("kitten_fighting.gif")

If you’re in Google Chrome or Opera, the f_auto parameter would have converted the GIF into an animated WebP file.

The Round-Up

Here’s a quick round-up of what we’ve covered so far:

  • Cloudinary automatically optimizes all the images on-the-fly.
  • You can resize and apply other on-demand transformation effects to your images.
  • You can change the image, while retaining the same URL.
  • Cloudinary’s opacity transformation and sprite generation features enables you to seamlessly design email campaigns that looks the same across all clients.
  • With on-the-fly image manipulation, you can generate watermarked versions of images, which are excellent to prevent email spams.
  • Cloudinary’s advanced analytics and reporting enables you to track your usage at all times.
  • Browser-based image format reporting delivers the image in the most optimal format, that is supported by the web browser.
  • Cloudinary also can optimize and apply various transformation effects to animated GIF images.

We’ve only begun to learn about Cloudinary’s amazing suite of image manipulation features. What we’ve discussed in the context of email marketing barely scratches the surface of the robust feature set offered by Cloudinary.

The good news is that all of these features would be applicable in multiple scenarios, in one way or the other. Did you know that Cloudinary serves these images via a global network of content delivery networks such as Akamai, Amazon Cloudfront and Fastly?

If you are looking to take your email marketing to the next level, Cloudinary is an excellent partner. The free Cloudinary account provides you enough quota to test the service and even possibly launch a trial email campaign.

Have you considered using Cloudinary in your email marketing programme? Let us know!

Sourav Kundu Sourav Kundu is an avid marketer with a passion for all things digital. From email marketing to app re-engagement campaigns, he loves putting on multiple hats during the workday. He's available for consultation over Twitter or his website.

The Future of Audio and Video on the Web

$
0
0

Web sites and platforms are becoming increasingly media-rich. Today, approximately 62 percent of internet traffic is made up of images, with audio and video constituting a growing percentage of the bytes.

The web’s ubiquity is making it possible to distribute engaging media experiences to users around the world. And the ecosystem of internet consumers and vendors have crafted APIs that enable developers to harness this power.

Cisco reports that 70 percent of current global internet traffic is video and it’s going to increase to 80 percent by 2020. This is huge!

In addition, considering the current trajectory of how media is consumed, it’s estimated that the next billion users will likely be all mobile. These new users will be diverse in every way; their location, cultural experience, level of computer expertise, connectivity and the types of devices via which they consume media content. Despite the facts about the next generation of users, the mobile web still has a lot of catching up to do.

Let’s take a look at these APIs and what the future holds for us with regard to audio and video on the web.

First, A Look Back

Consider how the mobile web has functioned overtime from the dot-com bubble. It didn’t play a big part in the audio and video innovation that has come so far. Why?

The mobile web was faced with a number of challenges:

  • Buffering: Flash was prevalent in the beginning of the web, when mobility wasn’t even a consideration. In most cases, we had large audio and video files sitting on servers. And when users tried to access them via the mobile web, it took a long time to download, thus making users wait endlessly for these files to play on their devices.

    Eventually individuals and companies began delivering small-sized video files to mobile users. There was no buffering, but the video quality was poor.

  • Bad Layout: Delivering video content via mobile was terrible in terms of the layout. In fact, there are many websites that still haven’t figured out the optimal way to deliver video content to mobile users. Most times, you still have to scroll the far end left or right of the mobile web page. Sometimes, a part of the video screen gets cut off except when you were using an IPhone.

  • No Offline Capabilities: There was no offline experience for users listening to audio or watching videos. Without internet connectivity, the video/audio stops playing. In many regions, low-end devices are common, and internet connections are very unreliable. In fact, many users in developing countries still find it hard to purchase data. But the mobile web had no capacity for caching and serving content while offline.

The Beginning of the Future

It’s no secret that the pace of innovation has accelerated for audio and video in the last decade and the mobile web is now providing a better user experience.

To ensure a great video experience, it must offer:

  • Fast playback
  • Ability to watch videos anywhere
  • Great user interface
  • High-quality video playback

Therefore, let’s take a good look at what is available today to provide great video experiences for the next billion users.

It’s well-known that if your video buffers, you lose viewers. The same goes when your video doesn’t start playing fast enough. So to please your viewers, you need to be able to quickly deliver videos.

One way of ensuring fast playback is using the Adaptive Bitrate Streaming technique. This technique enables videos to start quicker, with fewer buffering interruptions. Multiple streams of the video with different resolutions, qualities and bitrate are created, and during playback, the video player determines and selects the optimal stream in response to the end user’s internet connectivity. The video player automatically switches between variants (also known as streams) to adapt to changing network connections.

Today, we have services like Cloudinary that provide plug-and-play adaptive bitrate streaming with HLS and Mpeg-dash to enable fast video playback. And Cloudinary supports a lot of transformations you can apply to videosand deliver to users with minimal efforts.

In addition, startup-time for videos is very important. One good technique to employ when serving videos with little or no delay in startup time is precaching videos with service workers. With service workers, you can precache all your videos on page load before users start interacting with the videos. Cloudinary also delivers video via fast content delivery networks (CDNs), which provide advanced caching techniques that offloads the caching from the developer.

Ability to Watch Videos Anywhere

Users should be able to watch videos or listen to audio offline. Again, with Service Workers, you can control the logic of what version(s) of video or audio file users should download. A simple save for offline viewing/listening button can be created that once the user clicks it, the service worker caches the video and makes it available when there is no connectivity.

Another tool you can use with the service worker is background fetch. By default, service workers are killed when the user navigates away from a site or closes the browser. With background fetch, downloads are enabled in the background while the user navigates away.

“img1”
img1
“img2”
img2

Video Compression

Imagine being able to cut down your video file size by 40 percent, while preserving the same quality. Today, we have VP9, the WebM project’s next-generation open source video codec. VP9 offers:

  • A 15 percent better compression efficiency
  • Supported on more than 2 billion devices
  • Higher quality videos

Google’s VP9 codec was mainly used on YouTube before general adoption by the public. This compression technology amassed great gains for YouTube, which saw a video starting 15 to 18 percent faster using VP9 compared to other codecs, and 50 percent less buffering. Native support for VP9 video codec already exists across media devices, laptops and browsers. Another codec worthy of mention is HEVC (High Efficiency Video Coding), also known as H.265. It is a successor to AVC (Advanced Video Coding). It offers very good compression benefits while retaining quality. And it is processor intensive. The licensing fees are one of the main reasons HEVC adoption has been low on the web.

User Experience

Today, we have the opportunity to provide even better experiences for our users. One of these experiences is enabling the user to listen to/control an audio or video playlist from a device’s lock screen or from the notifications tab. How is that possible? It’s via the Media Session API. The Media Session API enables you to customize media notifications by providing metadata for the media your web app is playing. It also enables you to handle media-related events, such as seeking or track changing, which may come from notifications or media keys.

Source: developers.google.com

set media session

function setMediaSession() {
  if (!('mediaSession' in navigator)) {
    return;
  }
  let track = playlist[index];
  navigator.mediaSession.metadata = new MediaMetadata({
    title: track.title,
    artist: track.artist,
    artwork: track.artwork
  });
  navigator.mediaSession.setActionHandler('previoustrack', playPreviousVideo);
  navigator.mediaSession.setActionHandler('nexttrack', playNextVideo);
  navigator.mediaSession.setActionHandler('seekbackward', seekBackward);
  navigator.mediaSession.setActionHandler('seekforward', seekForward);
}
Without Media Session
Without Media Session
With Media Session
With Media Session

Source: developers.google.com

Another core part of an improved user experience is automatic fullscreen mode capabilities. When a user changes the orientation of the phone to landscape, the video player should automatically assume full screen mode. This capability is made possible by the [Screen Orientation API}(https://developer.mozilla.org/en/docs/Web/API/Screen/orientation).

var orientation = screen.orientation || screen.mozOrientation || screen.msOrientation;

orientation.addEventListener('change', () => {
  if (orientation.type.startWith('landscape')) {
    showFullScreenVideo();
  } else if (document.fullScreenElement) {
    document.exitFullscreen();
  }
});

The Future

We have explored various options available to provide an enhanced media experience for users in today’s world. Let’s quickly look at what we can expect in the future and how to prepare for it.

Color

There are new sets of standards designed to enhance the display of colors on our devices - from phones to TVs to monitors and more.

You may not realize that your display screens can not correctly reproduce all the colors you can see with your eyes. The new video standards around BT.2020 dramatically extend these colors.

Wider Range of Brightness

Today’s standard monitors have some challenges. They don’t have the capacity to display the full spectrum of brightness and blackness. In essence, blacks aren’t really that black and the brights aren’t really that bright. However, a new set of functions for converting digital values of brightness into what actually gets displayed on the screen are on the horizon.

The ability to know which devices can support High Dynamic Range (HDR) is key. Currently it is possible to do that with Chrome Canary.

var canPlay = MediaSource.isTypeSupported(VP9.0.0.1);

The Alliance Media group which consists of YouTube, Google, Amazon, Microsoft, Twitch, Mozilla, Hulu, Netflix and The BBC is now working on a new open source compression format to target HDR, wide color gamut, m4k, and 360 video, as well as providing the most demanding low bitrate solution imaginable for the billions of people who have slow internet connections.

This new format is the AV1 codec. As at the time of this writing, the codec is still in development, but there have been signs of significant progress so far. One of such noteworthy sign is that the AV1 codec is already achieving 20 percent better compression than VP9.

For VP9, there is support to determine if a device supports the codec and if it has the necessary hardware to render the frames and efficiently use power. Check this out:

if ('mediaCapabilities' in navigator) {
  // Get media capabilities for 640x480 at 30fps, 10kbps
  const configuration = { type: 'file', video: {
    contentType: 'video/webm; codecs="vp09.00.10.08"',
    width: 640,
    height: 480,
    bitrate: 10000,
    framerate: 30
  }};

  navigator.mediaCapabilities.decodingInfo(configuration).then((result) => {
    // Use VP9 if supported, smooth and power efficient
    vp9 = (result.supported && result.smooth && result.powerEfficient);
  });
}

360 Video

The demand for 360 videos and photos is gradually increasing. Social media has amplified the need for this type of videos. Furthermore, documentaries, adverts, real estate industries, wedding events have started adopting it more. Just recently, Vimeo announced the launch of 360 video on their platform. Users can now stream and sell 360 degree video content.

In the future, there will be massive adoption of 360 video content across different platforms. Kodak, Giroptic and various vendors are increasingly developing better and high quality cameras & devices to capture superior 360 video experience.

Live Streaming

Mark Zuckerberg once stated that Live video is the future. And he was not wrong! The likes of Facebook Live, Instagram Live, and Twitch has made more people content creators and brought a lot more people closer to the screen. Apps such as Periscope, Meerkat, YouNow and Blab are allowing people share their daily experiences live. In terms of growth of users and revenue, Youtube, Facebook and Twitter have seen massive adoption and experienced higher revenue as a result of incorporating live video features to their platform.

Right now, with a good 4G connection, you can just tap on a button on these social media platforms and you are live. In the future, it will be easier for people even with 3G connections or less to stream live or participate in a live video session!

Creation

The mobile web can do now what mobile apps already have the capability to do. A very good example is the mini-Snapchat clone, the mustache app created by Francois Beaufort. This web app can be added and launched from the home screen.

With Mustache, you can record a video of yourself with a mustache and shapes locating different parts of your face. It generates a preview of the recorded video and then you can share it with the world.

Before play/recording
Before play/recording
After play/recording
After play/recording

Note: You need to enable the experimental web platform API from your Chrome browser. Head over to chrome://flags and activate it from the list before trying to run the app.

Let’s analyze the APIs that made this mobile web app possible:

const faceDetector = new FaceDetector({ fastMode: true, maxDetectedFaces: 1 });

let faces = [];
let recorder;
let chunks = [];
let isDetectingFaces = false;
let easterEgg = false;
let showFace = false;

async function getUserMedia() {
  // Grab camera stream.
  const constraints = {
     video: {
       facingMode: 'user',
       frameRate: 60,
       width:  640,
       height: 480,
     }
  };
  video.srcObject = await navigator.mediaDevices.getUserMedia(constraints);
  await video.play();
  canvas.height = window.innerHeight;
  canvas.width = window.innerWidth;
  // HACK: Face Detector doesn't accept canvas whose width is odd.
  if (canvas.width % 2 == 1) {
    canvas.width += 1;
  }
  setTimeout(_ => { faces = []; }, 500);
  draw();
}

We have the FaceDetector API and and the mediaDevices API for camera video streaming available as experimental web platform APIs on Chrome, Mozilla and Microsoft edge.

Note: Cloudinary provides an advanced Facial Attribute detection API as an add-on that you can use in your web applications.

The experimental Shape Detection API enabled the possibility of drawing a mustache and hat at 60 fps on the screen while streaming the video. While, the Media Recorder API provides functionality to easily record media.

Note: Cloudinary provides an advanced OCR Text Detection and Extraction API as an add-on that you can use in your web applications.

In the source code of the mustache app, this is how the blob, which is the recorded video is been uploaded to the cloud:

async function uploadVideo(blob) {
  const url = new URL('https://www.googleapis.com/upload/storage/v1/b/pwa-mustache/o');
  url.searchParams.append('uploadType', 'media');
  url.searchParams.append('name', new Date().toISOString() + '.webm');
  // Upload video to Google Cloud Storage.
  const response = await fetch(url, {
    method: 'POST',
    body: blob,
    headers: new Headers({
      'Content-Type': 'video/webm',
      'Content-Length': blob.length
    })
  });
  const data = await response.json();
  shareButton.dataset.url = `https://storage.googleapis.com/${data.bucket}/${data.name}`;
}

With Cloudinary, you can upload videos directly from the browser to the cloud and have the video URL returned to you with a simple API. You also can apply transformations on the video.

The APIs mentioned in the building of this app are pretty experimental and are currently being developed. But we expect they will become mainstream and stable in the future. Check out the source code for the full implementation.

Conclusion

Over the last few years, the web has seen a lot of innovation and advancement. Flash will no longer be supported by Adobe in 2020, HTML5 is now mainstream, several APIs have been, or are currently being, developed to bring native capabilities to the web. Better video codecs have come on board to ensure smooth and more efficient delivery and display of audio and video to users.

I’m really excited for what the future holds. Much of the information in this article came from Google I/O’s powerful session on the future of audio and video on the web. Many thanks to Google and the developer community for always championing the progressive movement of the web.

Finally, a huge shout out to the amazing Paul Lewis for developing the progressive video-on-demand Biograf app. The source code is available on GitHub.

Prosper Otemuyiwa Prosper Otemuyiwa is a Food Ninja, Open Source Advocate & Self-acclaimed Developer Evangelist.

Make All Images on Your Website Responsive in 3 Easy Steps

$
0
0

Images are crucial to website performance, but most still don't implement responsive images. It’s not just about fitting an image on the screen, but also making the the image size relatively rational to the device. The srcset and sizes options, which are your best hit are hard to implement. Cloudinary provides an easier way, which we will discuss in this article.

In this blog, I’ll share a valuable shortcut that can help you turn all images responsive. But first, let’s talk a bit about what responsive images are: An image is considered responsive if it retains its quality on varying device sizes without having an unpleasant impact on performance.

To better understand this concept, let's take a look at some device dimensions:

Possible Full-Width Image Dimensions by Screen Sizes

  • Large/X-Large commercial screens: 2000+ pixels
  • Websites: 760 - 1200 pixels
  • Mobile Phones: < 760 pixels

Let’s assume you have a mobile-first strategy for building responsive apps. You may decide to use 760px images throughout, without considering larger screens. If these images are meant to take the full width of the devices on which they are rendered, then your content will look distorted and unprofessional on websites or commercial screens.

Your next attempt would be to use the largest possible image (2000px) and scale it down based on the screen sizes on which it is displayed.

Down-Scaling Images

Using CSS or JavaScript to down-scale images only makes them dimensionally responsive. The following image illustrates this better:

Down-scaling illustration
On mobile

Down-scaling illustration
On desktop

Both on web and mobile devices, the size is still 8.9MB. Bearing in mind that your mobile phones have less resources than your PC, we still have more work to do.

Using only a down-scaling approach is not ideal because it does not account for the size of the image; just its dimensions.

We have already seen that up-scaling wouldn't work either, hence we need something that handles both dimensions and size.

Size-Fitting

Our best option is to generate images based on the screen size and render them. This process can be extremely complex, but there’s a shortcut that can help you automate much of the process. You can make all images responsive in three easy steps with Cloudinary:

1. Include Cloudinary in Your Project

Add the Cloudinary SDK to your project simply by including it in your index.html using script tags:

<script src="https://cdnjs.cloudflare.com/ajax/libs/cloudinary-core/2.3.0/cloudinary-core-shrinkwrap.min.js"></script>

2. Add Images with data-src

You don't want the images rendered immediately, until JavaScript runs. Hence, include the image using the data-src attribute instead of src:

<img data-src="https://res.cloudinary.com/christekh/image/upload/w_auto,c_scale/v1501761946/pexels-photo-457044_etqwsd.jpg" alt="" class="cld-responsive">

Using this approach, Cloudinary analyzes your browser screen first, resizes the image saved in Cloudinary storage as provided in data-src, and then renders the image in the appropriate size and dimension using JavaScript.

Two things to note from the tag:

  • w_auto,c_scale transformation parameters tell Cloudinary to dynamically generate an image URL scaled to the correct width value, based on the detected width actually available for the image in the containing element.
  • The class cld-responsive tells Cloudinary which images to apply this feature too.

3. JavaScript Call

Finally, initialize a Cloudinary instance in your JavaScript files and call the responsive method on this instance:

// Initialize
var cl = cloudinary.Cloudinary.new({  cloud_name: '<Cloud Name>' });
// Call
cl.responsive();

Remember to create a free Cloudinary account so you can be handed a cloud name for configuring this instance.

This piece of code will walk through the DOM, find all image tags with the class cld-responsive to apply size and dimension fitting images on them.

Final Words

Always keep in mind that when you use CSS like the following code below to make images responsive, it does not guarantee a good user experience:

img {
  width: 100%;
  height: auto;
}

The sizes of these images remain the same. Large images on mobile devices eat up resources (like allocated memory and running processes) causing slow downloads or unexpected behavior on the user's device. Responsive images ensure that users save lots of data bandwidth & have great experiences when using your image-rich website or app.

Lastly, it’s good to keep in mind that the suggested approach relies on JavaScript. Therefore, preloading provided by srcset and sizes are sacrificed and your browser must have JavaScript active for this feature to work.

*This was originally posted on Ponyfoo

Christian Nwamba Christian Nwamba (CodeBeast), is a JavaScript Preacher, Community Builder and Developer Evangelist. In his next life, Chris hopes to remain a computer programmer.
Viewing all 601 articles
Browse latest View live